diff --git a/croppa/main.py b/croppa/main.py index 105245a..29bf228 100644 --- a/croppa/main.py +++ b/croppa/main.py @@ -103,7 +103,7 @@ class VideoEditor: # Display offset for panning when zoomed self.display_offset = [0, 0] - + # Progress bar state self.progress_bar_visible = False self.progress_bar_progress = 0.0 # 0.0 to 1.0 @@ -114,16 +114,31 @@ class VideoEditor: def _get_video_files_from_directory(self, directory: Path) -> List[Path]: """Get all video files from a directory, sorted by name""" - video_files = [] + video_files = set() for file_path in directory.iterdir(): if ( file_path.is_file() and file_path.suffix.lower() in self.VIDEO_EXTENSIONS - and not "_edited" in file_path.name ): - video_files.append(file_path) - return sorted(video_files) + video_files.add(file_path) + edited_videos = set() + for file_path in video_files: + if "_edited" in file_path.stem: + edited_videos.add(file_path) + + non_edited_videos = set() + for file_path in video_files: + if "_edited" in file_path.stem: + continue + edited_equivalent = file_path.with_name( + f"{file_path.stem}_edited{file_path.suffix}" + ) + if edited_equivalent in edited_videos: + continue + non_edited_videos.add(file_path) + + return sorted(non_edited_videos) def _load_video(self, video_path: Path): """Load a video file and initialize video properties""" if hasattr(self, "cap") and self.cap: @@ -322,7 +337,7 @@ class VideoEditor: 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: @@ -331,87 +346,163 @@ class VideoEditor: 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) - + 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) + 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) - + 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) - + 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) - + 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) - + 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) - + 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_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) - + 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_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) + 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): """Draw timeline at the bottom of the frame""" @@ -941,7 +1032,7 @@ class VideoEditor: # 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 if current_time - last_progress_update > 0.5: elapsed = current_time - start_time @@ -949,10 +1040,10 @@ class VideoEditor: eta = (elapsed / 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( f"Progress: {progress*100:.1f}% | {frames_written}/{total_output_frames} | " f"FPS: {fps_rate:.1f} | ETA: {eta:.1f}s\r", @@ -961,7 +1052,9 @@ class VideoEditor: 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 + 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 @@ -973,7 +1066,11 @@ class VideoEditor: 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) + 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(