feat(main.py): refactor video sampling and navigation logic to improve usability and consistency

This commit is contained in:
2025-08-20 11:48:34 +02:00
parent 9a8424feb3
commit 03d4d26335

118
main.py
View File

@@ -304,6 +304,25 @@ class MediaGrader:
largest_gap = max(gaps, key=lambda x: x[1] - x[0]) largest_gap = max(gaps, key=lambda x: x[1] - x[0])
return largest_gap return largest_gap
def get_sample_points(self):
"""Get standardized sample points for video navigation"""
segments = 8 # Divide video into 8 segments for sampling
segment_size = self.total_frames // segments
if segment_size == 0:
return []
return [
segment_size * 2, # 1/4 through
segment_size * 4, # 1/2 through
segment_size * 6, # 3/4 through
segment_size * 1, # 1/8 through
segment_size * 3, # 3/8 through
segment_size * 5, # 5/8 through
segment_size * 7, # 7/8 through
0 # Beginning
]
def jump_to_unwatched_region(self): def jump_to_unwatched_region(self):
"""Jump to the next unwatched region of the video""" """Jump to the next unwatched region of the video"""
if not self.is_video(self.media_files[self.current_index]): if not self.is_video(self.media_files[self.current_index]):
@@ -318,27 +337,13 @@ class MediaGrader:
if current_file not in self.jump_counters: if current_file not in self.jump_counters:
self.jump_counters[current_file] = 0 self.jump_counters[current_file] = 0
# Define sampling strategy: divide video into segments and sample them # Get standardized sample points
segments = 8 # Divide video into 8 segments for sampling sample_points = self.get_sample_points()
segment_size = self.total_frames // segments
if segment_size == 0: if not sample_points:
print("Video too short for sampling") print("Video too short for sampling")
return False return False
# Jump to different segments in a smart order
# Start with 1/4, 1/2, 3/4, then fill in the gaps
sample_points = [
segment_size * 2, # 1/4 through
segment_size * 4, # 1/2 through
segment_size * 6, # 3/4 through
segment_size * 1, # 1/8 through
segment_size * 3, # 3/8 through
segment_size * 5, # 5/8 through
segment_size * 7, # 7/8 through
0 # Beginning
]
current_jump = self.jump_counters[current_file] current_jump = self.jump_counters[current_file]
if current_jump >= len(sample_points): if current_jump >= len(sample_points):
@@ -416,19 +421,8 @@ class MediaGrader:
print("No sampling started yet. Use L first to establish sample points.") print("No sampling started yet. Use L first to establish sample points.")
return False return False
# Define same sampling strategy as L key # Use same sampling strategy as L key
segments = 8 sample_points = self.get_sample_points()
segment_size = self.total_frames // segments
sample_points = [
segment_size * 2, # 1/4 through
segment_size * 4, # 1/2 through
segment_size * 6, # 3/4 through
segment_size * 1, # 1/8 through
segment_size * 3, # 3/8 through
segment_size * 5, # 5/8 through
segment_size * 7, # 7/8 through
0 # Beginning
]
current_jump = self.jump_counters[current_file] current_jump = self.jump_counters[current_file]
@@ -517,13 +511,12 @@ class MediaGrader:
current_file = self.media_files[self.current_index] current_file = self.media_files[self.current_index]
# Calculate segment positions # Calculate segment positions - evenly spaced through video
segment_duration = self.total_frames // self.segment_count
overlap_frames = int(segment_duration * self.segment_overlap_percent / 100)
self.segment_positions = [] self.segment_positions = []
for i in range(self.segment_count): for i in range(self.segment_count):
start_frame = max(0, i * segment_duration - overlap_frames) # Position segments at 0%, 25%, 50%, 75% of video
position_ratio = i / max(1, self.segment_count - 1) if self.segment_count > 1 else 0
start_frame = int(position_ratio * (self.total_frames - 1))
self.segment_positions.append(start_frame) self.segment_positions.append(start_frame)
# Create video captures for each segment # Create video captures for each segment
@@ -719,8 +712,52 @@ class MediaGrader:
1 1
) )
# Draw multi-segment timeline
self.draw_multi_segment_timeline(combined_frame)
cv2.imshow("Media Grader", combined_frame) cv2.imshow("Media Grader", combined_frame)
def draw_multi_segment_timeline(self, frame):
"""Draw timeline showing all segment positions"""
if not self.is_video(self.media_files[self.current_index]) or not self.segment_caps:
return
height, width = frame.shape[:2]
# Timeline area - smaller than normal timeline
timeline_height = 30
timeline_y = height - timeline_height - 25 # Leave space for info text
timeline_margin = 20
timeline_bar_height = 8
# Draw timeline background
cv2.rectangle(frame, (0, timeline_y), (width, timeline_y + timeline_height), (40, 40, 40), -1)
# Calculate timeline bar position
bar_y = timeline_y + (timeline_height - timeline_bar_height) // 2
bar_x_start = timeline_margin
bar_x_end = width - timeline_margin
bar_width = bar_x_end - bar_x_start
# Draw timeline background bar
cv2.rectangle(frame, (bar_x_start, bar_y), (bar_x_end, bar_y + timeline_bar_height), (80, 80, 80), -1)
cv2.rectangle(frame, (bar_x_start, bar_y), (bar_x_end, bar_y + timeline_bar_height), (200, 200, 200), 1)
# Draw segment markers
if self.total_frames > 0:
for i, segment_pos in enumerate(self.segment_positions):
# Calculate position on timeline
progress = segment_pos / max(1, self.total_frames - 1)
marker_x = bar_x_start + int(bar_width * progress)
# Draw segment marker
color = (0, 255, 100) if i < len(self.segment_caps) and self.segment_caps[i] else (100, 100, 100)
cv2.circle(frame, (marker_x, bar_y + timeline_bar_height // 2), 4, color, -1)
cv2.circle(frame, (marker_x, bar_y + timeline_bar_height // 2), 4, (255, 255, 255), 1)
# Add segment number
cv2.putText(frame, str(i+1), (marker_x - 3, bar_y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.3, (255, 255, 255), 1)
def draw_timeline(self, frame): def draw_timeline(self, frame):
"""Draw timeline at the bottom of the frame""" """Draw timeline at the bottom of the frame"""
# Only draw timeline for video files in single mode # Only draw timeline for video files in single mode
@@ -940,6 +977,10 @@ class MediaGrader:
self.current_cap.release() self.current_cap.release()
self.current_cap = None self.current_cap = None
# Also release segment captures if in multi-segment mode
if self.multi_segment_mode:
self.cleanup_segment_captures()
try: try:
shutil.move(str(current_file), str(destination)) shutil.move(str(current_file), str(destination))
print(f"Moved {current_file.name} to grade {grade}") print(f"Moved {current_file.name} to grade {grade}")
@@ -1021,7 +1062,7 @@ class MediaGrader:
print(" N: Next file") print(" N: Next file")
print(" P: Previous file") print(" P: Previous file")
print(" U: Undo last grading action") print(" U: Undo last grading action")
print(" L: Sample video at key points (videos only, disabled in multi-segment)") print(" L: Sample video at key points (videos only)")
print(" H: Undo last L jump (videos only, disabled in multi-segment)") print(" H: Undo last L jump (videos only, disabled in multi-segment)")
print(" J: Bisect backwards from current position (videos only, disabled in multi-segment)") print(" J: Bisect backwards from current position (videos only, disabled in multi-segment)")
print(" K: Bisect forwards toward next sample (videos only, disabled in multi-segment)") print(" K: Bisect forwards toward next sample (videos only, disabled in multi-segment)")
@@ -1085,11 +1126,8 @@ class MediaGrader:
# File was restored, reload it # File was restored, reload it
break break
elif key == ord("l"): elif key == ord("l"):
if not self.multi_segment_mode: # Jump to largest unwatched region (works in both modes)
# Jump to largest unwatched region
self.jump_to_unwatched_region() self.jump_to_unwatched_region()
else:
print("Navigation keys (H/J/K/L) disabled in multi-segment mode")
elif key == ord("j"): elif key == ord("j"):
if not self.multi_segment_mode: if not self.multi_segment_mode:
self.bisect_backwards() self.bisect_backwards()