feat(main.py): refactor media grading logic with constants and improved controls
This commit is contained in:
		
							
								
								
									
										83
									
								
								main.py
									
									
									
									
									
								
							
							
						
						
									
										83
									
								
								main.py
									
									
									
									
									
								
							@@ -9,6 +9,20 @@ from pathlib import Path
 | 
			
		||||
from typing import List, Tuple, Optional
 | 
			
		||||
 | 
			
		||||
class MediaGrader:
 | 
			
		||||
    # Configuration constants
 | 
			
		||||
    DEFAULT_FPS = 30
 | 
			
		||||
    BASE_FRAME_DELAY_MS = 33  # ~30 FPS
 | 
			
		||||
    KEY_REPEAT_THRESHOLD_SEC = 0.5
 | 
			
		||||
    WINDOW_MAX_WIDTH = 1200
 | 
			
		||||
    WINDOW_MAX_HEIGHT = 800
 | 
			
		||||
    WINDOW_MAX_SCALE_UP = 2.0
 | 
			
		||||
    SPEED_INCREMENT = 0.1
 | 
			
		||||
    MIN_PLAYBACK_SPEED = 0.1
 | 
			
		||||
    MAX_PLAYBACK_SPEED = 100.0
 | 
			
		||||
    FAST_SEEK_MULTIPLIER = 500
 | 
			
		||||
    IFRAME_SNAP_INTERVAL = 30
 | 
			
		||||
    IMAGE_DISPLAY_DELAY_MS = 100
 | 
			
		||||
    
 | 
			
		||||
    def __init__(self, directory: str, seek_frames: int = 30, snap_to_iframe: bool = False):
 | 
			
		||||
        self.directory = Path(directory)
 | 
			
		||||
        self.seek_frames = seek_frames
 | 
			
		||||
@@ -23,13 +37,12 @@ class MediaGrader:
 | 
			
		||||
        
 | 
			
		||||
        # 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
 | 
			
		||||
        self.fast_seek_frames = self.seek_frames * self.FAST_SEEK_MULTIPLIER
 | 
			
		||||
        
 | 
			
		||||
        # Supported media extensions
 | 
			
		||||
        self.extensions = ['*.png', '*.jpg', '*.jpeg', '*.gif', '*.mp4', '*.avi', '*.mov', '*.mkv']
 | 
			
		||||
@@ -60,6 +73,15 @@ class MediaGrader:
 | 
			
		||||
        """Check if file is a video"""
 | 
			
		||||
        return file_path.suffix.lower() in ['.mp4', '.avi', '.mov', '.mkv', '.gif']
 | 
			
		||||
    
 | 
			
		||||
    def calculate_frame_delay(self) -> int:
 | 
			
		||||
        """Calculate frame delay in milliseconds based on playback speed"""
 | 
			
		||||
        if not self.is_playing:
 | 
			
		||||
            return 0  # No delay when paused
 | 
			
		||||
        
 | 
			
		||||
        # Base delay for 30 FPS, adjusted by playback speed
 | 
			
		||||
        delay_ms = int(self.BASE_FRAME_DELAY_MS / self.playback_speed)
 | 
			
		||||
        return max(1, delay_ms)  # Minimum 1ms delay
 | 
			
		||||
    
 | 
			
		||||
    def load_media(self, file_path: Path) -> bool:
 | 
			
		||||
        """Load media file for display"""
 | 
			
		||||
        if self.current_cap:
 | 
			
		||||
