feat(main.py): implement advanced seeking controls and auto-window resizing
This commit is contained in:
106
main.py
106
main.py
@@ -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
|
||||
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 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
|
||||
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 0
|
||||
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
|
||||
|
Reference in New Issue
Block a user