feat(main.py): add timeline UI for video seeking and introduce mouse event handling
This commit is contained in:
92
main.py
92
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]
|
||||
|
Reference in New Issue
Block a user