diff --git a/croppa/main.py b/croppa/main.py index 187b4c4..aeb40ef 100644 --- a/croppa/main.py +++ b/croppa/main.py @@ -104,6 +104,9 @@ class VideoEditor: # Cut points self.cut_start_frame = None self.cut_end_frame = None + + # Marker looping state + self.looping_between_markers = False # Display offset for panning when zoomed self.display_offset = [0, 0] @@ -320,7 +323,15 @@ class VideoEditor: frames_to_advance = max(1, int(self.playback_speed)) new_frame = self.current_frame + frames_to_advance - if new_frame >= self.total_frames: + + # Handle marker looping bounds + if self.looping_between_markers and self.cut_start_frame is not None and self.cut_end_frame is not None: + if new_frame >= self.cut_end_frame: + # Loop back to start marker + new_frame = self.cut_start_frame + self.current_frame = new_frame + return self.load_current_frame() + elif new_frame >= self.total_frames: new_frame = 0 # Loop - this will require a seek self.current_frame = new_frame return self.load_current_frame() @@ -348,8 +359,18 @@ class VideoEditor: # Hit actual end of video print(f"Reached actual end of video at frame {self.current_frame} (reported: {self.total_frames})") self.total_frames = self.current_frame - self.current_frame = 0 # Loop back to start + if self.looping_between_markers and self.cut_start_frame is not None: + self.current_frame = self.cut_start_frame # Loop back to start marker + else: + self.current_frame = 0 # Loop back to start return self.load_current_frame() + + # Handle marker looping after successful frame load + if self.looping_between_markers and self.cut_start_frame is not None and self.cut_end_frame is not None: + if self.current_frame >= self.cut_end_frame: + self.current_frame = self.cut_start_frame + return self.load_current_frame() + return success def apply_crop_zoom_and_rotation(self, frame): @@ -1017,6 +1038,28 @@ class VideoEditor: else: self.crop_rect = None + def toggle_marker_looping(self): + """Toggle looping between cut markers""" + # Check if both markers are set + if self.cut_start_frame is None or self.cut_end_frame is None: + print("Both markers must be set to enable looping. Use '1' and '2' to set markers.") + return False + + if self.cut_start_frame >= self.cut_end_frame: + print("Invalid marker range - start frame must be before end frame") + return False + + self.looping_between_markers = not self.looping_between_markers + + if self.looping_between_markers: + print(f"Marker looping ENABLED: frames {self.cut_start_frame} - {self.cut_end_frame}") + # Jump to start marker when enabling + self.seek_to_frame(self.cut_start_frame) + else: + print("Marker looping DISABLED") + + return True + def adjust_crop_size(self, direction: str, expand: bool, amount: int = None): @@ -1303,6 +1346,7 @@ class VideoEditor: print(" Ctrl+Scroll: Zoom in/out") print(" 1: Set cut start point") print(" 2: Set cut end point") + print(" T: Toggle loop between markers") if len(self.video_files) > 1: print(" N: Next video") print(" n: Previous video") @@ -1398,6 +1442,8 @@ class VideoEditor: elif key == 13: # Enter output_name = self._get_next_edited_filename(self.video_path) self.render_video(str(self.video_path.parent / output_name)) + elif key == ord("t"): + self.toggle_marker_looping() # Individual direction controls using shift combinations we can detect elif key == ord("J"): # Shift+i - expand up