Compare commits

...

3 Commits

Author SHA1 Message Date
8c45b30bca Enhance VideoEditor with motion path visualization for tracking points
This commit updates the VideoEditor class to visualize the motion path between previous and next tracking points using yellow arrows. The rendering of tracking points has been modified, with previous points displayed as red circles and next points as magenta circles, both with white borders for better visibility. The specification has been updated to reflect these changes in the display of tracking points and their motion direction.
2025-09-17 14:34:26 +02:00
615a3dce0d Implement methods for retrieving previous and next tracking points in VideoEditor
This commit introduces two new methods, _get_previous_tracking_point and _get_next_tracking_point, to the VideoEditor class. These methods allow for the retrieval of tracking points from the previous and next frames that contain tracking data, enhancing navigation capabilities. The rendering logic has been updated to utilize these methods, ensuring that the previous (red) and next (green) tracking points are displayed correctly during video playback. Additionally, the specification has been updated to reflect these changes in the display of tracking points.
2025-09-17 14:29:11 +02:00
1ce05d33ba Add complete reset functionality to VideoEditor for all transformations
This commit introduces a new method, complete_reset, in the VideoEditor class, allowing users to reset all transformations and settings, including crop, zoom, rotation, brightness, contrast, and motion tracking. The method ensures a clean slate for editing by clearing the transformation cache and saving the state. Additionally, the specification has been updated to include this new functionality, enhancing user control over the editing environment.
2025-09-17 14:12:02 +02:00
2 changed files with 133 additions and 19 deletions

View File

