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)
offx = max(0, min(int(self.display_offset[0]), offx_max))
offy = max(0, min(int(self.display_offset[1]), offy_max))
visible_w = self.window_width
visible_h = self.window_height
visible_w = min(new_w, self.window_width)
visible_h = min(new_h, self.window_height)
else:
offx = 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,
'offx': offx, 'offy': offy,
'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):
@@ -2350,21 +2352,10 @@ class VideoEditor:
# Send progress update
self.render_progress_queue.put(("progress", "Calculating output dimensions...", 0.05, 0.0))
# 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)
# Calculate output dimensions to MATCH preview visible region
params = self._get_display_params()
output_width = max(2, params['visible_w'] - (params['visible_w'] % 2))
output_height = max(2, params['visible_h'] - (params['visible_h'] % 2))
# Ensure dimensions are divisible by 2 for H.264 encoding
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))
# 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"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
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):
"""Process a single frame for rendering (optimized for speed)"""
try:
# Apply crop (vectorized operation)
if self.crop_rect:
if frame_number is None:
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))
# Apply rotation first to work in rotated space
if self.rotation_angle != 0:
frame = self.apply_rotation(frame)
# Clamp coordinates to frame bounds
# Apply EFFECTIVE crop regardless of whether a base crop exists, to enable follow and out-of-frame pad
x, y, w, h = self._get_effective_crop_rect_for_frame(frame_number or self.current_frame)
# Allow out-of-bounds by padding with black so center can remain when near edges
h_frame, w_frame = frame.shape[:2]
pad_left = max(0, -x)
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 and h > 0:
frame = frame[y : y + h, x : x + w]
else:
if w <= 0 or h <= 0:
return None
frame = frame[y : y + h, x : x + w]
# Apply brightness and contrast
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
if self.zoom_factor != 1.0:
height, width = frame.shape[:2]