refactor(croppa/main.py): improve screen coordinate to video frame conversion logic

This commit is contained in:
2025-08-19 09:39:46 +02:00
parent b2f28f215c
commit cdad9198bc

View File

@@ -30,7 +30,7 @@ class VideoEditor:
# Zoom and crop settings # Zoom and crop settings
MIN_ZOOM = 0.1 MIN_ZOOM = 0.1
MAX_ZOOM = 10.0 MAX_ZOOM = 10.0
ZOOM_INCREMENT = 0.1 ZOOM_INCREMENT = 0.25
def __init__(self, video_path: str): def __init__(self, video_path: str):
self.video_path = Path(video_path) self.video_path = Path(video_path)
@@ -330,44 +330,86 @@ class VideoEditor:
def set_crop_from_screen_coords(self, screen_rect): def set_crop_from_screen_coords(self, screen_rect):
"""Convert screen coordinates to video frame coordinates and set crop""" """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 x, y, w, h = screen_rect
# Simple conversion assuming video fills the display area
if self.current_display_frame is not None: 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 available_height = self.window_height - self.TIMELINE_HEIGHT
# Calculate video display area # Calculate how the original frame is displayed (after crop/zoom)
scale = min(self.window_width / frame_width, available_height / frame_height) 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: if scale < 1.0:
display_width = int(frame_width * scale) final_display_width = int(display_width * scale)
display_height = int(frame_height * scale) final_display_height = int(display_height * scale)
else: else:
display_width = frame_width final_display_width = display_width
display_height = frame_height final_display_height = display_height
scale = 1.0
start_x = (self.window_width - display_width) // 2 start_x = (self.window_width - final_display_width) // 2
start_y = (available_height - display_height) // 2 start_y = (available_height - final_display_height) // 2
# Convert coordinates # Convert screen coordinates to display frame coordinates
video_x = (x - start_x) / scale if scale < 1.0 else (x - start_x) display_x = (x - start_x) / scale
video_y = (y - start_y) / scale if scale < 1.0 else (y - start_y) display_y = (y - start_y) / scale
video_w = w / scale if scale < 1.0 else w display_w = w / scale
video_h = h / scale if scale < 1.0 else h display_h = h / scale
# Clamp to video bounds # Clamp to display frame bounds
video_x = max(0, min(video_x, frame_width)) display_x = max(0, min(display_x, display_width))
video_y = max(0, min(video_y, frame_height)) display_y = max(0, min(display_y, display_height))
video_w = min(video_w, frame_width - video_x) display_w = min(display_w, display_width - display_x)
video_h = min(video_h, frame_height - video_y) 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 # Save current crop for undo
if self.crop_rect: if self.crop_rect:
self.crop_history.append(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): def seek_to_timeline_position(self, mouse_x, bar_x_start, bar_width):
"""Seek to position based on mouse click on timeline""" """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_width = int(self.frame_width * self.zoom_factor)
output_height = int(self.frame_height * 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 # Initialize video writer
fourcc = cv2.VideoWriter_fourcc(*'mp4v') fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_path, fourcc, self.fps, (output_width, output_height)) out = cv2.VideoWriter(output_path, fourcc, self.fps, (output_width, output_height))
@@ -428,12 +472,23 @@ class VideoEditor:
if self.crop_rect: if self.crop_rect:
x, y, w, h = self.crop_rect x, y, w, h = self.crop_rect
x, y, w, h = int(x), int(y), int(w), int(h) 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)) x = max(0, min(x, frame.shape[1] - 1))
y = max(0, min(y, frame.shape[0] - 1)) y = max(0, min(y, frame.shape[0] - 1))
w = min(w, frame.shape[1] - x) w = min(w, frame.shape[1] - x)
h = min(h, frame.shape[0] - y) h = min(h, frame.shape[0] - y)
print(f"DEBUG: Adjusted crop: ({x}, {y}, {w}, {h})")
if w > 0 and h > 0: if w > 0 and h > 0:
frame = frame[y:y+h, x:x+w] 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 # Apply zoom
if self.zoom_factor != 1.0: if self.zoom_factor != 1.0:
@@ -441,7 +496,14 @@ class VideoEditor:
new_width = int(width * self.zoom_factor) new_width = int(width * self.zoom_factor)
new_height = int(height * self.zoom_factor) new_height = int(height * self.zoom_factor)
frame = cv2.resize(frame, (new_width, new_height), interpolation=cv2.INTER_LINEAR) 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) out.write(frame)
# Progress indicator # Progress indicator