@@ -1102,6 +1102,40 @@ class VideoEditor:
print(f"DEBUG: Jump next tracking wrap from {current} -> {tracking_frames[0]}; tracking_frames={tracking_frames}")
self.seek_to_frame(tracking_frames[0])
def _get_previous_tracking_point(self):
"""Get the tracking point from the previous frame that has tracking points."""
if self.is_image_mode or not self.tracking_points:
return None
tracking_frames = sorted(k for k, v in self.tracking_points.items() if v and 0 <= k < self.total_frames)
if not tracking_frames:
return None
# Find the last frame with tracking points that's before current frame
prev_frames = [f for f in tracking_frames if f < self.current_frame]
if not prev_frames:
return None
prev_frame = max(prev_frames)
return prev_frame, self.tracking_points[prev_frame]
def _get_next_tracking_point(self):
"""Get the tracking point from the next frame that has tracking points."""
if self.is_image_mode or not self.tracking_points:
return None
tracking_frames = sorted(k for k, v in self.tracking_points.items() if v and 0 <= k < self.total_frames)
if not tracking_frames:
return None
# Find the first frame with tracking points that's after current frame
next_frames = [f for f in tracking_frames if f > self.current_frame]
if not next_frames:
return None
next_frame = min(next_frames)
return next_frame, self.tracking_points[next_frame]
def advance_frame(self) -> bool:
"""Advance to next frame - handles playback speed and marker looping"""
if not self.is_playing:
@@ -1957,10 +1991,13 @@ class VideoEditor:
motion_text = (
f" | Motion: {self.tracking_enabled}" if self.tracking_enabled else ""
)
autorepeat_text = (
f" | Loop: ON" if self.looping_between_markers else ""
)
if self.is_image_mode:
info_text = f"Image | Zoom: {self.zoom_factor:.1f}x{rotation_text}{brightness_text}{contrast_text}{motion_text}"
else:
info_text = f"Frame: {self.current_frame}/{self.total_frames} | Speed: {self.playback_speed:.1f}x | Zoom: {self.zoom_factor:.1f}x{seek_multiplier_text}{rotation_text}{brightness_text}{contrast_text}{motion_text} | {'Playing' if self.is_playing else 'Paused'}"
info_text = f"Frame: {self.current_frame}/{self.total_frames} | Speed: {self.playback_speed:.1f}x | Zoom: {self.zoom_factor:.1f}x{seek_multiplier_text}{rotation_text}{brightness_text}{contrast_text}{motion_text}{autorepeat_text} | {'Playing' if self.is_playing else 'Paused'}"
cv2.putText(
canvas,
info_text,
@@ -2053,29 +2090,63 @@ class VideoEditor:
cv2.circle(canvas, (sx, sy), 6, (255, 0, 0), -1)
cv2.circle(canvas, (sx, sy), 6, (255, 255, 255), 1)
# Draw previous and next frame tracking points with 50% alpha
# Draw previous and next tracking points with motion path visualization
if not self.is_image_mode and self.tracking_points:
# Previous frame tracking points (red)
prev_frame = self.current_frame - 1
if prev_frame in self.tracking_points:
prev_pts = self.tracking_points[prev_frame]
prev_result = self._get_previous_tracking_point()
next_result = self._get_next_tracking_point()
# Draw motion path if we have both previous and next points
if prev_result and next_result:
prev_frame, prev_pts = prev_result
next_frame, next_pts = next_result
# Draw lines between corresponding tracking points
for i, (prev_rx, prev_ry) in enumerate(prev_pts):
if i < len(next_pts):
next_rx, next_ry = next_pts[i]
prev_sx, prev_sy = self._map_rotated_to_screen(prev_rx, prev_ry)
next_sx, next_sy = self._map_rotated_to_screen(next_rx, next_ry)
# Draw motion path line with arrow (thin and transparent)
overlay = canvas.copy()
cv2.line(overlay, (prev_sx, prev_sy), (next_sx, next_sy), (255, 255, 0), 1) # Thin yellow line
# Draw arrow head pointing from previous to next
angle = np.arctan2(next_sy - prev_sy, next_sx - prev_sx)
arrow_length = 12
arrow_angle = np.pi / 6 # 30 degrees
# Calculate arrow head points
arrow_x1 = int(next_sx - arrow_length * np.cos(angle - arrow_angle))
arrow_y1 = int(next_sy - arrow_length * np.sin(angle - arrow_angle))
arrow_x2 = int(next_sx - arrow_length * np.cos(angle + arrow_angle))
arrow_y2 = int(next_sy - arrow_length * np.sin(angle + arrow_angle))
cv2.line(overlay, (next_sx, next_sy), (arrow_x1, arrow_y1), (255, 255, 0), 1)
cv2.line(overlay, (next_sx, next_sy), (arrow_x2, arrow_y2), (255, 255, 0), 1)
cv2.addWeighted(overlay, 0.3, canvas, 0.7, 0, canvas) # Very transparent
# Previous tracking point (red) - from the most recent frame with tracking points before current
if prev_result:
prev_frame, prev_pts = prev_result
for (rx, ry) in prev_pts:
sx, sy = self._map_rotated_to_screen(rx, ry)
# Create overlay for alpha blending
# Create overlay for alpha blending (more transparent)
overlay = canvas.copy()
cv2.circle(overlay, (sx, sy), 4, (0, 0, 255), -1) # Red circle
cv2.addWeighted(overlay, 0.5, canvas, 0.5, 0, canvas)
cv2.circle(overlay, (sx, sy), 5, (0, 0, 255), -1) # Red circle
cv2.circle(overlay, (sx, sy), 5, (255, 255, 255), 1) # White border
cv2.addWeighted(overlay, 0.4, canvas, 0.6, 0, canvas) # More transparent
# Next frame tracking points (green)
next_frame = self.current_frame + 1
if next_frame in self.tracking_points:
next_pts = self.tracking_points[next_frame]
# Next tracking point (magenta/purple) - from the next frame with tracking points after current
if next_result:
next_frame, next_pts = next_result
for (rx, ry) in next_pts:
sx, sy = self._map_rotated_to_screen(rx, ry)
# Create overlay for alpha blending
# Create overlay for alpha blending (more transparent)
overlay = canvas.copy()
cv2.circle(overlay, (sx, sy), 4, (0, 255, 0), -1) # Green circle
cv2.addWeighted(overlay, 0.5, canvas, 0.5, 0, canvas)
cv2.circle(overlay, (sx, sy), 5, (255, 0, 255), -1) # Magenta circle
cv2.circle(overlay, (sx, sy), 5, (255, 255, 255), 1) # White border
cv2.addWeighted(overlay, 0.4, canvas, 0.6, 0, canvas) # More transparent
if self.tracking_enabled and not self.is_image_mode:
interp = self._get_interpolated_tracking_position(self.current_frame)
if interp:
@@ -2273,6 +2344,44 @@ class VideoEditor:
self.clear_transformation_cache()
self.save_state() # Save state when crop is undone
def complete_reset(self):
"""Complete reset of all transformations and settings"""
# Reset crop
if self.crop_rect:
self.crop_history.append(self.crop_rect)
self.crop_rect = None
# Reset zoom
self.zoom_factor = 1.0
self.zoom_center = None
# Reset rotation
self.rotation_angle = 0
# Reset brightness and contrast
self.brightness = 0
self.contrast = 1.0
# Reset motion tracking
self.tracking_enabled = False
self.tracking_points = {}
# Reset cut markers
self.cut_start_frame = None
self.cut_end_frame = None
self.looping_between_markers = False
# Reset display offset
self.display_offset = [0, 0]
# Clear transformation cache
self.clear_transformation_cache()
# Save state
self.save_state()
print("Complete reset applied - all transformations and markers cleared")
def toggle_marker_looping(self):
"""Toggle looping between cut markers"""
# Check if both markers are set
@@ -2818,7 +2927,8 @@ class VideoEditor:
print(" h/j/k/l: Contract crop (left/down/up/right)")
print(" H/J/K/L: Expand crop (left/down/up/right)")
print(" U: Undo crop")
print(" C: Clear crop")
print(" c: Clear crop")
print(" C: Complete reset (crop, zoom, rotation, brightness, contrast, tracking)")
print()
print("Motion Tracking:")
print(" Right-click: Add/remove tracking point (at current frame)")
@@ -2854,7 +2964,8 @@ class VideoEditor:
print(" h/j/k/l: Contract crop (left/down/up/right)")
print(" H/J/K/L: Expand crop (left/down/up/right)")
print(" U: Undo crop")
print(" C: Clear crop")
print(" c: Clear crop")
print(" C: Complete reset (crop, zoom, rotation, brightness, contrast, tracking)")
print()
print("Other Controls:")
print(" Ctrl+Scroll: Zoom in/out")
@@ -3045,6 +3156,8 @@ class VideoEditor:
self.zoom_factor = 1.0
self.clear_transformation_cache()
self.save_state() # Save state when crop is cleared
elif key == ord("C"):
self.complete_reset()
elif key == ord("1"):
# Cut markers only for videos
if not self.is_image_mode:

View File

@@ -53,6 +53,7 @@ Be careful to save and load settings when navigating this way
- **H/J/K/L**: Contract crop to left/down/up/right edges (15 pixels per keypress)
- **u**: Undo last crop
- **c**: Clear all cropping
- **C**: Complete reset (crop, zoom, rotation, brightness, contrast, tracking points, cut markers)
### Motion Tracking
- **Right-click**: Add tracking point (green circle with white border)
@@ -62,7 +63,7 @@ Be careful to save and load settings when navigating this way
- **Blue cross**: Shows computed tracking position
- **Automatic interpolation**: Tracks between keyframes
- **Crop follows**: Crop area centers on tracked object
- **Display** Points are rendered as blue dots per frame, in addition dots are rendered on each frame for each dot on the previous (in red) and next (in green) frame
- **Display** Points are rendered as blue dots per frame, in addition the previous tracking point (red) and next tracking point (magenta) are shown with yellow arrows indicating motion direction
#### Motion Tracking Navigation
- **,**: Jump to previous tracking marker (previous frame that has one or more tracking points). Wrap-around supported.