From 6559310adc89bdf58e0a24b3e67e67a7c7e16882 Mon Sep 17 00:00:00 2001 From: PhatPhuckDave Date: Mon, 8 Sep 2025 20:24:59 +0200 Subject: [PATCH] Fix rendering again --- croppa/main.py | 167 ++++++++++++++++++++++++++++--------------------- 1 file changed, 96 insertions(+), 71 deletions(-) diff --git a/croppa/main.py b/croppa/main.py index 2fb1698..ca7d0af 100644 --- a/croppa/main.py +++ b/croppa/main.py @@ -1626,6 +1626,11 @@ class VideoEditor: # Send progress update self.render_progress_queue.put(("progress", "Setting up FFmpeg encoder...", 0.1, 0.0)) + # Debug output dimensions + print(f"Output dimensions: {output_width}x{output_height}") + print(f"Zoom factor: {self.zoom_factor}") + print(f"Crop dimensions: {crop_width}x{crop_height}") + # Skip all the OpenCV codec bullshit and go straight to FFmpeg print("Using FFmpeg for encoding with OpenCV transformations...") return self._render_with_ffmpeg_pipe(output_path, start_frame, end_frame, output_width, output_height) @@ -1663,6 +1668,8 @@ class VideoEditor: self._handle_overwrite_completion() elif update_type == "error": self.update_progress_bar(progress, text, fps) + # Also show error as feedback message for better visibility + self.show_feedback_message(f"ERROR: {text}") elif update_type == "cancelled": self.hide_progress_bar() self.show_feedback_message("Render cancelled") @@ -1826,35 +1833,23 @@ class VideoEditor: return None def _render_with_ffmpeg_pipe(self, output_path: str, start_frame: int, end_frame: int, output_width: int, output_height: int): - """Hybrid approach: OpenCV transformations + FFmpeg encoding via pipe""" + """Hybrid approach: OpenCV transformations + FFmpeg encoding via temporary file""" + temp_raw_file = None try: + # Verify FFmpeg is available + try: + subprocess.run(['ffmpeg', '-version'], capture_output=True, check=True) + except (subprocess.CalledProcessError, FileNotFoundError): + error_msg = "FFmpeg not found - please install FFmpeg and ensure it's in your PATH" + print(error_msg) + self.render_progress_queue.put(("error", error_msg, 1.0, 0.0)) + return False + self.render_progress_queue.put(("progress", "Starting FFmpeg encoder...", 0.0, 0.0)) - # Start FFmpeg process to receive frames via pipe - # Use Windows-friendly approach with explicit binary mode - ffmpeg_cmd = [ - 'ffmpeg', '-y', '-v', 'quiet', - '-f', 'rawvideo', - '-vcodec', 'rawvideo', - '-s', f'{output_width}x{output_height}', - '-pix_fmt', 'bgr24', - '-r', str(self.fps), - '-i', '-', # Read from stdin - '-c:v', 'libx264', - '-preset', 'fast', - '-crf', '18', - '-pix_fmt', 'yuv420p', - output_path - ] - - # Start FFmpeg process with Windows-friendly settings - self.ffmpeg_process = subprocess.Popen( - ffmpeg_cmd, - stdin=subprocess.PIPE, - stderr=subprocess.PIPE, - bufsize=0, # Unbuffered for better pipe performance - universal_newlines=False # Binary mode for Windows - ) + # Create temporary raw video file instead of using pipes + temp_raw_file = output_path.replace('.mp4', '_temp.raw') + print(f"Using temporary file approach: {temp_raw_file}") # OpenCV for frame reading and transformations render_cap = cv2.VideoCapture(str(self.video_path)) @@ -1867,70 +1862,100 @@ class VideoEditor: self.render_progress_queue.put(("progress", f"Processing {total_frames} frames...", 0.1, 0.0)) - for i in range(total_frames): - if self.render_cancelled: - self.ffmpeg_process.stdin.close() - self.ffmpeg_process.terminate() - self.ffmpeg_process.wait() - render_cap.release() - self.ffmpeg_process = None - self.render_progress_queue.put(("cancelled", "Render cancelled", 0.0, 0.0)) - return False + # Write frames to temporary raw file + with open(temp_raw_file, 'wb') as raw_file: + for i in range(total_frames): + if self.render_cancelled: + render_cap.release() + self.render_progress_queue.put(("cancelled", "Render cancelled", 0.0, 0.0)) + return False - ret, frame = render_cap.read() - if not ret: - break - - # Apply transformations with OpenCV - processed_frame = self._process_frame_for_render(frame, output_width, output_height) - if processed_frame is not None: - # Write frame to FFmpeg via pipe - try: - self.ffmpeg_process.stdin.write(processed_frame.tobytes()) - frames_written += 1 - except BrokenPipeError: - # FFmpeg process died + ret, frame = render_cap.read() + if not ret: break - # Update progress with FPS calculation - current_time = time.time() - progress = 0.1 + (0.8 * (i + 1) / total_frames) - - # Calculate FPS and update progress (throttled) - if current_time - last_progress_update > 0.5: - elapsed = current_time - start_time - fps_rate = frames_written / elapsed if elapsed > 0 else 0 - self.render_progress_queue.put(("progress", f"Processed {i+1}/{total_frames} frames", progress, fps_rate)) - last_progress_update = current_time + # Apply transformations with OpenCV + processed_frame = self._process_frame_for_render(frame, output_width, output_height) + if processed_frame is not None: + # Debug frame dimensions + if i == 0: # Only print for first frame + print(f"Processed frame dimensions: {processed_frame.shape[1]}x{processed_frame.shape[0]}") + print(f"Expected dimensions: {output_width}x{output_height}") + + # Write frame to temporary raw file + raw_file.write(processed_frame.tobytes()) + frames_written += 1 - # Close FFmpeg input and wait for completion - self.ffmpeg_process.stdin.close() - stderr = self.ffmpeg_process.communicate()[1] - return_code = self.ffmpeg_process.returncode - self.ffmpeg_process = None + # Update progress with FPS calculation + current_time = time.time() + progress = 0.1 + (0.8 * (i + 1) / total_frames) + + # Calculate FPS and update progress (throttled) + if current_time - last_progress_update > 0.5: + elapsed = current_time - start_time + fps_rate = frames_written / elapsed if elapsed > 0 else 0 + self.render_progress_queue.put(("progress", f"Processed {i+1}/{total_frames} frames", progress, fps_rate)) + last_progress_update = current_time render_cap.release() + # Now encode the raw file with FFmpeg + self.render_progress_queue.put(("progress", "Encoding with FFmpeg...", 0.9, 0.0)) + + ffmpeg_cmd = [ + 'ffmpeg', '-y', '-v', 'error', + '-f', 'rawvideo', + '-vcodec', 'rawvideo', + '-s', f'{output_width}x{output_height}', + '-pix_fmt', 'bgr24', + '-r', str(self.fps), + '-i', temp_raw_file, + '-c:v', 'libx264', + '-preset', 'fast', + '-crf', '18', + '-pix_fmt', 'yuv420p', + output_path + ] + + # Run FFmpeg encoding + result = subprocess.run(ffmpeg_cmd, capture_output=True, text=True) + return_code = result.returncode + stderr = result.stderr + if return_code == 0: total_time = time.time() - start_time avg_fps = frames_written / total_time if total_time > 0 else 0 self.render_progress_queue.put(("complete", f"Rendered {frames_written} frames with FFmpeg", 1.0, avg_fps)) - print(f"Successfully rendered {frames_written} frames using FFmpeg pipe (avg {avg_fps:.1f} FPS)") + print(f"Successfully rendered {frames_written} frames using FFmpeg (avg {avg_fps:.1f} FPS)") return True else: - self.render_progress_queue.put(("error", f"FFmpeg encoding failed: {stderr.decode()}", 1.0, 0.0)) + error_details = stderr if stderr else "No error details available" + print(f"FFmpeg encoding failed with return code {return_code}") + print(f"FFmpeg stderr: {error_details}") + self.render_progress_queue.put(("error", f"FFmpeg encoding failed: {error_details}", 1.0, 0.0)) return False except Exception as e: error_msg = str(e) - # Handle specific Windows pipe errors - if "Errno 22" in error_msg or "invalid argument" in error_msg.lower(): - error_msg = "Windows pipe error - try using a different output path or restart the application" - elif "BrokenPipeError" in error_msg: - error_msg = "FFmpeg process terminated unexpectedly - check if FFmpeg is installed correctly" + print(f"FFmpeg rendering exception: {error_msg}") + print(f"Exception type: {type(e).__name__}") - self.render_progress_queue.put(("error", f"FFmpeg pipe rendering failed: {error_msg}", 1.0, 0.0)) + # Handle specific errors + if "Errno 22" in error_msg or "invalid argument" in error_msg.lower(): + error_msg = "File system error - try using a different output path" + elif "FileNotFoundError" in error_msg or "ffmpeg" in error_msg.lower(): + error_msg = "FFmpeg not found - please install FFmpeg and ensure it's in your PATH" + + self.render_progress_queue.put(("error", f"FFmpeg rendering failed: {error_msg}", 1.0, 0.0)) return False + finally: + # Clean up temporary file + if temp_raw_file and os.path.exists(temp_raw_file): + try: + os.remove(temp_raw_file) + print(f"Cleaned up temporary file: {temp_raw_file}") + except Exception as e: + print(f"Warning: Could not remove temporary file {temp_raw_file}: {e}") def run(self): """Main editor loop"""