Compare commits
2 Commits
498a1911b1
...
0570256f65
Author | SHA1 | Date | |
---|---|---|---|
0570256f65 | |||
68a1cc3e7d |
116
croppa/main.py
116
croppa/main.py
@@ -510,7 +510,7 @@ class VideoEditor:
|
|||||||
CROP_SIZE_STEP = 15 # pixels to expand/contract crop
|
CROP_SIZE_STEP = 15 # pixels to expand/contract crop
|
||||||
|
|
||||||
# Motion tracking settings
|
# Motion tracking settings
|
||||||
TRACKING_POINT_THRESHOLD = 10 # pixels for delete/snap radius
|
TRACKING_POINT_THRESHOLD = 20 # pixels for delete/snap radius
|
||||||
|
|
||||||
# Seek frame counts
|
# Seek frame counts
|
||||||
SEEK_FRAMES_CTRL = 60 # Ctrl modifier: 60 frames
|
SEEK_FRAMES_CTRL = 60 # Ctrl modifier: 60 frames
|
||||||
@@ -1132,6 +1132,32 @@ class VideoEditor:
|
|||||||
next_frame = min(next_frames)
|
next_frame = min(next_frames)
|
||||||
return next_frame, self.tracking_points[next_frame]
|
return next_frame, self.tracking_points[next_frame]
|
||||||
|
|
||||||
|
def _point_to_line_distance_and_foot(self, px, py, x1, y1, x2, y2):
|
||||||
|
"""Calculate distance from point (px, py) to infinite line (x1, y1) to (x2, y2) and return foot of perpendicular"""
|
||||||
|
# Convert line to general form: Ax + By + C = 0
|
||||||
|
# (y2 - y1)(x - x1) - (x2 - x1)(y - y1) = 0
|
||||||
|
A = y2 - y1
|
||||||
|
B = -(x2 - x1) # Note the negative sign
|
||||||
|
C = -(A * x1 + B * y1)
|
||||||
|
|
||||||
|
# Calculate distance: d = |Ax + By + C| / sqrt(A^2 + B^2)
|
||||||
|
denominator = (A * A + B * B) ** 0.5
|
||||||
|
if denominator == 0:
|
||||||
|
# Line is actually a point
|
||||||
|
distance = ((px - x1) ** 2 + (py - y1) ** 2) ** 0.5
|
||||||
|
return distance, (x1, y1)
|
||||||
|
|
||||||
|
distance = abs(A * px + B * py + C) / denominator
|
||||||
|
|
||||||
|
# Calculate foot of perpendicular: (xf, yf)
|
||||||
|
# xf = xu - A(Axu + Byu + C)/(A^2 + B^2)
|
||||||
|
# yf = yu - B(Axu + Byu + C)/(A^2 + B^2)
|
||||||
|
numerator = A * px + B * py + C
|
||||||
|
xf = px - A * numerator / (A * A + B * B)
|
||||||
|
yf = py - B * numerator / (A * A + B * B)
|
||||||
|
|
||||||
|
return distance, (xf, yf)
|
||||||
|
|
||||||
def advance_frame(self) -> bool:
|
def advance_frame(self) -> bool:
|
||||||
"""Advance to next frame - handles playback speed and marker looping"""
|
"""Advance to next frame - handles playback speed and marker looping"""
|
||||||
if not self.is_playing:
|
if not self.is_playing:
|
||||||
@@ -2155,23 +2181,91 @@ class VideoEditor:
|
|||||||
removed = True
|
removed = True
|
||||||
break
|
break
|
||||||
|
|
||||||
# If not removed, check for snapping to nearby points from other frames
|
# If not removed, check for snapping to nearby points or lines from other frames
|
||||||
if not removed:
|
if not removed:
|
||||||
snapped = False
|
snapped = False
|
||||||
# Check all tracking points from all frames for snapping
|
best_snap_distance = float('inf')
|
||||||
for _, points in self.tracking_points.items():
|
best_snap_point = None
|
||||||
|
|
||||||
|
# Check all tracking points from all frames for point snapping
|
||||||
|
for frame_num, 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:
|
distance = ((sxp - x) ** 2 + (syp - y) ** 2) ** 0.5
|
||||||
# Snap to this existing point
|
if distance <= threshold and distance < best_snap_distance:
|
||||||
self.tracking_points.setdefault(self.current_frame, []).append((int(px), int(py)))
|
best_snap_distance = distance
|
||||||
snapped = True
|
best_snap_point = (int(px), int(py))
|
||||||
break
|
|
||||||
if snapped:
|
# Check for line snapping between previous and next tracking points (the actual cyan arrows)
|
||||||
break
|
prev_result = self._get_previous_tracking_point()
|
||||||
|
next_result = self._get_next_tracking_point()
|
||||||
|
|
||||||
|
print(f"DEBUG: Line snapping - prev_result: {prev_result}, next_result: {next_result}")
|
||||||
|
|
||||||
|
if prev_result and next_result:
|
||||||
|
prev_frame, prev_pts = prev_result
|
||||||
|
next_frame, next_pts = next_result
|
||||||
|
|
||||||
|
print(f"DEBUG: Checking line between prev frame {prev_frame} and next frame {next_frame}")
|
||||||
|
|
||||||
|
# Check each corresponding pair of points between previous and next
|
||||||
|
for j in range(min(len(prev_pts), len(next_pts))):
|
||||||
|
px1, py1 = prev_pts[j]
|
||||||
|
px2, py2 = next_pts[j]
|
||||||
|
|
||||||
|
# Convert to screen coordinates
|
||||||
|
sx1, sy1 = self._map_rotated_to_screen(px1, py1)
|
||||||
|
sx2, sy2 = self._map_rotated_to_screen(px2, py2)
|
||||||
|
|
||||||
|
# Calculate distance to infinite line and foot of perpendicular
|
||||||
|
line_distance, (foot_x, foot_y) = self._point_to_line_distance_and_foot(x, y, sx1, sy1, sx2, sy2)
|
||||||
|
|
||||||
|
print(f"DEBUG: Line {j}: ({sx1},{sy1}) to ({sx2},{sy2}), distance to click ({x},{y}) = {line_distance:.2f}, foot = ({foot_x:.1f}, {foot_y:.1f})")
|
||||||
|
|
||||||
|
if line_distance <= threshold and line_distance < best_snap_distance:
|
||||||
|
print(f"DEBUG: Line snap found! Distance {line_distance:.2f} <= threshold {threshold}")
|
||||||
|
|
||||||
|
# Clamp the foot to the line segment
|
||||||
|
# Calculate parameter t for the foot point
|
||||||
|
line_dx = sx2 - sx1
|
||||||
|
line_dy = sy2 - sy1
|
||||||
|
line_length_sq = line_dx * line_dx + line_dy * line_dy
|
||||||
|
|
||||||
|
if line_length_sq > 0:
|
||||||
|
t = ((foot_x - sx1) * line_dx + (foot_y - sy1) * line_dy) / line_length_sq
|
||||||
|
t = max(0, min(1, t)) # Clamp to [0, 1]
|
||||||
|
|
||||||
|
# Calculate clamped foot point
|
||||||
|
clamped_foot_x = sx1 + t * line_dx
|
||||||
|
clamped_foot_y = sy1 + t * line_dy
|
||||||
|
|
||||||
|
print(f"DEBUG: Clamped foot from ({foot_x:.1f}, {foot_y:.1f}) to ({clamped_foot_x:.1f}, {clamped_foot_y:.1f})")
|
||||||
|
|
||||||
|
# Convert clamped foot back to rotated coordinates
|
||||||
|
closest_rx, closest_ry = self._map_screen_to_rotated(int(clamped_foot_x), int(clamped_foot_y))
|
||||||
|
|
||||||
|
best_snap_distance = line_distance
|
||||||
|
best_snap_point = (int(closest_rx), int(closest_ry))
|
||||||
|
print(f"DEBUG: Best line snap point: ({closest_rx}, {closest_ry})")
|
||||||
|
else:
|
||||||
|
print(f"DEBUG: No prev/next points found for line snapping")
|
||||||
|
|
||||||
|
# Apply the best snap if found
|
||||||
|
if best_snap_point:
|
||||||
|
print(f"DEBUG: Final best_snap_point: {best_snap_point} (distance: {best_snap_distance:.2f})")
|
||||||
|
self.tracking_points.setdefault(self.current_frame, []).append(best_snap_point)
|
||||||
|
snapped = True
|
||||||
|
else:
|
||||||
|
print(f"DEBUG: No snap found, adding new point at: ({rx}, {ry})")
|
||||||
|
|
||||||
# If no snapping, add new point at clicked location
|
# If no snapping, add new point at clicked location
|
||||||
if not snapped:
|
if not snapped:
|
||||||
|
print(f"DEBUG: No snap found, adding new point at: ({rx}, {ry})")
|
||||||
|
print(f"DEBUG: Click was at screen coords: ({x}, {y})")
|
||||||
|
print(f"DEBUG: Converted to rotated coords: ({rx}, {ry})")
|
||||||
|
# Verify the conversion
|
||||||
|
verify_sx, verify_sy = self._map_rotated_to_screen(rx, ry)
|
||||||
|
print(f"DEBUG: Verification - rotated ({rx}, {ry}) -> screen ({verify_sx}, {verify_sy})")
|
||||||
self.tracking_points.setdefault(self.current_frame, []).append((int(rx), int(ry)))
|
self.tracking_points.setdefault(self.current_frame, []).append((int(rx), int(ry)))
|
||||||
# self.show_feedback_message("Tracking point added")
|
# self.show_feedback_message("Tracking point added")
|
||||||
|
|
||||||
|
@@ -57,8 +57,9 @@ Be careful to save and load settings when navigating this way
|
|||||||
|
|
||||||
### Motion Tracking
|
### Motion Tracking
|
||||||
- **Right-click**: Add tracking point (green circle with white border)
|
- **Right-click**: Add tracking point (green circle with white border)
|
||||||
- **Right-click existing point**: Remove tracking point (within 50px)
|
- **Right-click existing point**: Remove tracking point (within 10px)
|
||||||
- **Right-click near existing point**: Snap to existing point from any frame (within 50px radius)
|
- **Right-click near existing point**: Snap to existing point from any frame (within 10px radius)
|
||||||
|
- **Right-click near motion path**: Snap to closest point on yellow arrow line between tracking points (within 10px radius)
|
||||||
- **v**: Toggle motion tracking on/off
|
- **v**: Toggle motion tracking on/off
|
||||||
- **V**: Clear all tracking points
|
- **V**: Clear all tracking points
|
||||||
- **Blue cross**: Shows computed tracking position
|
- **Blue cross**: Shows computed tracking position
|
||||||
|
Reference in New Issue
Block a user