refactor(main.py): add debug prints and validate cut markers against video length
This commit is contained in:
119
croppa/main.py
119
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
|
||||
|
Reference in New Issue
Block a user