Add a render progress bar

This commit is contained in:
2025-09-04 15:46:45 +02:00
parent 31240dabf9
commit dbefc5b359

View File

@@ -28,6 +28,16 @@ class VideoEditor:
TIMELINE_COLOR_BORDER = (200, 200, 200) TIMELINE_COLOR_BORDER = (200, 200, 200)
TIMELINE_COLOR_CUT_POINT = (255, 0, 0) TIMELINE_COLOR_CUT_POINT = (255, 0, 0)
# Progress bar configuration
PROGRESS_BAR_HEIGHT = 30
PROGRESS_BAR_MARGIN_PERCENT = 5 # 5% margin on each side
PROGRESS_BAR_TOP_MARGIN = 20 # Fixed top margin
PROGRESS_BAR_FADE_DURATION = 3.0 # seconds to fade out after completion
PROGRESS_BAR_COLOR_BG = (50, 50, 50)
PROGRESS_BAR_COLOR_FILL = (0, 255, 0) # Green when complete
PROGRESS_BAR_COLOR_PROGRESS = (0, 120, 255) # Blue during progress
PROGRESS_BAR_COLOR_BORDER = (200, 200, 200)
# Zoom and crop settings # Zoom and crop settings
MIN_ZOOM = 0.1 MIN_ZOOM = 0.1
MAX_ZOOM = 10.0 MAX_ZOOM = 10.0
@@ -94,6 +104,14 @@ class VideoEditor:
# Display offset for panning when zoomed # Display offset for panning when zoomed
self.display_offset = [0, 0] self.display_offset = [0, 0]
# Progress bar state
self.progress_bar_visible = False
self.progress_bar_progress = 0.0 # 0.0 to 1.0
self.progress_bar_complete = False
self.progress_bar_complete_time = None
self.progress_bar_text = ""
self.progress_bar_fps = 0.0 # Current rendering FPS
def _get_video_files_from_directory(self, directory: Path) -> List[Path]: def _get_video_files_from_directory(self, directory: Path) -> List[Path]:
"""Get all video files from a directory, sorted by name""" """Get all video files from a directory, sorted by name"""
video_files = [] video_files = []
@@ -296,6 +314,104 @@ class VideoEditor:
"""Adjust contrast by delta (0.1 to 3.0)""" """Adjust contrast by delta (0.1 to 3.0)"""
self.contrast = max(0.1, min(3.0, self.contrast + delta)) self.contrast = max(0.1, min(3.0, self.contrast + delta))
def show_progress_bar(self, text: str = "Processing..."):
"""Show progress bar with given text"""
self.progress_bar_visible = True
self.progress_bar_progress = 0.0
self.progress_bar_complete = False
self.progress_bar_complete_time = None
self.progress_bar_text = text
def update_progress_bar(self, progress: float, text: str = None, fps: float = None):
"""Update progress bar progress (0.0 to 1.0) and optionally text and FPS"""
if self.progress_bar_visible:
self.progress_bar_progress = max(0.0, min(1.0, progress))
if text is not None:
self.progress_bar_text = text
if fps is not None:
self.progress_bar_fps = fps
# Mark as complete when reaching 100%
if self.progress_bar_progress >= 1.0 and not self.progress_bar_complete:
self.progress_bar_complete = True
self.progress_bar_complete_time = time.time()
def hide_progress_bar(self):
"""Hide progress bar"""
self.progress_bar_visible = False
self.progress_bar_complete = False
self.progress_bar_complete_time = None
self.progress_bar_fps = 0.0
def draw_progress_bar(self, frame):
"""Draw progress bar on frame if visible - positioned at top with full width"""
if not self.progress_bar_visible:
return
# Check if we should fade out
if self.progress_bar_complete and self.progress_bar_complete_time:
elapsed = time.time() - self.progress_bar_complete_time
if elapsed > self.PROGRESS_BAR_FADE_DURATION:
self.hide_progress_bar()
return
# Calculate fade alpha (1.0 at start, 0.0 at end)
fade_alpha = max(0.0, 1.0 - (elapsed / self.PROGRESS_BAR_FADE_DURATION))
else:
fade_alpha = 1.0
height, width = frame.shape[:2]
# Calculate progress bar position (top of frame with 5% margins)
margin_width = int(width * self.PROGRESS_BAR_MARGIN_PERCENT / 100)
bar_width = width - (2 * margin_width)
bar_x = margin_width
bar_y = self.PROGRESS_BAR_TOP_MARGIN
# Apply fade alpha to colors
bg_color = tuple(int(c * fade_alpha) for c in self.PROGRESS_BAR_COLOR_BG)
border_color = tuple(int(c * fade_alpha) for c in self.PROGRESS_BAR_COLOR_BORDER)
if self.progress_bar_complete:
fill_color = tuple(int(c * fade_alpha) for c in self.PROGRESS_BAR_COLOR_FILL)
else:
fill_color = tuple(int(c * fade_alpha) for c in self.PROGRESS_BAR_COLOR_PROGRESS)
# Draw background
cv2.rectangle(frame, (bar_x, bar_y), (bar_x + bar_width, bar_y + self.PROGRESS_BAR_HEIGHT), bg_color, -1)
# Draw progress fill
fill_width = int(bar_width * self.progress_bar_progress)
if fill_width > 0:
cv2.rectangle(frame, (bar_x, bar_y), (bar_x + fill_width, bar_y + self.PROGRESS_BAR_HEIGHT), fill_color, -1)
# Draw border
cv2.rectangle(frame, (bar_x, bar_y), (bar_x + bar_width, bar_y + self.PROGRESS_BAR_HEIGHT), border_color, 2)
# Draw progress percentage on the left
percentage_text = f"{self.progress_bar_progress * 100:.1f}%"
text_color = tuple(int(255 * fade_alpha) for _ in range(3))
cv2.putText(frame, percentage_text, (bar_x + 12, bar_y + 22), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 4)
cv2.putText(frame, percentage_text, (bar_x + 10, bar_y + 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, text_color, 2)
# Draw FPS on the right if available
if self.progress_bar_fps > 0:
fps_text = f"{self.progress_bar_fps:.1f} FPS"
fps_text_size = cv2.getTextSize(fps_text, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)[0]
fps_x = bar_x + bar_width - fps_text_size[0] - 10
cv2.putText(frame, fps_text, (fps_x + 2, bar_y + 22), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 4)
cv2.putText(frame, fps_text, (fps_x, bar_y + 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, text_color, 2)
# Draw main text in center
if self.progress_bar_text:
text_size = cv2.getTextSize(self.progress_bar_text, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)[0]
text_x = bar_x + (bar_width - text_size[0]) // 2
text_y = bar_y + 20
# Draw text shadow for better visibility
cv2.putText(frame, self.progress_bar_text, (text_x + 2, text_y + 2), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 4)
cv2.putText(frame, self.progress_bar_text, (text_x, text_y), cv2.FONT_HERSHEY_SIMPLEX, 0.5, text_color, 2)
def draw_timeline(self, frame): def draw_timeline(self, frame):
"""Draw timeline at the bottom of the frame""" """Draw timeline at the bottom of the frame"""
height, width = frame.shape[:2] height, width = frame.shape[:2]
@@ -582,6 +698,9 @@ class VideoEditor:
# Draw timeline # Draw timeline
self.draw_timeline(canvas) self.draw_timeline(canvas)
# Draw progress bar (if visible)
self.draw_progress_bar(canvas)
cv2.imshow("Video Editor", canvas) cv2.imshow("Video Editor", canvas)
def mouse_callback(self, event, x, y, flags, param): def mouse_callback(self, event, x, y, flags, param):
@@ -746,6 +865,9 @@ class VideoEditor:
print(f"Rendering video to {output_path}...") print(f"Rendering video to {output_path}...")
start_time = time.time() start_time = time.time()
# Show progress bar
self.show_progress_bar("Initializing render...")
# Determine frame range # Determine frame range
start_frame = self.cut_start_frame if self.cut_start_frame is not None else 0 start_frame = self.cut_start_frame if self.cut_start_frame is not None else 0
end_frame = ( end_frame = (
@@ -756,8 +878,12 @@ class VideoEditor:
if start_frame >= end_frame: if start_frame >= end_frame:
print("Invalid cut range!") print("Invalid cut range!")
self.update_progress_bar(1.0, "Error: Invalid cut range!")
return False return False
# Update progress for initialization
self.update_progress_bar(0.05, "Calculating output dimensions...")
# Calculate output dimensions (accounting for rotation) # Calculate output dimensions (accounting for rotation)
if self.crop_rect: if self.crop_rect:
crop_width = int(self.crop_rect[2]) crop_width = int(self.crop_rect[2])
@@ -774,6 +900,9 @@ class VideoEditor:
output_width = int(crop_width * self.zoom_factor) output_width = int(crop_width * self.zoom_factor)
output_height = int(crop_height * self.zoom_factor) output_height = int(crop_height * self.zoom_factor)
# Update progress for video writer setup
self.update_progress_bar(0.1, "Setting up video writer...")
# Use mp4v codec (most compatible with MP4) # Use mp4v codec (most compatible with MP4)
fourcc = cv2.VideoWriter_fourcc(*"mp4v") fourcc = cv2.VideoWriter_fourcc(*"mp4v")
out = cv2.VideoWriter( out = cv2.VideoWriter(
@@ -782,11 +911,13 @@ class VideoEditor:
if not out.isOpened(): if not out.isOpened():
print("Error: Could not open video writer!") print("Error: Could not open video writer!")
self.update_progress_bar(1.0, "Error: Could not open video writer!")
return False return False
# Simple sequential processing - the I/O is the bottleneck anyway # 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
last_progress_update = 0 last_progress_update = 0
last_display_update = 0
for frame_idx in range(start_frame, end_frame + 1): for frame_idx in range(start_frame, end_frame + 1):
# Read frame # Read frame
@@ -805,29 +936,44 @@ class VideoEditor:
out.write(processed_frame) out.write(processed_frame)
frames_written = frame_idx - start_frame + 1 frames_written = frame_idx - start_frame + 1
current_time = time.time()
# Update progress bar (10% to 95% of progress reserved for frame processing)
progress = 0.1 + (0.85 * (frames_written / total_output_frames))
# Throttled progress update # Throttled progress update
current_time = time.time()
if current_time - last_progress_update > 0.5: if current_time - last_progress_update > 0.5:
progress = frames_written / total_output_frames * 100
elapsed = current_time - start_time elapsed = current_time - start_time
fps_rate = frames_written / elapsed fps_rate = frames_written / elapsed
eta = (elapsed / frames_written) * ( eta = (elapsed / frames_written) * (
total_output_frames - frames_written total_output_frames - frames_written
) )
progress_text = f"Rendering {frames_written}/{total_output_frames} frames (ETA: {eta:.1f}s)"
self.update_progress_bar(progress, progress_text, fps_rate)
print( print(
f"Progress: {progress:.1f}% | {frames_written}/{total_output_frames} | " f"Progress: {progress*100:.1f}% | {frames_written}/{total_output_frames} | "
f"FPS: {fps_rate:.1f} | ETA: {eta:.1f}s\r", f"FPS: {fps_rate:.1f} | ETA: {eta:.1f}s\r",
end="", end="",
) )
last_progress_update = current_time last_progress_update = current_time
# Update display more frequently to show progress bar
if current_time - last_display_update > 0.1: # Update display every 100ms
self.display_current_frame()
cv2.waitKey(1) # Allow OpenCV to process events
last_display_update = current_time
out.release() out.release()
total_time = time.time() - start_time total_time = time.time() - start_time
total_frames_written = end_frame - start_frame + 1 total_frames_written = end_frame - start_frame + 1
avg_fps = total_frames_written / total_time if total_time > 0 else 0 avg_fps = total_frames_written / total_time if total_time > 0 else 0
# Complete the progress bar
self.update_progress_bar(1.0, f"Complete! Rendered {total_frames_written} frames in {total_time:.1f}s", avg_fps)
print(f"\nVideo rendered successfully to {output_path}") print(f"\nVideo rendered successfully to {output_path}")
print( print(
f"Rendered {total_frames_written} frames in {total_time:.2f}s (avg {avg_fps:.1f} FPS)" f"Rendered {total_frames_written} frames in {total_time:.2f}s (avg {avg_fps:.1f} FPS)"