feat(main.py): implement smart video seeking with keyframe optimization
This commit is contained in:
@@ -13,8 +13,6 @@ import json
|
|||||||
class VideoEditor:
|
class VideoEditor:
|
||||||
# Configuration constants
|
# Configuration constants
|
||||||
BASE_FRAME_DELAY_MS = 16 # ~60 FPS
|
BASE_FRAME_DELAY_MS = 16 # ~60 FPS
|
||||||
KEY_REPEAT_RATE_SEC = 0.3
|
|
||||||
FAST_SEEK_ACTIVATION_TIME = 1.5
|
|
||||||
SPEED_INCREMENT = 0.2
|
SPEED_INCREMENT = 0.2
|
||||||
MIN_PLAYBACK_SPEED = 0.1
|
MIN_PLAYBACK_SPEED = 0.1
|
||||||
MAX_PLAYBACK_SPEED = 10.0
|
MAX_PLAYBACK_SPEED = 10.0
|
||||||
@@ -502,18 +500,69 @@ class VideoEditor:
|
|||||||
self.current_frame = target_frame
|
self.current_frame = target_frame
|
||||||
self.load_current_frame()
|
self.load_current_frame()
|
||||||
|
|
||||||
|
def seek_video_smart(self, frames_delta: int):
|
||||||
|
"""Seek using keyframe jumping for large jumps"""
|
||||||
|
target_frame = max(
|
||||||
|
0, min(self.current_frame + frames_delta, self.total_frames - 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
# For small jumps, use regular seeking
|
||||||
|
if abs(frames_delta) <= 10:
|
||||||
|
self.current_frame = target_frame
|
||||||
|
self.load_current_frame()
|
||||||
|
return
|
||||||
|
|
||||||
|
# For larger jumps, use keyframe seeking
|
||||||
|
# Jump to nearest keyframe first, then seek forward
|
||||||
|
keyframe_interval = self._detect_keyframe_interval()
|
||||||
|
nearest_keyframe = (target_frame // keyframe_interval) * keyframe_interval
|
||||||
|
|
||||||
|
# Seek to keyframe first
|
||||||
|
self.cap.set(cv2.CAP_PROP_POS_FRAMES, nearest_keyframe)
|
||||||
|
self.current_frame = nearest_keyframe
|
||||||
|
|
||||||
|
# Then seek forward to target frame
|
||||||
|
frames_to_seek = target_frame - nearest_keyframe
|
||||||
|
for _ in range(frames_to_seek):
|
||||||
|
ret, frame = self.cap.read()
|
||||||
|
if not ret:
|
||||||
|
break
|
||||||
|
self.current_frame += 1
|
||||||
|
|
||||||
|
self.current_display_frame = frame if 'frame' in locals() else None
|
||||||
|
|
||||||
|
def _detect_keyframe_interval(self):
|
||||||
|
"""Detect the keyframe interval for the current video"""
|
||||||
|
# Try to get keyframe interval from video properties
|
||||||
|
try:
|
||||||
|
# Some backends support keyframe interval detection
|
||||||
|
keyframe_interval = self.cap.get(cv2.CAP_PROP_FRAME_COUNT) / self.cap.get(cv2.CAP_PROP_FPS)
|
||||||
|
if keyframe_interval > 0:
|
||||||
|
return int(keyframe_interval)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Fallback: estimate based on video characteristics
|
||||||
|
if self.fps >= 60:
|
||||||
|
return 60 # High FPS videos often have keyframes every 60 frames
|
||||||
|
elif self.fps >= 30:
|
||||||
|
return 30 # Standard videos have keyframes every 30 frames
|
||||||
|
else:
|
||||||
|
return 15 # Lower FPS videos have keyframes every 15 frames
|
||||||
|
|
||||||
def seek_video_with_modifier(
|
def seek_video_with_modifier(
|
||||||
self, direction: int, shift_pressed: bool, ctrl_pressed: bool
|
self, direction: int, shift_pressed: bool, ctrl_pressed: bool
|
||||||
):
|
):
|
||||||
"""Seek video with different frame counts based on modifiers"""
|
"""Seek video with different frame counts based on modifiers"""
|
||||||
if ctrl_pressed:
|
if ctrl_pressed:
|
||||||
frames = direction * 60 # Ctrl: 60 frames
|
frames = direction * 60 # Ctrl: 60 frames
|
||||||
|
self.seek_video_smart(frames)
|
||||||
elif shift_pressed:
|
elif shift_pressed:
|
||||||
frames = direction * 10 # Shift: 10 frames
|
frames = direction * 10 # Shift: 10 frames
|
||||||
|
self.seek_video_smart(frames)
|
||||||
else:
|
else:
|
||||||
frames = direction * 1 # Default: 1 frame
|
frames = direction * 1 # Default: 1 frame
|
||||||
|
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, key: int, direction: int, shift_pressed: bool, ctrl_pressed: bool):
|
||||||
"""Start auto-repeat seeking for a held key"""
|
"""Start auto-repeat seeking for a held key"""
|
||||||
@@ -544,12 +593,9 @@ class VideoEditor:
|
|||||||
|
|
||||||
# Do initial seek immediately
|
# Do initial seek immediately
|
||||||
self.seek_video_with_modifier(direction, shift_pressed, ctrl_pressed)
|
self.seek_video_with_modifier(direction, shift_pressed, ctrl_pressed)
|
||||||
print(f"DEBUG: Started auto-repeat for key {key}")
|
|
||||||
|
|
||||||
def stop_auto_repeat_seek(self):
|
def stop_auto_repeat_seek(self):
|
||||||
"""Stop auto-repeat seeking"""
|
"""Stop auto-repeat seeking"""
|
||||||
if self.auto_repeat_active:
|
|
||||||
print(f"DEBUG: Stopped auto-repeat")
|
|
||||||
self.auto_repeat_active = False
|
self.auto_repeat_active = False
|
||||||
self.auto_repeat_key = None
|
self.auto_repeat_key = None
|
||||||
self.auto_repeat_direction = 0
|
self.auto_repeat_direction = 0
|
||||||
@@ -1826,26 +1872,19 @@ class VideoEditor:
|
|||||||
delay = self.calculate_frame_delay() if self.is_playing else 1 # Very short delay for responsive key detection
|
delay = self.calculate_frame_delay() if self.is_playing else 1 # Very short delay for responsive key detection
|
||||||
key = cv2.waitKey(delay) & 0xFF
|
key = cv2.waitKey(delay) & 0xFF
|
||||||
|
|
||||||
# Debug key detection
|
|
||||||
if key != 255:
|
|
||||||
print(f"DEBUG: Key detected: {key} (auto_repeat_active: {self.auto_repeat_active}, auto_repeat_key: {self.auto_repeat_key})")
|
|
||||||
|
|
||||||
# Handle auto-repeat - stop if a different key is pressed or no key for too long
|
# Handle auto-repeat - stop if a different key is pressed or no key for too long
|
||||||
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)
|
# Check if enough time has passed since last key activity (key was released)
|
||||||
if time.time() - self.last_key_activity > 0.1: # 100ms timeout
|
if time.time() - self.last_key_activity > 0.1: # 100ms timeout
|
||||||
print(f"DEBUG: Key released - stopping auto-repeat")
|
|
||||||
self.stop_auto_repeat_seek()
|
self.stop_auto_repeat_seek()
|
||||||
elif key != 255 and self.auto_repeat_active:
|
elif key != 255 and self.auto_repeat_active:
|
||||||
# A key is pressed, update the last key activity time
|
# A key is pressed, update the last key activity time
|
||||||
self.last_key_activity = time.time()
|
self.last_key_activity = time.time()
|
||||||
# If it's a different key, stop auto-repeat
|
# If it's a different key, stop auto-repeat
|
||||||
if key != self.auto_repeat_key:
|
if key != self.auto_repeat_key:
|
||||||
print(f"DEBUG: Different key pressed ({key} vs {self.auto_repeat_key}) - stopping auto-repeat")
|
|
||||||
self.stop_auto_repeat_seek()
|
self.stop_auto_repeat_seek()
|
||||||
# If it's the same key, just update the last activity time (don't restart auto-repeat)
|
# If it's the same key, just update the last activity time (don't restart auto-repeat)
|
||||||
else:
|
|
||||||
print(f"DEBUG: Same key pressed ({key}) - updating last activity time")
|
|
||||||
|
|
||||||
# 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"
|
||||||
|
Reference in New Issue
Block a user