refactor(croppa/main.py): improve screen coordinate to video frame conversion logic
This commit is contained in:
112
croppa/main.py
112
croppa/main.py
@@ -30,7 +30,7 @@ class VideoEditor:
|
||||
# Zoom and crop settings
|
||||
MIN_ZOOM = 0.1
|
||||
MAX_ZOOM = 10.0
|
||||
ZOOM_INCREMENT = 0.1
|
||||
ZOOM_INCREMENT = 0.25
|
||||
|
||||
def __init__(self, video_path: str):
|
||||
self.video_path = Path(video_path)
|
||||
@@ -330,44 +330,86 @@ class VideoEditor:
|
||||
|
||||
def set_crop_from_screen_coords(self, screen_rect):
|
||||
"""Convert screen coordinates to video frame coordinates and set crop"""
|
||||
# This is a simplified version - in a full implementation you'd need to
|
||||
# account for the scaling and positioning of the video frame within the window
|
||||
x, y, w, h = screen_rect
|
||||
|
||||
# Simple conversion assuming video fills the display area
|
||||
if self.current_display_frame is not None:
|
||||
frame_height, frame_width = self.current_display_frame.shape[:2]
|
||||
# Get the original frame dimensions
|
||||
original_height, original_width = self.current_display_frame.shape[:2]
|
||||
available_height = self.window_height - self.TIMELINE_HEIGHT
|
||||
|
||||
# Calculate video display area
|
||||
scale = min(self.window_width / frame_width, available_height / frame_height)
|
||||
# Calculate how the original frame is displayed (after crop/zoom)
|
||||
display_frame = self.apply_crop_and_zoom(self.current_display_frame.copy())
|
||||
if display_frame is None:
|
||||
return
|
||||
|
||||
display_height, display_width = display_frame.shape[:2]
|
||||
|
||||
# Calculate scale for the display frame
|
||||
scale = min(self.window_width / display_width, available_height / display_height)
|
||||
if scale < 1.0:
|
||||
display_width = int(frame_width * scale)
|
||||
display_height = int(frame_height * scale)
|
||||
final_display_width = int(display_width * scale)
|
||||
final_display_height = int(display_height * scale)
|
||||
else:
|
||||
display_width = frame_width
|
||||
display_height = frame_height
|
||||
final_display_width = display_width
|
||||
final_display_height = display_height
|
||||
scale = 1.0
|
||||
|
||||
start_x = (self.window_width - display_width) // 2
|
||||
start_y = (available_height - display_height) // 2
|
||||
start_x = (self.window_width - final_display_width) // 2
|
||||
start_y = (available_height - final_display_height) // 2
|
||||
|
||||
# Convert coordinates
|
||||
video_x = (x - start_x) / scale if scale < 1.0 else (x - start_x)
|
||||
video_y = (y - start_y) / scale if scale < 1.0 else (y - start_y)
|
||||
video_w = w / scale if scale < 1.0 else w
|
||||
video_h = h / scale if scale < 1.0 else h
|
||||
# Convert screen coordinates to display frame coordinates
|
||||
display_x = (x - start_x) / scale
|
||||
display_y = (y - start_y) / scale
|
||||
display_w = w / scale
|
||||
display_h = h / scale
|
||||
|
||||
# Clamp to video bounds
|
||||
video_x = max(0, min(video_x, frame_width))
|
||||
video_y = max(0, min(video_y, frame_height))
|
||||
video_w = min(video_w, frame_width - video_x)
|
||||
video_h = min(video_h, frame_height - video_y)
|
||||
# Clamp to display frame bounds
|
||||
display_x = max(0, min(display_x, display_width))
|
||||
display_y = max(0, min(display_y, display_height))
|
||||
display_w = min(display_w, display_width - display_x)
|
||||
display_h = min(display_h, display_height - display_y)
|
||||
|
||||
if video_w > 10 and video_h > 10: # Minimum size check
|
||||
# Convert display frame coordinates back to original frame coordinates
|
||||
# This is the inverse of apply_crop_and_zoom
|
||||
# The order in apply_crop_and_zoom is: crop first, then zoom
|
||||
# So we need to reverse: zoom first, then crop
|
||||
|
||||
# Step 1: Reverse zoom (zoom is applied to the cropped frame)
|
||||
if self.zoom_factor != 1.0:
|
||||
display_x = display_x / self.zoom_factor
|
||||
display_y = display_y / self.zoom_factor
|
||||
display_w = display_w / self.zoom_factor
|
||||
display_h = display_h / self.zoom_factor
|
||||
|
||||
# Step 2: Reverse crop (crop is applied to the original frame)
|
||||
original_x = display_x
|
||||
original_y = display_y
|
||||
original_w = display_w
|
||||
original_h = display_h
|
||||
|
||||
# Add the crop offset to get back to original frame coordinates
|
||||
if self.crop_rect:
|
||||
crop_x, crop_y, crop_w, crop_h = self.crop_rect
|
||||
original_x += crop_x
|
||||
original_y += crop_y
|
||||
|
||||
# Clamp to original frame bounds
|
||||
original_x = max(0, min(original_x, original_width))
|
||||
original_y = max(0, min(original_y, original_height))
|
||||
original_w = min(original_w, original_width - original_x)
|
||||
original_h = min(original_h, original_height - original_y)
|
||||
|
||||
print(f"DEBUG: Final crop coords: ({original_x}, {original_y}, {original_w}, {original_h})")
|
||||
print(f"DEBUG: Original frame size: {original_width}x{original_height}")
|
||||
|
||||
if original_w > 10 and original_h > 10: # Minimum size check
|
||||
# Save current crop for undo
|
||||
if self.crop_rect:
|
||||
self.crop_history.append(self.crop_rect)
|
||||
self.crop_rect = (video_x, video_y, video_w, video_h)
|
||||
self.crop_rect = (original_x, original_y, original_w, original_h)
|
||||
print(f"DEBUG: Crop set successfully")
|
||||
else:
|
||||
print(f"DEBUG: Crop too small or invalid, ignoring")
|
||||
|
||||
def seek_to_timeline_position(self, mouse_x, bar_x_start, bar_width):
|
||||
"""Seek to position based on mouse click on timeline"""
|
||||
@@ -406,6 +448,8 @@ class VideoEditor:
|
||||
output_width = int(self.frame_width * self.zoom_factor)
|
||||
output_height = int(self.frame_height * self.zoom_factor)
|
||||
|
||||
print(f"DEBUG: Expected output dimensions: {output_width}x{output_height}")
|
||||
|
||||
# Initialize video writer
|
||||
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
|
||||
out = cv2.VideoWriter(output_path, fourcc, self.fps, (output_width, output_height))
|
||||
@@ -428,12 +472,23 @@ class VideoEditor:
|
||||
if self.crop_rect:
|
||||
x, y, w, h = self.crop_rect
|
||||
x, y, w, h = int(x), int(y), int(w), int(h)
|
||||
|
||||
print(f"DEBUG: Applying crop: ({x}, {y}, {w}, {h}) to frame {frame.shape}")
|
||||
|
||||
# Ensure crop coordinates are within frame bounds
|
||||
x = max(0, min(x, frame.shape[1] - 1))
|
||||
y = max(0, min(y, frame.shape[0] - 1))
|
||||
w = min(w, frame.shape[1] - x)
|
||||
h = min(h, frame.shape[0] - y)
|
||||
|
||||
print(f"DEBUG: Adjusted crop: ({x}, {y}, {w}, {h})")
|
||||
|
||||
if w > 0 and h > 0:
|
||||
frame = frame[y:y+h, x:x+w]
|
||||
print(f"DEBUG: Frame after crop: {frame.shape}")
|
||||
else:
|
||||
print(f"ERROR: Invalid crop dimensions, skipping frame")
|
||||
continue
|
||||
|
||||
# Apply zoom
|
||||
if self.zoom_factor != 1.0:
|
||||
@@ -441,7 +496,14 @@ class VideoEditor:
|
||||
new_width = int(width * self.zoom_factor)
|
||||
new_height = int(height * self.zoom_factor)
|
||||
frame = cv2.resize(frame, (new_width, new_height), interpolation=cv2.INTER_LINEAR)
|
||||
print(f"DEBUG: Frame after zoom: {frame.shape}")
|
||||
|
||||
# Ensure frame matches output dimensions
|
||||
if frame.shape[1] != output_width or frame.shape[0] != output_height:
|
||||
print(f"DEBUG: Resizing frame from {frame.shape} to ({output_height}, {output_width}, 3)")
|
||||
frame = cv2.resize(frame, (output_width, output_height), interpolation=cv2.INTER_LINEAR)
|
||||
|
||||
print(f"DEBUG: Final frame shape: {frame.shape}")
|
||||
out.write(frame)
|
||||
|
||||
# Progress indicator
|
||||
|
Reference in New Issue
Block a user