feat(main.py): refactor video sampling and navigation logic to improve usability and consistency
This commit is contained in:
120
main.py
120
main.py
@@ -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,26 +337,12 @@ 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]
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -939,6 +976,10 @@ class MediaGrader:
|
|||||||
if self.current_cap:
|
if self.current_cap:
|
||||||
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))
|
||||||
@@ -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()
|
||||||
|
Reference in New Issue
Block a user