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
|
MAX_PLAYBACK_SPEED = 100.0
|
||||||
FAST_SEEK_MULTIPLIER = 60
|
FAST_SEEK_MULTIPLIER = 60
|
||||||
IMAGE_DISPLAY_DELAY_MS = 100
|
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__(
|
def __init__(
|
||||||
self, directory: str, seek_frames: int = 30, snap_to_iframe: bool = False
|
self, directory: str, seek_frames: int = 30, snap_to_iframe: bool = False
|
||||||
@@ -59,6 +69,12 @@ class MediaGrader:
|
|||||||
".mov",
|
".mov",
|
||||||
".mkv",
|
".mkv",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Mouse interaction for timeline
|
||||||
|
self.mouse_dragging = False
|
||||||
|
self.timeline_rect = None
|
||||||
|
self.window_width = 800
|
||||||
|
self.window_height = 600
|
||||||
|
|
||||||
# Create grade directories
|
# Create grade directories
|
||||||
for i in range(1, 6):
|
for i in range(1, 6):
|
||||||
@@ -164,9 +180,84 @@ class MediaGrader:
|
|||||||
cv2.putText(
|
cv2.putText(
|
||||||
frame, info_text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 0), 1
|
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)
|
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):
|
def advance_frame(self):
|
||||||
"""Advance to next frame(s) based on playback speed"""
|
"""Advance to next frame(s) based on playback speed"""
|
||||||
if (
|
if (
|
||||||
@@ -309,6 +400,7 @@ class MediaGrader:
|
|||||||
print(" Q/ESC: Quit")
|
print(" Q/ESC: Quit")
|
||||||
|
|
||||||
cv2.namedWindow("Media Grader", cv2.WINDOW_NORMAL)
|
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):
|
while self.media_files and self.current_index < len(self.media_files):
|
||||||
current_file = self.media_files[self.current_index]
|
current_file = self.media_files[self.current_index]
|
||||||
|
|||||||
Reference in New Issue
Block a user