diff --git a/croppa/main.py b/croppa/main.py index f7982e5..8ab057d 100644 --- a/croppa/main.py +++ b/croppa/main.py @@ -160,6 +160,7 @@ class VideoEditor: self.mouse_left_down = False self.mouse_right_down = False self.crop_zoom_keyframes = {} # {frame_index: {'crop_rect': (x, y, w, h), 'zoom_factor': float}} + self.crop_zoom_keyframes_enabled = False # Toggle for automatic keyframe creation # Zoom settings self.zoom_factor = 1.0 @@ -317,6 +318,7 @@ class VideoEditor: } for frame, data in self.crop_zoom_keyframes.items() }, + 'crop_zoom_keyframes_enabled': self.crop_zoom_keyframes_enabled, } with open(state_file, 'w') as f: @@ -431,6 +433,11 @@ class VideoEditor: self.crop_zoom_keyframes = loaded_keyframes print(f"Loaded crop/zoom keyframes: {len(self.crop_zoom_keyframes)}") + # Load crop/zoom keyframes enabled flag + if 'crop_zoom_keyframes_enabled' in state: + self.crop_zoom_keyframes_enabled = state['crop_zoom_keyframes_enabled'] + print(f"Loaded crop_zoom_keyframes_enabled: {self.crop_zoom_keyframes_enabled}") + # Load template matching state if 'template_matching_full_frame' in state: self.template_matching_full_frame = state['template_matching_full_frame'] @@ -1305,6 +1312,11 @@ class VideoEditor: base_rect = self._get_base_crop_rect_for_frame(frame_index) return base_rect, self.zoom_factor + # If keyframes are disabled, always use current crop/zoom values + if not self.crop_zoom_keyframes_enabled: + base_rect = self._get_base_crop_rect_for_frame(frame_index) + return base_rect, self.zoom_factor + if not self.crop_zoom_keyframes: base_rect = self._get_base_crop_rect_for_frame(frame_index) return base_rect, self.zoom_factor @@ -1347,6 +1359,8 @@ class VideoEditor: def _update_crop_zoom_keyframe_for_current_frame(self): """Create or update crop/zoom keyframe at current frame from current crop/zoom settings.""" + if not self.crop_zoom_keyframes_enabled: + return frame_index = getattr(self, 'current_frame', 0) base_rect = self._get_base_crop_rect_for_frame(frame_index) self.crop_zoom_keyframes[frame_index] = { @@ -3053,10 +3067,13 @@ class VideoEditor: autorepeat_text = ( f" | Loop: ON" if self.looping_between_markers else "" ) + keyframes_text = ( + f" | Keyframes: ON" if self.crop_zoom_keyframes_enabled else "" + ) if self.is_image_mode: - info_text = f"Image | Zoom: {effective_zoom:.1f}x{rotation_text}{brightness_text}{contrast_text}{motion_text}{feature_text}{template_text}" + info_text = f"Image | Zoom: {effective_zoom:.1f}x{rotation_text}{brightness_text}{contrast_text}{motion_text}{feature_text}{template_text}{keyframes_text}" else: - info_text = f"Frame: {self.current_frame}/{self.total_frames} | Speed: {self.playback_speed:.1f}x | Zoom: {effective_zoom:.1f}x{seek_multiplier_text}{rotation_text}{brightness_text}{contrast_text}{motion_text}{feature_text}{template_text}{autorepeat_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: {effective_zoom:.1f}x{seek_multiplier_text}{rotation_text}{brightness_text}{contrast_text}{motion_text}{feature_text}{template_text}{autorepeat_text}{keyframes_text} | {'Playing' if self.is_playing else 'Paused'}" cv2.putText( canvas, info_text, @@ -3303,7 +3320,7 @@ class VideoEditor: " C - complete reset", "", "Crop/Zoom Keyframes:", - " automatic per frame on crop/zoom change", + " K - toggle keyframes (default: OFF)", " 3 / 4 - prev / next crop/zoom keyframe", " 5 - clone previous keyframe to current frame", " 6 - delete keyframe at current frame", @@ -5256,6 +5273,11 @@ class VideoEditor: self.display_needs_update = True # Crop/zoom keyframe controls (automatic per frame; navigation + clone/delete) + elif key == ord("K"): # Shift+k - toggle keyframes + self.crop_zoom_keyframes_enabled = not self.crop_zoom_keyframes_enabled + status = "ON" if self.crop_zoom_keyframes_enabled else "OFF" + self.show_feedback_message(f"Crop/zoom keyframes {status}") + self.save_state() elif key == ord("3"): self.jump_to_previous_crop_zoom_keyframe() elif key == ord("4"):