Refactor frame processing in VideoEditor to enhance cropping and rotation handling

This commit updates the frame processing logic in the VideoEditor class to apply rotation before cropping, ensuring accurate handling of frames in rotated space. It introduces effective cropping that accommodates out-of-bounds scenarios by padding with black, improving the visual output during editing. Additionally, debug statements have been refined to provide clearer information on output dimensions and effective crop details, enhancing the overall user experience in video editing.
This commit is contained in:
2025-09-17 09:00:14 +02:00
parent f1d4145e43
commit e80278a2dd

View File

@@ -1206,8 +1206,8 @@ class VideoEditor:
offy_max = max(0, new_h - self.window_height) offy_max = max(0, new_h - self.window_height)
offx = max(0, min(int(self.display_offset[0]), offx_max)) offx = max(0, min(int(self.display_offset[0]), offx_max))
offy = max(0, min(int(self.display_offset[1]), offy_max)) offy = max(0, min(int(self.display_offset[1]), offy_max))
visible_w = self.window_width visible_w = min(new_w, self.window_width)
visible_h = self.window_height visible_h = min(new_h, self.window_height)
else: else:
offx = 0 offx = 0
offy = 0 offy = 0
@@ -1224,7 +1224,9 @@ class VideoEditor:
'eff_x': eff_x, 'eff_y': eff_y, 'eff_w': eff_w, 'eff_h': eff_h, 'eff_x': eff_x, 'eff_y': eff_y, 'eff_w': eff_w, 'eff_h': eff_h,
'offx': offx, 'offy': offy, 'offx': offx, 'offy': offy,
'scale': scale, 'scale': scale,
'start_x': start_x, 'start_y': start_y 'start_x': start_x, 'start_y': start_y,
'visible_w': visible_w, 'visible_h': visible_h,
'available_h': available_height
} }
def _map_original_to_screen(self, ox, oy): def _map_original_to_screen(self, ox, oy):
@@ -2350,21 +2352,10 @@ class VideoEditor:
# Send progress update # Send progress update
self.render_progress_queue.put(("progress", "Calculating output dimensions...", 0.05, 0.0)) self.render_progress_queue.put(("progress", "Calculating output dimensions...", 0.05, 0.0))
# Calculate output dimensions (accounting for rotation) # Calculate output dimensions to MATCH preview visible region
if self.crop_rect: params = self._get_display_params()
crop_width = int(self.crop_rect[2]) output_width = max(2, params['visible_w'] - (params['visible_w'] % 2))
crop_height = int(self.crop_rect[3]) output_height = max(2, params['visible_h'] - (params['visible_h'] % 2))
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)
# Ensure dimensions are divisible by 2 for H.264 encoding # Ensure dimensions are divisible by 2 for H.264 encoding
output_width = output_width - (output_width % 2) output_width = output_width - (output_width % 2)
@@ -2374,9 +2365,10 @@ class VideoEditor:
self.render_progress_queue.put(("progress", "Setting up FFmpeg encoder...", 0.1, 0.0)) self.render_progress_queue.put(("progress", "Setting up FFmpeg encoder...", 0.1, 0.0))
# Debug output dimensions # Debug output dimensions
print(f"Output dimensions: {output_width}x{output_height}") print(f"Output dimensions (match preview): {output_width}x{output_height}")
print(f"Zoom factor: {self.zoom_factor}") print(f"Zoom factor: {self.zoom_factor}")
print(f"Crop dimensions: {crop_width}x{crop_height}") eff_x, eff_y, eff_w, eff_h = self._get_effective_crop_rect_for_frame(start_frame)
print(f"Effective crop (rotated): {eff_x},{eff_y} {eff_w}x{eff_h}")
# Skip all the OpenCV codec bullshit and go straight to FFmpeg # Skip all the OpenCV codec bullshit and go straight to FFmpeg
print("Using FFmpeg for encoding with OpenCV transformations...") print("Using FFmpeg for encoding with OpenCV transformations...")
@@ -2526,32 +2518,45 @@ class VideoEditor:
def _process_frame_for_render(self, frame, output_width: int, output_height: int, frame_number: int = None): def _process_frame_for_render(self, frame, output_width: int, output_height: int, frame_number: int = None):
"""Process a single frame for rendering (optimized for speed)""" """Process a single frame for rendering (optimized for speed)"""
try: try:
# Apply crop (vectorized operation) # Apply rotation first to work in rotated space
if self.crop_rect: if self.rotation_angle != 0:
if frame_number is None: frame = self.apply_rotation(frame)
x, y, w, h = map(int, self.crop_rect)
else:
x, y, w, h = map(int, self._get_effective_crop_rect_for_frame(frame_number))
# Clamp coordinates to frame bounds # Apply EFFECTIVE crop regardless of whether a base crop exists, to enable follow and out-of-frame pad
h_frame, w_frame = frame.shape[:2] x, y, w, h = self._get_effective_crop_rect_for_frame(frame_number or self.current_frame)
x = max(0, min(x, w_frame - 1))
y = max(0, min(y, h_frame - 1))
w = min(w, w_frame - x)
h = min(h, h_frame - y)
if w > 0 and h > 0: # Allow out-of-bounds by padding with black so center can remain when near edges
frame = frame[y : y + h, x : x + w] h_frame, w_frame = frame.shape[:2]
else: pad_left = max(0, -x)
return None pad_top = max(0, -y)
pad_right = max(0, (x + w) - w_frame)
pad_bottom = max(0, (y + h) - h_frame)
if any(p > 0 for p in (pad_left, pad_top, pad_right, pad_bottom)):
frame = cv2.copyMakeBorder(
frame,
pad_top,
pad_bottom,
pad_left,
pad_right,
borderType=cv2.BORDER_CONSTANT,
value=(0, 0, 0),
)
x = x + pad_left
y = y + pad_top
w_frame, h_frame = frame.shape[1], frame.shape[0]
# Clamp crop to padded frame
x = max(0, min(x, w_frame - 1))
y = max(0, min(y, h_frame - 1))
w = min(w, w_frame - x)
h = min(h, h_frame - y)
if w <= 0 or h <= 0:
return None
frame = frame[y : y + h, x : x + w]
# Apply brightness and contrast # Apply brightness and contrast
frame = self.apply_brightness_contrast(frame) frame = self.apply_brightness_contrast(frame)
# Apply rotation
if self.rotation_angle != 0:
frame = self.apply_rotation(frame)
# Apply zoom and resize directly to final output dimensions # Apply zoom and resize directly to final output dimensions
if self.zoom_factor != 1.0: if self.zoom_factor != 1.0:
height, width = frame.shape[:2] height, width = frame.shape[:2]