diff --git a/croppa/main.py b/croppa/main.py index 74b70a5..ad04de4 100644 --- a/croppa/main.py +++ b/croppa/main.py @@ -460,6 +460,12 @@ class VideoEditor: # Try to load saved state for this media file if self.load_state(): print("Loaded saved state for this media file") + if self.cut_start_frame is not None: + print(f" Cut start frame: {self.cut_start_frame}") + if self.cut_end_frame is not None: + print(f" Cut end frame: {self.cut_end_frame}") + else: + print("No saved state found for this media file") def switch_to_video(self, index: int): """Switch to a specific video by index""" @@ -511,6 +517,11 @@ class VideoEditor: self, direction: int, shift_pressed: bool, ctrl_pressed: bool ): """Seek video with different frame counts based on modifiers and seek multiplier""" + # Don't allow seeking while rendering to prevent crashes + if self.is_rendering(): + print("Cannot seek while rendering is in progress") + return + if ctrl_pressed: base_frames = 60 # Ctrl: 60 frames elif shift_pressed: @@ -1500,7 +1511,7 @@ class VideoEditor: """Start video rendering in a separate thread""" # Check if already rendering if self.render_thread and self.render_thread.is_alive(): - print("Render already in progress!") + print("Render already in progress! Use 'X' to cancel first.") return False # Reset render state @@ -1515,10 +1526,12 @@ class VideoEditor: self.render_thread.start() print(f"Started rendering to {output_path} in background thread...") + print("You can continue editing while rendering. Press 'X' to cancel.") return True def _render_video_worker(self, output_path: str): """Worker method that runs in the render thread""" + render_cap = None try: if not output_path.endswith(".mp4"): output_path += ".mp4" @@ -1528,6 +1541,12 @@ class VideoEditor: # Send progress update to main thread self.render_progress_queue.put(("init", "Initializing render...", 0.0, 0.0)) + # 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(): + self.render_progress_queue.put(("error", "Could not open video for rendering!", 1.0, 0.0)) + return False + # Determine frame range start_frame = self.cut_start_frame if self.cut_start_frame is not None else 0 end_frame = ( @@ -1583,9 +1602,9 @@ class VideoEditor: self.render_progress_queue.put(("cancelled", "Render cancelled", 0.0, 0.0)) return False - # Read frame - self.cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx) - ret, frame = self.cap.read() + # 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 @@ -1634,9 +1653,20 @@ class VideoEditor: return True except Exception as e: - self.render_progress_queue.put(("error", f"Render error: {str(e)}", 1.0, 0.0)) - print(f"Render error: {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" + + self.render_progress_queue.put(("error", f"Render error: {error_msg}", 1.0, 0.0)) + print(f"Render error: {error_msg}") return False + finally: + # Always clean up the render VideoCapture + if render_cap: + render_cap.release() def update_render_progress(self): """Process progress updates from the render thread"""