From d1b26fe8b4fd3c7f4ed6ab86a06f07c82dc158fb Mon Sep 17 00:00:00 2001 From: PhatPhuckDave Date: Mon, 8 Sep 2025 00:36:37 +0200 Subject: [PATCH] refactor(main.py): add debug prints and validate cut markers against video length --- croppa/main.py | 119 ++++--------------------------------------------- 1 file changed, 9 insertions(+), 110 deletions(-) diff --git a/croppa/main.py b/croppa/main.py index 4f7bf87..a4fb196 100644 --- a/croppa/main.py +++ b/croppa/main.py @@ -208,6 +208,7 @@ class VideoEditor: self.current_frame = state['current_frame'] if 'crop_rect' in state and state['crop_rect'] is not None: self.crop_rect = tuple(state['crop_rect']) + print(f"DEBUG: Loaded crop_rect: {self.crop_rect}") if 'zoom_factor' in state: self.zoom_factor = state['zoom_factor'] if 'zoom_center' in state and state['zoom_center'] is not None: @@ -225,6 +226,14 @@ class VideoEditor: self.cut_end_frame = state['cut_end_frame'] print(f"Restored cut_end_frame: {self.cut_end_frame}") + # Validate cut markers against current video length + if self.cut_start_frame is not None and self.cut_start_frame >= self.total_frames: + print(f"DEBUG: cut_start_frame {self.cut_start_frame} is beyond video length {self.total_frames}, clearing") + self.cut_start_frame = None + if self.cut_end_frame is not None and self.cut_end_frame >= self.total_frames: + print(f"DEBUG: cut_end_frame {self.cut_end_frame} is beyond video length {self.total_frames}, clearing") + self.cut_end_frame = None + # Calculate and show marker positions on timeline if self.cut_start_frame is not None and self.cut_end_frame is not None: start_progress = self.cut_start_frame / max(1, self.total_frames - 1) @@ -1494,13 +1503,6 @@ class VideoEditor: else: return self._render_video_threaded(output_path) - def render_video_sync(self, output_path: str): - """Render video synchronously (for overwrite operations)""" - if self.is_image_mode: - return self._render_image(output_path) - else: - return self._render_video_sync(output_path) - def _render_video_threaded(self, output_path: str): """Start video rendering in a separate thread""" # Check if already rendering @@ -1523,109 +1525,6 @@ class VideoEditor: print("You can continue editing while rendering. Press 'X' to cancel.") return True - def _render_video_sync(self, output_path: str): - """Render video synchronously (for overwrite operations)""" - render_cap = None - try: - if not output_path.endswith(".mp4"): - output_path += ".mp4" - - start_time = time.time() - print("Rendering video synchronously...") - - # Create a separate VideoCapture for the render thread to avoid thread safety issues - render_cap = cv2.VideoCapture(str(self.video_path)) - if not render_cap.isOpened(): - print("Could not open video for rendering!") - return False - - # Determine frame range - start_frame = self.cut_start_frame if self.cut_start_frame is not None else 0 - end_frame = ( - self.cut_end_frame - if self.cut_end_frame is not None - else self.total_frames - 1 - ) - - if start_frame >= end_frame: - print("Invalid cut range!") - return False - - # Calculate output dimensions (accounting for rotation) - if self.crop_rect: - crop_width = int(self.crop_rect[2]) - crop_height = int(self.crop_rect[3]) - else: - crop_width = self.frame_width - crop_height = self.frame_height - - # Swap dimensions if rotation is 90 or 270 degrees - if self.rotation_angle == 90 or self.rotation_angle == 270: - output_width = int(crop_height * self.zoom_factor) - output_height = int(crop_width * self.zoom_factor) - else: - output_width = int(crop_width * self.zoom_factor) - output_height = int(crop_height * self.zoom_factor) - - # Use mp4v codec (most compatible with MP4) - fourcc = cv2.VideoWriter_fourcc(*"mp4v") - out = cv2.VideoWriter( - output_path, fourcc, self.fps, (output_width, output_height) - ) - - if not out.isOpened(): - print("Could not open video writer!") - return False - - # Simple sequential processing - total_output_frames = end_frame - start_frame + 1 - frames_written = 0 - - for frame_idx in range(start_frame, end_frame + 1): - # Read frame using the separate VideoCapture - render_cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx) - ret, frame = render_cap.read() - - if not ret: - break - - # Process and write frame directly - processed_frame = self._process_frame_for_render( - frame, output_width, output_height - ) - - if processed_frame is not None: - out.write(processed_frame) - frames_written += 1 - - out.release() - - # Ensure the video writer is completely closed and file handles are freed - del out - time.sleep(0.1) # Small delay to ensure file is unlocked - - total_time = time.time() - start_time - avg_fps = frames_written / total_time if total_time > 0 else 0 - - print(f"Video rendered successfully to {output_path}") - print(f"Rendered {frames_written} frames in {total_time:.2f}s (avg {avg_fps:.1f} FPS)") - return True - - except Exception as e: - error_msg = str(e) - # Handle specific FFmpeg threading errors - if "async_lock" in error_msg or "pthread_frame" in error_msg: - error_msg = "FFmpeg threading error - try restarting the application" - elif "Assertion" in error_msg: - error_msg = "Video codec error - the video file may be corrupted or incompatible" - - print(f"Render error: {error_msg}") - return False - finally: - # Always clean up the render VideoCapture - if render_cap: - render_cap.release() - def _render_video_worker(self, output_path: str): """Worker method that runs in the render thread""" render_cap = None