@@ -102,19 +124,14 @@ class MediaGrader:
 | 
			
		||||
        """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_w = self.WINDOW_MAX_WIDTH / width if width > self.WINDOW_MAX_WIDTH else 1.0
 | 
			
		||||
        scale_h = self.WINDOW_MAX_HEIGHT / height if height > self.WINDOW_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
 | 
			
		||||
        if scale > self.WINDOW_MAX_SCALE_UP:
 | 
			
		||||
            scale = self.WINDOW_MAX_SCALE_UP
 | 
			
		||||
        
 | 
			
		||||
        new_width = int(width * scale)
 | 
			
		||||
        new_height = int(height * scale)
 | 
			
		||||
@@ -130,7 +147,7 @@ class MediaGrader:
 | 
			
		||||
        
 | 
			
		||||
        if self.snap_to_iframe and frames_delta < 0:
 | 
			
		||||
            # Find previous I-frame (approximation)
 | 
			
		||||
            new_frame = max(0, new_frame - (new_frame % 30))
 | 
			
		||||
            new_frame = max(0, new_frame - (new_frame % self.IFRAME_SNAP_INTERVAL))
 | 
			
		||||
        
 | 
			
		||||
        self.current_cap.set(cv2.CAP_PROP_POS_FRAMES, new_frame)
 | 
			
		||||
        self.current_frame = new_frame
 | 
			
		||||
@@ -139,19 +156,17 @@ class MediaGrader:
 | 
			
		||||
        """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
 | 
			
		||||
        # Determine seek amount based on key 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
 | 
			
		||||
        # Try different arrow key detection methods
 | 
			
		||||
        if key == 2424832 or key == 81:  # Left arrow (different systems)
 | 
			
		||||
            if self.last_key == key and (current_time - self.last_key_time) < self.KEY_REPEAT_THRESHOLD_SEC:
 | 
			
		||||
                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:
 | 
			
		||||
        elif key == 2555904 or key == 83:  # Right arrow (different systems)
 | 
			
		||||
            if self.last_key == key and (current_time - self.last_key_time) < self.KEY_REPEAT_THRESHOLD_SEC:
 | 
			
		||||
                seek_amount = self.fast_seek_frames
 | 
			
		||||
            else:
 | 
			
		||||
                seek_amount = self.coarse_seek_frames
 | 
			
		||||
@@ -159,10 +174,6 @@ class MediaGrader:
 | 
			
		||||
            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
 | 
			
		||||
        
 | 
			
		||||
@@ -221,7 +232,6 @@ class MediaGrader:
 | 
			
		||||
        print("  Space: Pause/Play")
 | 
			
		||||
        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")
 | 
			
		||||
@@ -241,10 +251,11 @@ 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 30
 | 
			
		||||
            window_resized = False
 | 
			
		||||
            
 | 
			
		||||
            while True:
 | 
			
		||||
                # Only advance frame if playing (for videos)
 | 
			
		||||
                if self.is_playing or not self.is_video(current_file):
 | 
			
		||||
                    result = self.display_media(current_file)
 | 
			
		||||
                    if result is None or not result[0]:
 | 
			
		||||
                        break
 | 
			
		||||
@@ -258,7 +269,7 @@ class MediaGrader:
 | 
			
		||||
                    
 | 
			
		||||
                    # 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"
 | 
			
		||||
                    help_text = "Seek: ←→ (accel) ,. (fine) | 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)
 | 
			
		||||
@@ -268,19 +279,23 @@ class MediaGrader:
 | 
			
		||||
                    
 | 
			
		||||
                    cv2.imshow('Media Grader', frame)
 | 
			
		||||
                
 | 
			
		||||
                # Calculate appropriate delay
 | 
			
		||||
                delay = self.calculate_frame_delay() if self.is_video(current_file) else self.IMAGE_DISPLAY_DELAY_MS
 | 
			
		||||
                
 | 
			
		||||
                key = cv2.waitKey(delay) & 0xFF
 | 
			
		||||
                
 | 
			
		||||
                # Debug: print key codes to help with arrow key detection
 | 
			
		||||
                if key != 255:  # 255 means no key pressed
 | 
			
		||||
                    print(f"Key pressed: {key}")
 | 
			
		||||
                
 | 
			
		||||
                if key == ord('q') or key == 27:  # Q or ESC
 | 
			
		||||
                    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 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
 | 
			
		||||
                    self.playback_speed = max(self.MIN_PLAYBACK_SPEED, self.playback_speed - self.SPEED_INCREMENT)
 | 
			
		||||
                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
 | 
			
		||||
                    self.playback_speed = min(self.MAX_PLAYBACK_SPEED, self.playback_speed + self.SPEED_INCREMENT)
 | 
			
		||||
                elif self.handle_seeking_key(key):
 | 
			
		||||
                    # Seeking was handled
 | 
			
		||||
                    pass
 | 
			
		||||
@@ -295,10 +310,6 @@ class MediaGrader:
 | 
			
		||||
                        return
 | 
			
		||||
                    break
 | 
			
		||||
            
 | 
			
		||||
                if not self.is_playing and not self.is_video(current_file):
 | 
			
		||||
                    # For images, wait indefinitely when paused
 | 
			
		||||
                    continue
 | 
			
		||||
            
 | 
			
		||||
            if key not in [ord('p')]:  # Don't increment for previous
 | 
			
		||||
                self.current_index += 1
 | 
			
		||||
        
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user