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:
@@ -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]
|
||||
|
Reference in New Issue
Block a user