refactor(main.py): simplify seeking logic and improve key repeat handling
This commit is contained in:
129
main.py
129
main.py
@@ -13,8 +13,9 @@ class MediaGrader:
|
||||
# Configuration constants
|
||||
DEFAULT_FPS = 30
|
||||
BASE_FRAME_DELAY_MS = 33 # ~30 FPS
|
||||
KEY_REPEAT_THRESHOLD_SEC = 0.5 # Faster detection for repeat
|
||||
KEY_REPEAT_RATE_SEC = 0.5 # How often to process key repeats (10 times per second)
|
||||
FAST_SEEK_ACTIVATION_TIME = 0.5 # How long to hold before fast seek
|
||||
FRAME_RENDER_TIME_MS = 50 # Time to let frames render between seeks
|
||||
WINDOW_MAX_WIDTH = 1200
|
||||
WINDOW_MAX_HEIGHT = 800
|
||||
WINDOW_MAX_SCALE_UP = 2.0
|
||||
@@ -40,10 +41,11 @@ class MediaGrader:
|
||||
self.current_frame = 0
|
||||
self.total_frames = 0
|
||||
|
||||
# Key repeat tracking
|
||||
self.last_key_time = 0
|
||||
self.last_key = None
|
||||
# Key repeat tracking with rate limiting
|
||||
self.last_seek_time = 0
|
||||
self.current_seek_key = None
|
||||
self.key_first_press_time = 0
|
||||
self.is_seeking = False
|
||||
|
||||
# Seeking modes
|
||||
self.fine_seek_frames = 1 # Frame-by-frame
|
||||
@@ -90,7 +92,7 @@ class MediaGrader:
|
||||
|
||||
def is_video(self, file_path: Path) -> bool:
|
||||
"""Check if file is a video"""
|
||||
return file_path.suffix.lower() in [".mp4", ".avi", ".mov", ".mkv", ".gif"]
|
||||
return file_path.suffix.lower() in self.extensions
|
||||
|
||||
def calculate_frame_delay(self) -> int:
|
||||
"""Calculate frame delay in milliseconds based on playback speed"""
|
||||
@@ -305,65 +307,74 @@ class MediaGrader:
|
||||
f"Seeking from {self.current_frame} to {target_frame} (delta: {frames_delta})"
|
||||
)
|
||||
|
||||
# Use I-frame seeking for smoother performance
|
||||
if abs(frames_delta) > 5 or self.snap_to_iframe:
|
||||
self.seek_to_iframe(target_frame)
|
||||
else:
|
||||
# For small seeks, use direct frame seeking
|
||||
# Use simple direct seeking - let OpenCV handle it
|
||||
self.current_cap.set(cv2.CAP_PROP_POS_FRAMES, target_frame)
|
||||
self.load_current_frame()
|
||||
|
||||
print(f"Seeked to frame {self.current_frame}")
|
||||
|
||||
def handle_seeking_key(self, key: int) -> bool:
|
||||
"""Handle seeking keys with different granularities. Returns True if key was handled."""
|
||||
def process_seek_key(self, key: int) -> bool:
|
||||
"""Process seeking keys with proper rate limiting"""
|
||||
current_time = time.time()
|
||||
|
||||
# Determine seek amount based on key and timing
|
||||
# Check if this is a seek key
|
||||
seek_direction = 0
|
||||
seek_amount = 0
|
||||
is_arrow_key = False
|
||||
|
||||
# Try different arrow key detection methods
|
||||
if key == ord("a"): # Left arrow (various systems)
|
||||
is_arrow_key = True
|
||||
direction = -1
|
||||
elif key == ord("d"): # Right arrow (various systems)
|
||||
is_arrow_key = True
|
||||
direction = 1
|
||||
elif key == ord(","): # Comma - fine seek backward
|
||||
if key == ord("a"): # Seek backward
|
||||
seek_direction = -1
|
||||
elif key == ord("d"): # Seek forward
|
||||
seek_direction = 1
|
||||
elif key == ord(","): # Fine seek backward
|
||||
seek_amount = -self.fine_seek_frames
|
||||
elif key == ord("."): # Period - fine seek forward
|
||||
elif key == ord("."): # Fine seek forward
|
||||
seek_amount = self.fine_seek_frames
|
||||
else:
|
||||
# Not a seek key, reset seeking state
|
||||
if self.current_seek_key is not None:
|
||||
self.current_seek_key = None
|
||||
self.is_seeking = False
|
||||
print("Seek key released")
|
||||
return False
|
||||
|
||||
if is_arrow_key:
|
||||
# Track key press timing for fast seek detection
|
||||
if self.last_key != key:
|
||||
# New key press
|
||||
self.key_first_press_time = current_time
|
||||
self.last_key = key
|
||||
seek_amount = direction * self.coarse_seek_frames
|
||||
else:
|
||||
# Repeated key press
|
||||
time_held = current_time - self.key_first_press_time
|
||||
time_since_last = current_time - self.last_key_time
|
||||
|
||||
print(
|
||||
f"Key held for {time_held:.2f}s, since last: {time_since_last:.2f}s"
|
||||
)
|
||||
|
||||
if time_held > self.FAST_SEEK_ACTIVATION_TIME:
|
||||
# Fast seek mode
|
||||
seek_amount = direction * self.fast_seek_frames
|
||||
print(f"FAST SEEK: {seek_amount} frames")
|
||||
else:
|
||||
# Normal seek
|
||||
seek_amount = direction * self.coarse_seek_frames
|
||||
|
||||
# Handle fine seeking (comma/period) - always immediate
|
||||
if seek_amount != 0:
|
||||
self.seek_video(seek_amount)
|
||||
self.last_key_time = current_time
|
||||
return True
|
||||
|
||||
# Handle arrow key seeking with rate limiting
|
||||
if seek_direction != 0:
|
||||
# Check if we should process this key press
|
||||
if self.current_seek_key != key:
|
||||
# New key press
|
||||
self.current_seek_key = key
|
||||
self.key_first_press_time = current_time
|
||||
self.last_seek_time = current_time
|
||||
self.is_seeking = True
|
||||
|
||||
# Immediate first seek
|
||||
seek_amount = seek_direction * self.coarse_seek_frames
|
||||
self.seek_video(seek_amount)
|
||||
print(f"Started seeking {seek_direction}")
|
||||
return True
|
||||
|
||||
elif self.is_seeking:
|
||||
# Continuing to hold the same key
|
||||
time_since_last_seek = current_time - self.last_seek_time
|
||||
time_held = current_time - self.key_first_press_time
|
||||
|
||||
# Only seek if enough time has passed (rate limiting)
|
||||
if time_since_last_seek >= self.KEY_REPEAT_RATE_SEC:
|
||||
self.last_seek_time = current_time
|
||||
|
||||
# Determine seek amount based on how long key has been held
|
||||
if time_held > self.FAST_SEEK_ACTIVATION_TIME:
|
||||
seek_amount = seek_direction * self.fast_seek_frames
|
||||
print(f"FAST SEEK: {seek_amount} frames")
|
||||
else:
|
||||
seek_amount = seek_direction * self.coarse_seek_frames
|
||||
|
||||
self.seek_video(seek_amount)
|
||||
return True
|
||||
|
||||
return False
|
||||
@@ -423,6 +434,7 @@ class MediaGrader:
|
||||
print(" N: Next file")
|
||||
print(" P: Previous file")
|
||||
print(" Q/ESC: Quit")
|
||||
print(f" Seek repeat rate: {1/self.KEY_REPEAT_RATE_SEC:.1f} seeks/second")
|
||||
|
||||
cv2.namedWindow("Media Grader", cv2.WINDOW_NORMAL)
|
||||
|
||||
@@ -443,6 +455,10 @@ class MediaGrader:
|
||||
|
||||
# Calculate appropriate delay
|
||||
if self.is_video(current_file):
|
||||
if self.is_seeking:
|
||||
# Shorter delay when seeking to be more responsive
|
||||
delay = self.FRAME_RENDER_TIME_MS
|
||||
else:
|
||||
delay = self.calculate_frame_delay()
|
||||
else:
|
||||
delay = self.IMAGE_DISPLAY_DELAY_MS
|
||||
@@ -464,14 +480,14 @@ class MediaGrader:
|
||||
self.playback_speed - self.SPEED_INCREMENT,
|
||||
)
|
||||
print(f"Speed: {self.playback_speed:.1f}x")
|
||||
elif key == ord("w"):
|
||||
elif key == ord("s"):
|
||||
self.playback_speed = min(
|
||||
self.MAX_PLAYBACK_SPEED,
|
||||
self.playback_speed + self.SPEED_INCREMENT,
|
||||
)
|
||||
print(f"Speed: {self.playback_speed:.1f}x")
|
||||
elif self.handle_seeking_key(key):
|
||||
# Seeking was handled and frame was updated
|
||||
elif self.process_seek_key(key):
|
||||
# Seeking was handled
|
||||
pass
|
||||
elif key == ord("n"): # Next file
|
||||
break
|
||||
@@ -484,13 +500,12 @@ class MediaGrader:
|
||||
return
|
||||
break
|
||||
elif key == 255: # No key pressed
|
||||
# Reset key tracking if no key is pressed
|
||||
if self.last_key is not None:
|
||||
self.last_key = None
|
||||
print("Key released")
|
||||
# Continue seeking if we're in seeking mode
|
||||
if self.is_seeking and self.current_seek_key is not None:
|
||||
self.process_seek_key(self.current_seek_key)
|
||||
|
||||
# Advance frame only if playing (and it's a video)
|
||||
if self.is_playing and self.is_video(current_file):
|
||||
# Advance frame only if playing (and it's a video) and not seeking
|
||||
if self.is_playing and self.is_video(current_file) and not self.is_seeking:
|
||||
if not self.advance_frame():
|
||||
# End of video
|
||||
break
|
||||
|
Reference in New Issue
Block a user