refactor(main.py): simplify seeking logic and improve key repeat handling

This commit is contained in:
2025-08-18 16:48:13 +02:00
parent e43865a7c8
commit 85768f6323

129
main.py
View File

@@ -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