diff --git a/main.py b/main.py index 452f09c..2c15251 100644 --- a/main.py +++ b/main.py @@ -20,6 +20,16 @@ class MediaGrader: MAX_PLAYBACK_SPEED = 100.0 FAST_SEEK_MULTIPLIER = 60 IMAGE_DISPLAY_DELAY_MS = 100 + + # Timeline configuration + TIMELINE_HEIGHT = 60 + TIMELINE_MARGIN = 20 + TIMELINE_BAR_HEIGHT = 12 + TIMELINE_HANDLE_SIZE = 12 + TIMELINE_COLOR_BG = (80, 80, 80) + TIMELINE_COLOR_PROGRESS = (0, 120, 255) + TIMELINE_COLOR_HANDLE = (255, 255, 255) + TIMELINE_COLOR_BORDER = (200, 200, 200) def __init__( self, directory: str, seek_frames: int = 30, snap_to_iframe: bool = False @@ -59,6 +69,12 @@ class MediaGrader: ".mov", ".mkv", ] + + # Mouse interaction for timeline + self.mouse_dragging = False + self.timeline_rect = None + self.window_width = 800 + self.window_height = 600 # Create grade directories for i in range(1, 6): @@ -164,9 +180,84 @@ class MediaGrader: cv2.putText( frame, info_text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 0), 1 ) + + # Draw timeline + self.draw_timeline(frame) cv2.imshow("Media Grader", frame) + def draw_timeline(self, frame): + """Draw timeline at the bottom of the frame""" + height, width = frame.shape[:2] + self.window_height = height + self.window_width = width + + # Timeline background area + timeline_y = height - self.TIMELINE_HEIGHT + cv2.rectangle(frame, (0, timeline_y), (width, height), (40, 40, 40), -1) + + # Calculate timeline bar position + bar_y = timeline_y + (self.TIMELINE_HEIGHT - self.TIMELINE_BAR_HEIGHT) // 2 + bar_x_start = self.TIMELINE_MARGIN + bar_x_end = width - self.TIMELINE_MARGIN + bar_width = bar_x_end - bar_x_start + + self.timeline_rect = (bar_x_start, bar_y, bar_width, self.TIMELINE_BAR_HEIGHT) + + # Draw timeline background + cv2.rectangle(frame, (bar_x_start, bar_y), (bar_x_end, bar_y + self.TIMELINE_BAR_HEIGHT), self.TIMELINE_COLOR_BG, -1) + cv2.rectangle(frame, (bar_x_start, bar_y), (bar_x_end, bar_y + self.TIMELINE_BAR_HEIGHT), self.TIMELINE_COLOR_BORDER, 1) + + # Draw progress for videos + if self.is_video(self.media_files[self.current_index]) and self.total_frames > 0: + progress = self.current_frame / max(1, self.total_frames - 1) + progress_width = int(bar_width * progress) + if progress_width > 0: + cv2.rectangle(frame, (bar_x_start, bar_y), (bar_x_start + progress_width, bar_y + self.TIMELINE_BAR_HEIGHT), self.TIMELINE_COLOR_PROGRESS, -1) + + # Draw handle + handle_x = bar_x_start + progress_width + handle_y = bar_y + self.TIMELINE_BAR_HEIGHT // 2 + cv2.circle(frame, (handle_x, handle_y), self.TIMELINE_HANDLE_SIZE // 2, self.TIMELINE_COLOR_HANDLE, -1) + cv2.circle(frame, (handle_x, handle_y), self.TIMELINE_HANDLE_SIZE // 2, self.TIMELINE_COLOR_BORDER, 2) + + def mouse_callback(self, event, x, y, flags, param): + """Handle mouse events for timeline interaction""" + if not self.timeline_rect or not self.is_video(self.media_files[self.current_index]): + return + + bar_x_start, bar_y, bar_width, bar_height = self.timeline_rect + bar_x_end = bar_x_start + bar_width + + # Check if mouse is over timeline + if bar_y <= y <= bar_y + bar_height + 10: # Add some extra height for easier clicking + if event == cv2.EVENT_LBUTTONDOWN: + if bar_x_start <= x <= bar_x_end: + self.mouse_dragging = True + self.seek_to_position(x, bar_x_start, bar_width) + elif event == cv2.EVENT_MOUSEMOVE and self.mouse_dragging: + if bar_x_start <= x <= bar_x_end: + self.seek_to_position(x, bar_x_start, bar_width) + elif event == cv2.EVENT_LBUTTONUP: + self.mouse_dragging = False + + def seek_to_position(self, mouse_x, bar_x_start, bar_width): + """Seek to position based on mouse click/drag on timeline""" + if not self.current_cap or not self.is_video(self.media_files[self.current_index]): + return + + # Calculate position ratio + relative_x = mouse_x - bar_x_start + position_ratio = max(0, min(1, relative_x / bar_width)) + + # Calculate target frame + target_frame = int(position_ratio * (self.total_frames - 1)) + target_frame = max(0, min(target_frame, self.total_frames - 1)) + + # Seek to target frame + self.current_cap.set(cv2.CAP_PROP_POS_FRAMES, target_frame) + self.load_current_frame() + def advance_frame(self): """Advance to next frame(s) based on playback speed""" if ( @@ -309,6 +400,7 @@ class MediaGrader: print(" Q/ESC: Quit") cv2.namedWindow("Media Grader", cv2.WINDOW_NORMAL) + cv2.setMouseCallback("Media Grader", self.mouse_callback) while self.media_files and self.current_index < len(self.media_files): current_file = self.media_files[self.current_index]