diff --git a/croppa/main.py b/croppa/main.py index 424247f..ee5fd04 100644 --- a/croppa/main.py +++ b/croppa/main.py @@ -459,8 +459,8 @@ class ProjectView: class VideoEditor: # Configuration constants BASE_FRAME_DELAY_MS = 1 # ~60 FPS - SPEED_INCREMENT = 0.2 - MIN_PLAYBACK_SPEED = 0.1 + SPEED_INCREMENT = 0.1 + MIN_PLAYBACK_SPEED = 0.05 MAX_PLAYBACK_SPEED = 10.0 # Seek multiplier configuration @@ -983,8 +983,19 @@ class VideoEditor: def calculate_frame_delay(self) -> int: """Calculate frame delay in milliseconds based on playback speed""" - delay_ms = int(self.BASE_FRAME_DELAY_MS / self.playback_speed) - return max(1, delay_ms) + # Round to 2 decimals to handle floating point precision issues + speed = round(self.playback_speed, 2) + print(f"Playback speed: {speed}") + if speed >= 1.0: + # Speed >= 1: maximum FPS (no delay) + return 1 + else: + # Speed < 1: scale FPS based on speed + # At 0.05 speed = 3 FPS, at 1.0 speed = 60 FPS + # Formula: fps = 60 * speed, so delay = 1000 / fps + target_fps = 80 * speed + delay_ms = int(1000 / target_fps) + return max(1, delay_ms) def seek_video(self, frames_delta: int): """Seek video by specified number of frames""" @@ -1166,9 +1177,8 @@ class VideoEditor: if not self.is_playing: return True - # Calculate how many frames to advance based on speed - frames_to_advance = max(1, int(self.playback_speed)) - new_frame = self.current_frame + frames_to_advance + # Always advance by 1 frame - speed is controlled by delay timing + new_frame = self.current_frame + 1 # 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: @@ -3045,10 +3055,15 @@ class VideoEditor: # Use calculated frame delay for proper playback speed delay_ms = self.calculate_frame_delay() else: - # Use minimal delay when not playing for responsive UI - delay_ms = 1 + # Use non-blocking wait for immediate responsiveness when not playing + delay_ms = 0 + # Auto advance frame when playing (videos only) + if self.is_playing and not self.is_image_mode: + self.advance_frame() + # Key capture with appropriate delay + print(f"Delay ms: {delay_ms}") key = cv2.waitKey(delay_ms) & 0xFF # Route keys based on window focus @@ -3302,10 +3317,6 @@ class VideoEditor: print(f"Contracted crop from left by {self.crop_size_step}px") - # Auto advance frame when playing (videos only) - if self.is_playing and not self.is_image_mode: - self.advance_frame() - self.save_state() self.cleanup_render_thread() if hasattr(self, 'cap') and self.cap: