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)
|
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
|
||||||
|
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]
|
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))
|
x = max(0, min(x, w_frame - 1))
|
||||||
y = max(0, min(y, h_frame - 1))
|
y = max(0, min(y, h_frame - 1))
|
||||||
w = min(w, w_frame - x)
|
w = min(w, w_frame - x)
|
||||||
h = min(h, h_frame - y)
|
h = min(h, h_frame - y)
|
||||||
|
if w <= 0 or h <= 0:
|
||||||
if w > 0 and h > 0:
|
|
||||||
frame = frame[y : y + h, x : x + w]
|
|
||||||
else:
|
|
||||||
return None
|
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]
|
||||||
|
Reference in New Issue
Block a user