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
|
# 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
|
||||||
|
Reference in New Issue
Block a user