Maybe faster.......
This commit is contained in:
@@ -6,9 +6,6 @@ import numpy as np
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, Tuple, List
|
from typing import Optional, Tuple, List
|
||||||
import time
|
import time
|
||||||
import threading
|
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
|
||||||
from queue import Queue
|
|
||||||
|
|
||||||
|
|
||||||
class VideoEditor:
|
class VideoEditor:
|
||||||
@@ -547,96 +544,45 @@ class VideoEditor:
|
|||||||
print("Error: Could not open video writer!")
|
print("Error: Could not open video writer!")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Simple sequential processing - the I/O is the bottleneck anyway
|
||||||
total_output_frames = end_frame - start_frame + 1
|
total_output_frames = end_frame - start_frame + 1
|
||||||
frames_written = 0
|
|
||||||
last_progress_update = 0
|
last_progress_update = 0
|
||||||
|
|
||||||
# Batch processing configuration
|
for frame_idx in range(start_frame, end_frame + 1):
|
||||||
batch_size = min(50, max(10, total_output_frames // 20)) # Adaptive batch size
|
# Read frame
|
||||||
frame_queue = Queue(maxsize=batch_size * 2)
|
self.cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx)
|
||||||
processed_queue = Queue(maxsize=batch_size * 2)
|
ret, frame = self.cap.read()
|
||||||
|
|
||||||
def frame_reader():
|
if not ret:
|
||||||
"""Background thread for reading frames"""
|
|
||||||
try:
|
|
||||||
for frame_idx in range(start_frame, end_frame + 1):
|
|
||||||
self.cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx)
|
|
||||||
ret, frame = self.cap.read()
|
|
||||||
if ret:
|
|
||||||
frame_queue.put((frame_idx, frame))
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
finally:
|
|
||||||
frame_queue.put(None) # Signal end of frames
|
|
||||||
|
|
||||||
def frame_processor():
|
|
||||||
"""Background thread for processing frames"""
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
item = frame_queue.get()
|
|
||||||
if item is None: # End signal
|
|
||||||
break
|
|
||||||
|
|
||||||
frame_idx, frame = item
|
|
||||||
processed_frame = self._process_frame_for_render(frame, output_width, output_height)
|
|
||||||
|
|
||||||
if processed_frame is not None:
|
|
||||||
processed_queue.put((frame_idx, processed_frame))
|
|
||||||
|
|
||||||
frame_queue.task_done()
|
|
||||||
finally:
|
|
||||||
processed_queue.put(None) # Signal end of processing
|
|
||||||
|
|
||||||
# Start background threads
|
|
||||||
reader_thread = threading.Thread(target=frame_reader, daemon=True)
|
|
||||||
processor_thread = threading.Thread(target=frame_processor, daemon=True)
|
|
||||||
|
|
||||||
reader_thread.start()
|
|
||||||
processor_thread.start()
|
|
||||||
|
|
||||||
# Main thread writes frames in order
|
|
||||||
expected_frame = start_frame
|
|
||||||
frame_buffer = {} # Buffer for out-of-order frames
|
|
||||||
|
|
||||||
while frames_written < total_output_frames:
|
|
||||||
item = processed_queue.get()
|
|
||||||
if item is None: # End signal
|
|
||||||
break
|
break
|
||||||
|
|
||||||
frame_idx, processed_frame = item
|
# Process and write frame directly (minimize memory copies)
|
||||||
frame_buffer[frame_idx] = processed_frame
|
processed_frame = self._process_frame_for_render(frame, output_width, output_height)
|
||||||
|
|
||||||
# Write frames in order
|
if processed_frame is not None:
|
||||||
while expected_frame in frame_buffer:
|
out.write(processed_frame)
|
||||||
out.write(frame_buffer.pop(expected_frame))
|
|
||||||
frames_written += 1
|
|
||||||
expected_frame += 1
|
|
||||||
|
|
||||||
# Progress update (throttled to avoid too frequent updates)
|
frames_written = frame_idx - start_frame + 1
|
||||||
|
|
||||||
|
# Throttled progress update
|
||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
if current_time - last_progress_update > 0.5: # Update every 0.5 seconds
|
if current_time - last_progress_update > 0.5:
|
||||||
progress = frames_written / total_output_frames * 100
|
progress = frames_written / total_output_frames * 100
|
||||||
elapsed = current_time - start_time
|
elapsed = current_time - start_time
|
||||||
if frames_written > 0:
|
fps_rate = frames_written / elapsed
|
||||||
eta = (elapsed / frames_written) * (total_output_frames - frames_written)
|
eta = (elapsed / frames_written) * (total_output_frames - frames_written)
|
||||||
fps_rate = frames_written / elapsed
|
print(f"Progress: {progress:.1f}% | {frames_written}/{total_output_frames} | "
|
||||||
print(f"Progress: {progress:.1f}% | {frames_written}/{total_output_frames} | "
|
f"FPS: {fps_rate:.1f} | ETA: {eta:.1f}s\r", end="")
|
||||||
f"FPS: {fps_rate:.1f} | ETA: {eta:.1f}s\r", end="")
|
|
||||||
last_progress_update = current_time
|
last_progress_update = current_time
|
||||||
|
|
||||||
processed_queue.task_done()
|
|
||||||
|
|
||||||
# Wait for threads to complete
|
|
||||||
reader_thread.join()
|
|
||||||
processor_thread.join()
|
|
||||||
|
|
||||||
out.release()
|
out.release()
|
||||||
|
|
||||||
total_time = time.time() - start_time
|
total_time = time.time() - start_time
|
||||||
avg_fps = frames_written / total_time if total_time > 0 else 0
|
total_frames_written = end_frame - start_frame + 1
|
||||||
|
avg_fps = total_frames_written / total_time if total_time > 0 else 0
|
||||||
|
|
||||||
print(f"\nVideo rendered successfully to {output_path}")
|
print(f"\nVideo rendered successfully to {output_path}")
|
||||||
print(f"Rendered {frames_written} frames in {total_time:.2f}s (avg {avg_fps:.1f} FPS)")
|
print(f"Rendered {total_frames_written} frames in {total_time:.2f}s (avg {avg_fps:.1f} FPS)")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _process_frame_for_render(self, frame, output_width: int, output_height: int):
|
def _process_frame_for_render(self, frame, output_width: int, output_height: int):
|
||||||
|
Reference in New Issue
Block a user