diff --git a/croppa/main.py b/croppa/main.py index 70e5594..3eb4954 100644 --- a/croppa/main.py +++ b/croppa/main.py @@ -17,11 +17,7 @@ class VideoEditor: MAX_PLAYBACK_SPEED = 10.0 # Auto-repeat seeking configuration - INITIAL_REPEAT_DELAY = 0.5 # seconds before auto-repeat starts - REPEAT_RATE_SLOW = 0.15 # seconds between seeks when holding key - REPEAT_RATE_FAST = 0.05 # seconds between seeks after holding for a while - FAST_SEEK_THRESHOLD = 2.0 # seconds before switching to fast repeat - DISPLAY_UPDATE_THROTTLE = 0.033 # minimum time between display updates (30 FPS) + AUTO_REPEAT_DISPLAY_RATE = 1.0 # Timeline configuration TIMELINE_HEIGHT = 60 @@ -88,18 +84,12 @@ class VideoEditor: self.window_width = 1200 self.window_height = 800 - # Seeking state - self.key_first_press_time = 0 - self.last_seek_time = 0 - # Auto-repeat seeking state self.auto_repeat_active = False - self.auto_repeat_key = None self.auto_repeat_direction = 0 self.auto_repeat_shift = False self.auto_repeat_ctrl = False self.last_display_update = 0 - self.last_key_activity = 0 # Track when key was last detected # Crop settings self.crop_rect = None # (x, y, width, height) @@ -496,6 +486,7 @@ class VideoEditor: ) self.current_frame = target_frame self.load_current_frame() + self.save_state() def seek_video_with_modifier( @@ -511,79 +502,43 @@ class VideoEditor: self.seek_video(frames) - def start_auto_repeat_seek(self, key: int, direction: int, shift_pressed: bool, ctrl_pressed: bool): - """Start auto-repeat seeking for a held key""" + def start_auto_repeat_seek(self, direction: int, shift_pressed: bool, ctrl_pressed: bool): + """Start auto-repeat seeking""" if self.is_image_mode: return - - current_time = time.time() - - # If the same key is already being auto-repeated, just update the last activity time - if (self.auto_repeat_active and - self.auto_repeat_key == key and - self.auto_repeat_direction == direction and - self.auto_repeat_shift == shift_pressed and - self.auto_repeat_ctrl == ctrl_pressed): - # Update last activity time to keep auto-repeat alive - self.last_seek_time = current_time - return - # Start new auto-repeat self.auto_repeat_active = True - self.auto_repeat_key = key self.auto_repeat_direction = direction self.auto_repeat_shift = shift_pressed self.auto_repeat_ctrl = ctrl_pressed - self.key_first_press_time = current_time - self.last_seek_time = current_time - self.last_key_activity = current_time - # Do initial seek immediately self.seek_video_with_modifier(direction, shift_pressed, ctrl_pressed) def stop_auto_repeat_seek(self): """Stop auto-repeat seeking""" self.auto_repeat_active = False - self.auto_repeat_key = None self.auto_repeat_direction = 0 self.auto_repeat_shift = False self.auto_repeat_ctrl = False def update_auto_repeat_seek(self): - """Update auto-repeat seeking if active""" + """Update auto-repeat seeking""" if not self.auto_repeat_active or self.is_image_mode: return current_time = time.time() - time_since_first_press = current_time - self.key_first_press_time - # Determine repeat rate based on how long the key has been held - if time_since_first_press < self.INITIAL_REPEAT_DELAY: - # Still in initial delay period - return - elif time_since_first_press < self.FAST_SEEK_THRESHOLD: - # Slow repeat rate - repeat_rate = self.REPEAT_RATE_SLOW - else: - # Fast repeat rate - repeat_rate = self.REPEAT_RATE_FAST - - # Check if enough time has passed for the next seek - if current_time - self.last_seek_time >= repeat_rate: + if current_time - self.last_display_update >= self.AUTO_REPEAT_DISPLAY_RATE: self.seek_video_with_modifier( self.auto_repeat_direction, self.auto_repeat_shift, self.auto_repeat_ctrl ) - self.last_seek_time = current_time + self.last_display_update = current_time def should_update_display(self) -> bool: - """Check if display should be updated based on throttling""" - current_time = time.time() - if current_time - self.last_display_update >= self.DISPLAY_UPDATE_THROTTLE: - self.last_display_update = current_time - return True - return False + """Check if display should be updated""" + return True def seek_to_frame(self, frame_number: int): """Seek to specific frame""" @@ -608,11 +563,15 @@ class VideoEditor: # Loop back to start marker new_frame = self.cut_start_frame self.current_frame = new_frame - return self.load_current_frame() + self.load_current_frame() + self.save_state() + return True 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() + self.load_current_frame() + self.save_state() + return True # For sequential playback at normal speed, just read the next frame without seeking if frames_to_advance == 1: @@ -620,6 +579,7 @@ class VideoEditor: if ret: self.current_frame = new_frame self.current_display_frame = frame + self.save_state() return True else: @@ -628,7 +588,9 @@ class VideoEditor: 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 - return self.load_current_frame() + self.load_current_frame() + self.save_state() + return True else: # For speed > 1.0, we need to seek to skip frames self.current_frame = new_frame @@ -641,14 +603,19 @@ class VideoEditor: 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() + self.load_current_frame() + self.save_state() + return True # 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() + self.load_current_frame() + self.save_state() + return True + self.save_state() return success def apply_crop_zoom_and_rotation(self, frame): @@ -1819,18 +1786,9 @@ class VideoEditor: key = cv2.waitKey(delay) & 0xFF - # Handle auto-repeat - stop if a different key is pressed or no key for too long + # Handle auto-repeat - stop if no key is pressed if key == 255 and self.auto_repeat_active: # 255 means no key pressed - # Check if enough time has passed since last key activity (key was released) - if time.time() - self.last_key_activity > 0.1: # 100ms timeout - self.stop_auto_repeat_seek() - elif key != 255 and self.auto_repeat_active: - # A key is pressed, update the last key activity time - self.last_key_activity = time.time() - # If it's a different key, stop auto-repeat - if key != self.auto_repeat_key: - self.stop_auto_repeat_seek() - # If it's the same key, just update the last activity time (don't restart auto-repeat) + self.stop_auto_repeat_seek() # Get modifier key states window_title = "Image Editor" if self.is_image_mode else "Video Editor" @@ -1851,37 +1809,31 @@ class VideoEditor: if not self.is_image_mode: # Check if it's uppercase A (Shift+A) if key == ord("A"): - # Only start auto-repeat if not already active for this key - if not (self.auto_repeat_active and self.auto_repeat_key == key): - self.start_auto_repeat_seek(key, -1, True, False) # Shift+A: -10 frames + if not self.auto_repeat_active: + self.start_auto_repeat_seek(-1, True, False) # Shift+A: -10 frames else: - # Only start auto-repeat if not already active for this key - if not (self.auto_repeat_active and self.auto_repeat_key == key): - self.start_auto_repeat_seek(key, -1, False, False) # A: -1 frame + if not self.auto_repeat_active: + self.start_auto_repeat_seek(-1, False, False) # A: -1 frame elif key == ord("d") or key == ord("D"): # Seeking only for videos if not self.is_image_mode: # Check if it's uppercase D (Shift+D) if key == ord("D"): - # Only start auto-repeat if not already active for this key - if not (self.auto_repeat_active and self.auto_repeat_key == key): - self.start_auto_repeat_seek(key, 1, True, False) # Shift+D: +10 frames + if not self.auto_repeat_active: + self.start_auto_repeat_seek(1, True, False) # Shift+D: +10 frames else: - # Only start auto-repeat if not already active for this key - if not (self.auto_repeat_active and self.auto_repeat_key == key): - self.start_auto_repeat_seek(key, 1, False, False) # D: +1 frame + if not self.auto_repeat_active: + self.start_auto_repeat_seek(1, False, False) # D: +1 frame elif key == 1: # Ctrl+A # Seeking only for videos if not self.is_image_mode: - # Only start auto-repeat if not already active for this key - if not (self.auto_repeat_active and self.auto_repeat_key == key): - self.start_auto_repeat_seek(key, -1, False, True) # Ctrl+A: -60 frames + if not self.auto_repeat_active: + self.start_auto_repeat_seek(-1, False, True) # Ctrl+A: -60 frames elif key == 4: # Ctrl+D # Seeking only for videos if not self.is_image_mode: - # Only start auto-repeat if not already active for this key - if not (self.auto_repeat_active and self.auto_repeat_key == key): - self.start_auto_repeat_seek(key, 1, False, True) # Ctrl+D: +60 frames + if not self.auto_repeat_active: + self.start_auto_repeat_seek(1, False, True) # Ctrl+D: +60 frames elif key == ord("-") or key == ord("_"): self.rotate_clockwise() print(f"Rotated to {self.rotation_angle}°")