From 2b9988b5922f4bcedb686d5e831158fc76f7b728 Mon Sep 17 00:00:00 2001 From: PhatPhuckDave Date: Mon, 18 Aug 2025 16:17:35 +0200 Subject: [PATCH] feat(main.py): implement advanced seeking controls and auto-window resizing --- main.py | 110 ++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 95 insertions(+), 15 deletions(-) diff --git a/main.py b/main.py index fe17c19..7c07b76 100644 --- a/main.py +++ b/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 - 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