diff --git a/croppa/main.py b/croppa/main.py index 889d144..47a01f8 100644 --- a/croppa/main.py +++ b/croppa/main.py @@ -1057,38 +1057,50 @@ class VideoEditor: self.current_frame = max(0, min(frame_number, self.total_frames - 1)) 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): - """Jump to the previous defined marker (cut_start_frame/cut_end_frame).""" + """Jump to the previous tracking marker (frame with tracking points).""" if self.is_image_mode: return - markers = [m for m in [self.cut_start_frame, self.cut_end_frame] if isinstance(m, int)] - if not markers: + self.stop_auto_repeat_seek() + tracking_frames = sorted(k for k, v in self.tracking_points.items() if v) + if not tracking_frames: + print("DEBUG: No tracking markers; prev jump ignored") return - markers = sorted(set(max(0, min(m, self.total_frames - 1)) for m in markers)) - # Find previous marker relative to current_frame (wrap if needed) - prev = None - for m in markers: - if m < self.current_frame: - prev = m - if prev is None: - prev = markers[-1] - self.seek_to_frame(prev) + current = self.current_frame + candidates = [f for f in tracking_frames if f < current] + target = candidates[-1] if candidates else tracking_frames[-1] + print(f"DEBUG: Jump prev tracking from {current} -> {target}; tracking_frames={tracking_frames}") + self.seek_to_frame(target) def jump_to_next_marker(self): - """Jump to the next defined marker (cut_start_frame/cut_end_frame).""" + """Jump to the next tracking marker (frame with tracking points).""" if self.is_image_mode: return - markers = [m for m in [self.cut_start_frame, self.cut_end_frame] if isinstance(m, int)] - if not markers: + self.stop_auto_repeat_seek() + tracking_frames = sorted(k for k, v in self.tracking_points.items() if v) + if not tracking_frames: + print("DEBUG: No tracking markers; next jump ignored") return - markers = sorted(set(max(0, min(m, self.total_frames - 1)) for m in markers)) - # Find next marker greater than current_frame (wrap if needed) - for m in markers: - if m > self.current_frame: - self.seek_to_frame(m) + current = self.current_frame + for f in tracking_frames: + if f > current: + print(f"DEBUG: Jump next tracking from {current} -> {f}; tracking_frames={tracking_frames}") + self.seek_to_frame(f) return - # Wrap to first - self.seek_to_frame(markers[0]) + print(f"DEBUG: Jump next tracking wrap from {current} -> {tracking_frames[0]}; tracking_frames={tracking_frames}") + self.seek_to_frame(tracking_frames[0]) def advance_frame(self) -> bool: """Advance to next frame - handles playback speed and marker looping""" diff --git a/croppa/spec.md b/croppa/spec.md index 2ebff37..947ab5a 100644 --- a/croppa/spec.md +++ b/croppa/spec.md @@ -57,6 +57,10 @@ That coordinate is to be mapped to the bottom left corner of the original raw un - **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 +#### Motion Tracking Navigation +- **,**: Jump to previous tracking marker (previous frame that has one or more tracking points). Wrap-around supported. +- **.**: Jump to next tracking marker (next frame that has one or more tracking points). Wrap-around supported. + ### Markers and Looping - **1**: Set cut start marker at current frame - **2**: Set cut end marker at current frame