feat(main.py): implement advanced seeking controls and auto-window resizing

This commit is contained in:
2025-08-18 16:17:35 +02:00
parent 1b2fa03ab6
commit 2b9988b592

110
main.py
View File

@@ -4,6 +4,7 @@ import glob
import cv2
import argparse
import shutil
import time
from pathlib import Path
from typing import List, Tuple, Optional
@@ -20,6 +21,16 @@ class MediaGrader:
self.current_frame = 0
self.total_frames = 0
# Key repeat tracking
self.last_key_time = 0
self.key_repeat_delay = 0.1 # 100ms between repeats
self.last_key = None
# Seeking modes
self.fine_seek_frames = 1 # Frame-by-frame
self.coarse_seek_frames = self.seek_frames # User-configurable
self.fast_seek_frames = self.seek_frames * 5 # 5x the normal seek
# Supported media extensions
self.extensions = ['*.png', '*.jpg', '*.jpeg', '*.gif', '*.mp4', '*.avi', '*.mov', '*.mkv']
@@ -87,6 +98,29 @@ class MediaGrader:
return False, None
return True, frame
def auto_resize_window(self, frame):
"""Auto-resize window to fit media while respecting screen limits"""
height, width = frame.shape[:2]
# Get screen size (approximate - OpenCV doesn't have direct access)
# Use reasonable defaults for common screen sizes
max_width = 1200
max_height = 800
# Calculate scaling factor to fit within max dimensions
scale_w = max_width / width if width > max_width else 1.0
scale_h = max_height / height if height > max_height else 1.0
scale = min(scale_w, scale_h)
# Don't scale up small images too much
if scale > 2.0:
scale = 2.0
new_width = int(width * scale)
new_height = int(height * scale)
cv2.resizeWindow('Media Grader', new_width, new_height)
def seek_video(self, frames_delta: int):
"""Seek video by specified number of frames"""
if not self.current_cap or not self.is_video(self.media_files[self.current_index]):
@@ -101,6 +135,42 @@ class MediaGrader:
self.current_cap.set(cv2.CAP_PROP_POS_FRAMES, new_frame)
self.current_frame = new_frame
def handle_seeking_key(self, key: int) -> bool:
"""Handle seeking keys with different granularities. Returns True if key was handled."""
current_time = time.time()
# Determine seek amount based on modifier keys and timing
seek_amount = 0
if key == 81: # Left arrow
# Use different seek amounts based on key repeat pattern
if self.last_key == key and (current_time - self.last_key_time) < 0.5:
# Fast repeat - use larger seek
seek_amount = -self.fast_seek_frames
else:
# Normal seek
seek_amount = -self.coarse_seek_frames
elif key == 83: # Right arrow
if self.last_key == key and (current_time - self.last_key_time) < 0.5:
seek_amount = self.fast_seek_frames
else:
seek_amount = self.coarse_seek_frames
elif key == ord(','): # Comma - fine seek backward
seek_amount = -self.fine_seek_frames
elif key == ord('.'): # Period - fine seek forward
seek_amount = self.fine_seek_frames
elif key == ord('['): # Left bracket - medium seek backward
seek_amount = -self.coarse_seek_frames
elif key == ord(']'): # Right bracket - medium seek forward
seek_amount = self.coarse_seek_frames
else:
return False
self.seek_video(seek_amount)
self.last_key = key
self.last_key_time = current_time
return True
def grade_media(self, grade: int):
"""Move current media file to grade directory"""
if not self.media_files or grade < 1 or grade > 5:
@@ -149,7 +219,9 @@ class MediaGrader:
print(f"Found {len(self.media_files)} media files")
print("Controls:")
print(" Space: Pause/Play")
print(" Left/Right: Seek backward/forward")
print(" Left/Right: Seek backward/forward (accelerates on repeat)")
print(" , / . : Frame-by-frame seek (fine control)")
print(" [ / ] : Normal seek (medium control)")
print(" A/D: Decrease/Increase playback speed")
print(" 1-5: Grade and move file")
print(" N: Next file")
@@ -169,7 +241,8 @@ class MediaGrader:
window_title = f"Media Grader - {current_file.name} ({self.current_index + 1}/{len(self.media_files)})"
cv2.setWindowTitle('Media Grader', window_title)
delay = int(33 / self.playback_speed) if self.is_video(current_file) else 0
delay = int(33 / self.playback_speed) if self.is_video(current_file) else 30
window_resized = False
while True:
result = self.display_media(current_file)
@@ -178,10 +251,20 @@ class MediaGrader:
ret, frame = result
# Auto-resize window on first frame
if not window_resized:
self.auto_resize_window(frame)
window_resized = True
# Add info overlay
info_text = f"Speed: {self.playback_speed:.1f}x | Frame: {self.current_frame}/{self.total_frames} | File: {self.current_index + 1}/{len(self.media_files)}"
help_text = "Seek: ←→ (accel) ,. (fine) [] (med) | A/D speed | 1-5 grade | Space pause | Q quit"
# White background for text visibility
cv2.putText(frame, info_text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
cv2.putText(frame, info_text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 0), 1)
cv2.putText(frame, help_text, (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)
cv2.putText(frame, help_text, (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1)
cv2.imshow('Media Grader', frame)
@@ -191,19 +274,16 @@ class MediaGrader:
return
elif key == ord(' '): # Space - pause/play
self.is_playing = not self.is_playing
delay = int(33 / self.playback_speed) if self.is_playing and self.is_video(current_file) else 0
elif key == 81 or key == ord('a'): # Left arrow or A - decrease speed
if key == 81: # Left arrow - seek backward
self.seek_video(-self.seek_frames)
else: # A - decrease speed
self.playback_speed = max(0.1, self.playback_speed - 0.1)
delay = int(33 / self.playback_speed) if self.is_video(current_file) else 0
elif key == 83 or key == ord('d'): # Right arrow or D
if key == 83: # Right arrow - seek forward
self.seek_video(self.seek_frames)
else: # D - increase speed
self.playback_speed = min(5.0, self.playback_speed + 0.1)
delay = int(33 / self.playback_speed) if self.is_video(current_file) else 0
delay = int(33 / self.playback_speed) if self.is_playing and self.is_video(current_file) else 30
elif key == ord('a'): # A - decrease speed
self.playback_speed = max(0.1, self.playback_speed - 0.1)
delay = int(33 / self.playback_speed) if self.is_video(current_file) else 30
elif key == ord('d'): # D - increase speed
self.playback_speed = min(5.0, self.playback_speed + 0.1)
delay = int(33 / self.playback_speed) if self.is_video(current_file) else 30
elif self.handle_seeking_key(key):
# Seeking was handled
pass
elif key == ord('n'): # Next file
break
elif key == ord('p'): # Previous file