Compare commits
2 Commits
84a0748f0b
...
498a1911b1
Author | SHA1 | Date | |
---|---|---|---|
498a1911b1 | |||
d068da20f4 |
114
croppa/main.py
114
croppa/main.py
@@ -509,6 +509,14 @@ class VideoEditor:
|
|||||||
# Crop adjustment settings
|
# Crop adjustment settings
|
||||||
CROP_SIZE_STEP = 15 # pixels to expand/contract crop
|
CROP_SIZE_STEP = 15 # pixels to expand/contract crop
|
||||||
|
|
||||||
|
# Motion tracking settings
|
||||||
|
TRACKING_POINT_THRESHOLD = 10 # pixels for delete/snap radius
|
||||||
|
|
||||||
|
# Seek frame counts
|
||||||
|
SEEK_FRAMES_CTRL = 60 # Ctrl modifier: 60 frames
|
||||||
|
SEEK_FRAMES_SHIFT = 10 # Shift modifier: 10 frames
|
||||||
|
SEEK_FRAMES_DEFAULT = 1 # Default: 1 frame
|
||||||
|
|
||||||
def __init__(self, path: str):
|
def __init__(self, path: str):
|
||||||
self.path = Path(path)
|
self.path = Path(path)
|
||||||
|
|
||||||
@@ -996,11 +1004,11 @@ class VideoEditor:
|
|||||||
):
|
):
|
||||||
"""Seek video with different frame counts based on modifiers and seek multiplier"""
|
"""Seek video with different frame counts based on modifiers and seek multiplier"""
|
||||||
if ctrl_pressed:
|
if ctrl_pressed:
|
||||||
base_frames = 60 # Ctrl: 60 frames
|
base_frames = self.SEEK_FRAMES_CTRL
|
||||||
elif shift_pressed:
|
elif shift_pressed:
|
||||||
base_frames = 10 # Shift: 10 frames
|
base_frames = self.SEEK_FRAMES_SHIFT
|
||||||
else:
|
else:
|
||||||
base_frames = 1 # Default: 1 frame
|
base_frames = self.SEEK_FRAMES_DEFAULT
|
||||||
|
|
||||||
# Apply seek multiplier to the base frame count
|
# Apply seek multiplier to the base frame count
|
||||||
frames = direction * int(base_frames * self.seek_multiplier)
|
frames = direction * int(base_frames * self.seek_multiplier)
|
||||||
@@ -1057,18 +1065,6 @@ class VideoEditor:
|
|||||||
self.current_frame = max(0, min(frame_number, self.total_frames - 1))
|
self.current_frame = max(0, min(frame_number, self.total_frames - 1))
|
||||||
self.load_current_frame()
|
self.load_current_frame()
|
||||||
|
|
||||||
def _get_sorted_markers(self):
|
|
||||||
"""Return sorted unique marker list [cut_start_frame, cut_end_frame] as ints within bounds."""
|
|
||||||
markers = []
|
|
||||||
for m in (self.cut_start_frame, self.cut_end_frame):
|
|
||||||
if isinstance(m, int):
|
|
||||||
markers.append(m)
|
|
||||||
if not markers:
|
|
||||||
return []
|
|
||||||
# Clamp and dedupe
|
|
||||||
clamped = set(max(0, min(m, self.total_frames - 1)) for m in markers)
|
|
||||||
return sorted(clamped)
|
|
||||||
|
|
||||||
def jump_to_previous_marker(self):
|
def jump_to_previous_marker(self):
|
||||||
"""Jump to the previous tracking marker (frame with tracking points)."""
|
"""Jump to the previous tracking marker (frame with tracking points)."""
|
||||||
if self.is_image_mode:
|
if self.is_image_mode:
|
||||||
@@ -1316,90 +1312,6 @@ class VideoEditor:
|
|||||||
'available_h': available_height
|
'available_h': available_height
|
||||||
}
|
}
|
||||||
|
|
||||||
def _map_original_to_screen(self, ox, oy):
|
|
||||||
"""Map a point in original frame coords to canvas screen coords."""
|
|
||||||
frame_number = getattr(self, 'current_frame', 0)
|
|
||||||
# Since crop is applied after rotation, mapping to rotated space uses only rotation
|
|
||||||
angle = self.rotation_angle
|
|
||||||
if angle == 90:
|
|
||||||
rx, ry = oy, self.frame_width - 1 - ox
|
|
||||||
elif angle == 180:
|
|
||||||
rx, ry = self.frame_width - 1 - ox, self.frame_height - 1 - oy
|
|
||||||
elif angle == 270:
|
|
||||||
rx, ry = self.frame_height - 1 - oy, ox
|
|
||||||
else:
|
|
||||||
rx, ry = ox, oy
|
|
||||||
# Now account for crop/zoom/offset using unified params
|
|
||||||
params = self._get_display_params()
|
|
||||||
rx -= params['eff_x']
|
|
||||||
ry -= params['eff_y']
|
|
||||||
zx = rx * self.zoom_factor
|
|
||||||
zy = ry * self.zoom_factor
|
|
||||||
inframe_x = zx - params['offx']
|
|
||||||
inframe_y = zy - params['offy']
|
|
||||||
sx = int(round(params['start_x'] + inframe_x * params['scale']))
|
|
||||||
sy = int(round(params['start_y'] + inframe_y * params['scale']))
|
|
||||||
return sx, sy
|
|
||||||
|
|
||||||
def _map_screen_to_original(self, sx, sy):
|
|
||||||
"""Map a point on canvas screen coords back to original frame coords."""
|
|
||||||
frame_number = getattr(self, 'current_frame', 0)
|
|
||||||
angle = self.rotation_angle
|
|
||||||
ch, cw = self.frame_height, self.frame_width
|
|
||||||
# Zoomed dimensions
|
|
||||||
if angle in (90, 270):
|
|
||||||
rotated_w, rotated_h = ch, cw
|
|
||||||
else:
|
|
||||||
rotated_w, rotated_h = cw, ch
|
|
||||||
new_w = int(rotated_w * self.zoom_factor)
|
|
||||||
new_h = int(rotated_h * self.zoom_factor)
|
|
||||||
# Whether apply_crop_zoom_and_rotation cropped due to zoom
|
|
||||||
cropped_due_to_zoom = (self.zoom_factor != 1.0) and (new_w > self.window_width or new_h > self.window_height)
|
|
||||||
# Visible dims before canvas scaling
|
|
||||||
visible_w = new_w if not cropped_due_to_zoom else min(new_w, self.window_width)
|
|
||||||
visible_h = new_h if not cropped_due_to_zoom else min(new_h, self.window_height)
|
|
||||||
# Canvas scale and placement
|
|
||||||
available_height = self.window_height - (0 if self.is_image_mode else self.TIMELINE_HEIGHT)
|
|
||||||
scale_raw = min(self.window_width / max(1, visible_w), available_height / max(1, visible_h))
|
|
||||||
scale_canvas = scale_raw if scale_raw < 1.0 else 1.0
|
|
||||||
final_w = int(visible_w * scale_canvas)
|
|
||||||
final_h = int(visible_h * scale_canvas)
|
|
||||||
start_x_canvas = (self.window_width - final_w) // 2
|
|
||||||
start_y_canvas = (available_height - final_h) // 2
|
|
||||||
# Back to processed (zoomed+cropped) space
|
|
||||||
zx = (sx - start_x_canvas) / max(1e-6, scale_canvas)
|
|
||||||
zy = (sy - start_y_canvas) / max(1e-6, scale_canvas)
|
|
||||||
# Add display offset in zoomed space (only if cropped_due_to_zoom)
|
|
||||||
if cropped_due_to_zoom:
|
|
||||||
offx_max = max(0, new_w - self.window_width)
|
|
||||||
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))
|
|
||||||
else:
|
|
||||||
offx = 0
|
|
||||||
offy = 0
|
|
||||||
zx += offx
|
|
||||||
zy += offy
|
|
||||||
# Reverse zoom
|
|
||||||
rx = zx / max(1e-6, self.zoom_factor)
|
|
||||||
ry = zy / max(1e-6, self.zoom_factor)
|
|
||||||
# Reverse crop in rotated space to get rotated coordinates
|
|
||||||
cx, cy, cw, ch = self._get_effective_crop_rect_for_frame(frame_number)
|
|
||||||
rx = rx + cx
|
|
||||||
ry = ry + cy
|
|
||||||
# Reverse rotation to original frame coords
|
|
||||||
if angle == 90:
|
|
||||||
ox, oy = self.frame_width - 1 - ry, rx
|
|
||||||
elif angle == 180:
|
|
||||||
ox, oy = self.frame_width - 1 - rx, self.frame_height - 1 - ry
|
|
||||||
elif angle == 270:
|
|
||||||
ox, oy = ry, self.frame_height - 1 - rx
|
|
||||||
else:
|
|
||||||
ox, oy = rx, ry
|
|
||||||
ox = max(0, min(int(round(ox)), self.frame_width - 1))
|
|
||||||
oy = max(0, min(int(round(oy)), self.frame_height - 1))
|
|
||||||
return ox, oy
|
|
||||||
|
|
||||||
def _map_rotated_to_screen(self, rx, ry):
|
def _map_rotated_to_screen(self, rx, ry):
|
||||||
"""Map a point in ROTATED frame coords to canvas screen coords (post-crop)."""
|
"""Map a point in ROTATED frame coords to canvas screen coords (post-crop)."""
|
||||||
# Subtract crop offset in rotated space (EFFECTIVE crop at current frame)
|
# Subtract crop offset in rotated space (EFFECTIVE crop at current frame)
|
||||||
@@ -2225,7 +2137,7 @@ class VideoEditor:
|
|||||||
if not self.is_image_mode:
|
if not self.is_image_mode:
|
||||||
# Store tracking points in ROTATED frame coordinates (pre-crop)
|
# Store tracking points in ROTATED frame coordinates (pre-crop)
|
||||||
rx, ry = self._map_screen_to_rotated(x, y)
|
rx, ry = self._map_screen_to_rotated(x, y)
|
||||||
threshold = 50
|
threshold = self.TRACKING_POINT_THRESHOLD
|
||||||
removed = False
|
removed = False
|
||||||
|
|
||||||
# First check for removal of existing points on current frame
|
# First check for removal of existing points on current frame
|
||||||
@@ -2247,7 +2159,7 @@ class VideoEditor:
|
|||||||
if not removed:
|
if not removed:
|
||||||
snapped = False
|
snapped = False
|
||||||
# Check all tracking points from all frames for snapping
|
# Check all tracking points from all frames for snapping
|
||||||
for frame_num, points in self.tracking_points.items():
|
for _, points in self.tracking_points.items():
|
||||||
for (px, py) in points:
|
for (px, py) in points:
|
||||||
sxp, syp = self._map_rotated_to_screen(px, py)
|
sxp, syp = self._map_rotated_to_screen(px, py)
|
||||||
if (sxp - x) ** 2 + (syp - y) ** 2 <= threshold ** 2:
|
if (sxp - x) ** 2 + (syp - y) ** 2 <= threshold ** 2:
|
||||||
|
Reference in New Issue
Block a user