refactor(main.py): simplify and consolidate auto-repeat seeking logic for cleaner code
This commit is contained in:
128
croppa/main.py
128
croppa/main.py
@@ -17,11 +17,7 @@ class VideoEditor:
|
|||||||
MAX_PLAYBACK_SPEED = 10.0
|
MAX_PLAYBACK_SPEED = 10.0
|
||||||
|
|
||||||
# Auto-repeat seeking configuration
|
# Auto-repeat seeking configuration
|
||||||
INITIAL_REPEAT_DELAY = 0.5 # seconds before auto-repeat starts
|
AUTO_REPEAT_DISPLAY_RATE = 1.0
|
||||||
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)
|
|
||||||
|
|
||||||
# Timeline configuration
|
# Timeline configuration
|
||||||
TIMELINE_HEIGHT = 60
|
TIMELINE_HEIGHT = 60
|
||||||
@@ -88,18 +84,12 @@ class VideoEditor:
|
|||||||
self.window_width = 1200
|
self.window_width = 1200
|
||||||
self.window_height = 800
|
self.window_height = 800
|
||||||
|
|
||||||
# Seeking state
|
|
||||||
self.key_first_press_time = 0
|
|
||||||
self.last_seek_time = 0
|
|
||||||
|
|
||||||
# Auto-repeat seeking state
|
# Auto-repeat seeking state
|
||||||
self.auto_repeat_active = False
|
self.auto_repeat_active = False
|
||||||
self.auto_repeat_key = None
|
|
||||||
self.auto_repeat_direction = 0
|
self.auto_repeat_direction = 0
|
||||||
self.auto_repeat_shift = False
|
self.auto_repeat_shift = False
|
||||||
self.auto_repeat_ctrl = False
|
self.auto_repeat_ctrl = False
|
||||||
self.last_display_update = 0
|
self.last_display_update = 0
|
||||||
self.last_key_activity = 0 # Track when key was last detected
|
|
||||||
|
|
||||||
# Crop settings
|
# Crop settings
|
||||||
self.crop_rect = None # (x, y, width, height)
|
self.crop_rect = None # (x, y, width, height)
|
||||||
@@ -496,6 +486,7 @@ class VideoEditor:
|
|||||||
)
|
)
|
||||||
self.current_frame = target_frame
|
self.current_frame = target_frame
|
||||||
self.load_current_frame()
|
self.load_current_frame()
|
||||||
|
self.save_state()
|
||||||
|
|
||||||
|
|
||||||
def seek_video_with_modifier(
|
def seek_video_with_modifier(
|
||||||
@@ -511,79 +502,43 @@ class VideoEditor:
|
|||||||
|
|
||||||
self.seek_video(frames)
|
self.seek_video(frames)
|
||||||
|
|
||||||
def start_auto_repeat_seek(self, key: int, direction: int, shift_pressed: bool, ctrl_pressed: bool):
|
def start_auto_repeat_seek(self, direction: int, shift_pressed: bool, ctrl_pressed: bool):
|
||||||
"""Start auto-repeat seeking for a held key"""
|
"""Start auto-repeat seeking"""
|
||||||
if self.is_image_mode:
|
if self.is_image_mode:
|
||||||
return
|
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_active = True
|
||||||
self.auto_repeat_key = key
|
|
||||||
self.auto_repeat_direction = direction
|
self.auto_repeat_direction = direction
|
||||||
self.auto_repeat_shift = shift_pressed
|
self.auto_repeat_shift = shift_pressed
|
||||||
self.auto_repeat_ctrl = ctrl_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)
|
self.seek_video_with_modifier(direction, shift_pressed, ctrl_pressed)
|
||||||
|
|
||||||
def stop_auto_repeat_seek(self):
|
def stop_auto_repeat_seek(self):
|
||||||
"""Stop auto-repeat seeking"""
|
"""Stop auto-repeat seeking"""
|
||||||
self.auto_repeat_active = False
|
self.auto_repeat_active = False
|
||||||
self.auto_repeat_key = None
|
|
||||||
self.auto_repeat_direction = 0
|
self.auto_repeat_direction = 0
|
||||||
self.auto_repeat_shift = False
|
self.auto_repeat_shift = False
|
||||||
self.auto_repeat_ctrl = False
|
self.auto_repeat_ctrl = False
|
||||||
|
|
||||||
def update_auto_repeat_seek(self):
|
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:
|
if not self.auto_repeat_active or self.is_image_mode:
|
||||||
return
|
return
|
||||||
|
|
||||||
current_time = time.time()
|
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 current_time - self.last_display_update >= self.AUTO_REPEAT_DISPLAY_RATE:
|
||||||
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:
|
|
||||||
self.seek_video_with_modifier(
|
self.seek_video_with_modifier(
|
||||||
self.auto_repeat_direction,
|
self.auto_repeat_direction,
|
||||||
self.auto_repeat_shift,
|
self.auto_repeat_shift,
|
||||||
self.auto_repeat_ctrl
|
self.auto_repeat_ctrl
|
||||||
)
|
)
|
||||||
self.last_seek_time = current_time
|
self.last_display_update = current_time
|
||||||
|
|
||||||
def should_update_display(self) -> bool:
|
def should_update_display(self) -> bool:
|
||||||
"""Check if display should be updated based on throttling"""
|
"""Check if display should be updated"""
|
||||||
current_time = time.time()
|
return True
|
||||||
if current_time - self.last_display_update >= self.DISPLAY_UPDATE_THROTTLE:
|
|
||||||
self.last_display_update = current_time
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def seek_to_frame(self, frame_number: int):
|
def seek_to_frame(self, frame_number: int):
|
||||||
"""Seek to specific frame"""
|
"""Seek to specific frame"""
|
||||||
@@ -608,11 +563,15 @@ class VideoEditor:
|
|||||||
# Loop back to start marker
|
# Loop back to start marker
|
||||||
new_frame = self.cut_start_frame
|
new_frame = self.cut_start_frame
|
||||||
self.current_frame = new_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:
|
elif new_frame >= self.total_frames:
|
||||||
new_frame = 0 # Loop - this will require a seek
|
new_frame = 0 # Loop - this will require a seek
|
||||||
self.current_frame = new_frame
|
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
|
# For sequential playback at normal speed, just read the next frame without seeking
|
||||||
if frames_to_advance == 1:
|
if frames_to_advance == 1:
|
||||||
@@ -620,6 +579,7 @@ class VideoEditor:
|
|||||||
if ret:
|
if ret:
|
||||||
self.current_frame = new_frame
|
self.current_frame = new_frame
|
||||||
self.current_display_frame = frame
|
self.current_display_frame = frame
|
||||||
|
self.save_state()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -628,7 +588,9 @@ class VideoEditor:
|
|||||||
print(f"Reached actual end of video at frame {self.current_frame} (reported: {self.total_frames})")
|
print(f"Reached actual end of video at frame {self.current_frame} (reported: {self.total_frames})")
|
||||||
self.total_frames = self.current_frame
|
self.total_frames = self.current_frame
|
||||||
self.current_frame = 0 # Loop back to start
|
self.current_frame = 0 # Loop back to start
|
||||||
return self.load_current_frame()
|
self.load_current_frame()
|
||||||
|
self.save_state()
|
||||||
|
return True
|
||||||
else:
|
else:
|
||||||
# For speed > 1.0, we need to seek to skip frames
|
# For speed > 1.0, we need to seek to skip frames
|
||||||
self.current_frame = new_frame
|
self.current_frame = new_frame
|
||||||
@@ -641,14 +603,19 @@ class VideoEditor:
|
|||||||
self.current_frame = self.cut_start_frame # Loop back to start marker
|
self.current_frame = self.cut_start_frame # Loop back to start marker
|
||||||
else:
|
else:
|
||||||
self.current_frame = 0 # Loop back to start
|
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
|
# 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.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:
|
if self.current_frame >= self.cut_end_frame:
|
||||||
self.current_frame = self.cut_start_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
|
return success
|
||||||
|
|
||||||
def apply_crop_zoom_and_rotation(self, frame):
|
def apply_crop_zoom_and_rotation(self, frame):
|
||||||
@@ -1819,18 +1786,9 @@ class VideoEditor:
|
|||||||
key = cv2.waitKey(delay) & 0xFF
|
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
|
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)
|
self.stop_auto_repeat_seek()
|
||||||
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)
|
|
||||||
|
|
||||||
# Get modifier key states
|
# Get modifier key states
|
||||||
window_title = "Image Editor" if self.is_image_mode else "Video Editor"
|
window_title = "Image Editor" if self.is_image_mode else "Video Editor"
|
||||||
@@ -1851,37 +1809,31 @@ class VideoEditor:
|
|||||||
if not self.is_image_mode:
|
if not self.is_image_mode:
|
||||||
# Check if it's uppercase A (Shift+A)
|
# Check if it's uppercase A (Shift+A)
|
||||||
if key == ord("A"):
|
if key == ord("A"):
|
||||||
# Only start auto-repeat if not already active for this key
|
if not self.auto_repeat_active:
|
||||||
if not (self.auto_repeat_active and self.auto_repeat_key == key):
|
self.start_auto_repeat_seek(-1, True, False) # Shift+A: -10 frames
|
||||||
self.start_auto_repeat_seek(key, -1, True, False) # Shift+A: -10 frames
|
|
||||||
else:
|
else:
|
||||||
# Only start auto-repeat if not already active for this key
|
if not self.auto_repeat_active:
|
||||||
if not (self.auto_repeat_active and self.auto_repeat_key == key):
|
self.start_auto_repeat_seek(-1, False, False) # A: -1 frame
|
||||||
self.start_auto_repeat_seek(key, -1, False, False) # A: -1 frame
|
|
||||||
elif key == ord("d") or key == ord("D"):
|
elif key == ord("d") or key == ord("D"):
|
||||||
# Seeking only for videos
|
# Seeking only for videos
|
||||||
if not self.is_image_mode:
|
if not self.is_image_mode:
|
||||||
# Check if it's uppercase D (Shift+D)
|
# Check if it's uppercase D (Shift+D)
|
||||||
if key == ord("D"):
|
if key == ord("D"):
|
||||||
# Only start auto-repeat if not already active for this key
|
if not self.auto_repeat_active:
|
||||||
if not (self.auto_repeat_active and self.auto_repeat_key == key):
|
self.start_auto_repeat_seek(1, True, False) # Shift+D: +10 frames
|
||||||
self.start_auto_repeat_seek(key, 1, True, False) # Shift+D: +10 frames
|
|
||||||
else:
|
else:
|
||||||
# Only start auto-repeat if not already active for this key
|
if not self.auto_repeat_active:
|
||||||
if not (self.auto_repeat_active and self.auto_repeat_key == key):
|
self.start_auto_repeat_seek(1, False, False) # D: +1 frame
|
||||||
self.start_auto_repeat_seek(key, 1, False, False) # D: +1 frame
|
|
||||||
elif key == 1: # Ctrl+A
|
elif key == 1: # Ctrl+A
|
||||||
# Seeking only for videos
|
# Seeking only for videos
|
||||||
if not self.is_image_mode:
|
if not self.is_image_mode:
|
||||||
# Only start auto-repeat if not already active for this key
|
if not self.auto_repeat_active:
|
||||||
if not (self.auto_repeat_active and self.auto_repeat_key == key):
|
self.start_auto_repeat_seek(-1, False, True) # Ctrl+A: -60 frames
|
||||||
self.start_auto_repeat_seek(key, -1, False, True) # Ctrl+A: -60 frames
|
|
||||||
elif key == 4: # Ctrl+D
|
elif key == 4: # Ctrl+D
|
||||||
# Seeking only for videos
|
# Seeking only for videos
|
||||||
if not self.is_image_mode:
|
if not self.is_image_mode:
|
||||||
# Only start auto-repeat if not already active for this key
|
if not self.auto_repeat_active:
|
||||||
if not (self.auto_repeat_active and self.auto_repeat_key == key):
|
self.start_auto_repeat_seek(1, False, True) # Ctrl+D: +60 frames
|
||||||
self.start_auto_repeat_seek(key, 1, False, True) # Ctrl+D: +60 frames
|
|
||||||
elif key == ord("-") or key == ord("_"):
|
elif key == ord("-") or key == ord("_"):
|
||||||
self.rotate_clockwise()
|
self.rotate_clockwise()
|
||||||
print(f"Rotated to {self.rotation_angle}°")
|
print(f"Rotated to {self.rotation_angle}°")
|
||||||
|
Reference in New Issue
Block a user