diff --git a/main.py b/main.py index 2ef0327..b16c515 100644 --- a/main.py +++ b/main.py @@ -304,6 +304,25 @@ class MediaGrader: largest_gap = max(gaps, key=lambda x: x[1] - x[0]) 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): """Jump to the next unwatched region of the video""" 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: self.jump_counters[current_file] = 0 - # Define sampling strategy: divide video into segments and sample them - segments = 8 # Divide video into 8 segments for sampling - segment_size = self.total_frames // segments + # Get standardized sample points + sample_points = self.get_sample_points() - if segment_size == 0: + if not sample_points: print("Video too short for sampling") 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] @@ -416,19 +421,8 @@ class MediaGrader: print("No sampling started yet. Use L first to establish sample points.") return False - # Define same sampling strategy as L key - segments = 8 - 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 - ] + # Use same sampling strategy as L key + sample_points = self.get_sample_points() current_jump = self.jump_counters[current_file] @@ -517,13 +511,12 @@ class MediaGrader: current_file = self.media_files[self.current_index] - # Calculate segment positions - segment_duration = self.total_frames // self.segment_count - overlap_frames = int(segment_duration * self.segment_overlap_percent / 100) - + # Calculate segment positions - evenly spaced through video self.segment_positions = [] 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) # Create video captures for each segment @@ -719,8 +712,52 @@ class MediaGrader: 1 ) + # Draw multi-segment timeline + self.draw_multi_segment_timeline(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): """Draw timeline at the bottom of the frame""" # Only draw timeline for video files in single mode @@ -939,6 +976,10 @@ class MediaGrader: if self.current_cap: self.current_cap.release() self.current_cap = None + + # Also release segment captures if in multi-segment mode + if self.multi_segment_mode: + self.cleanup_segment_captures() try: shutil.move(str(current_file), str(destination)) @@ -1021,7 +1062,7 @@ class MediaGrader: print(" N: Next file") print(" P: Previous file") 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(" 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)") @@ -1085,11 +1126,8 @@ class MediaGrader: # File was restored, reload it break elif key == ord("l"): - if not self.multi_segment_mode: - # Jump to largest unwatched region - self.jump_to_unwatched_region() - else: - print("Navigation keys (H/J/K/L) disabled in multi-segment mode") + # Jump to largest unwatched region (works in both modes) + self.jump_to_unwatched_region() elif key == ord("j"): if not self.multi_segment_mode: self.bisect_backwards()