diff --git a/main.py b/main.py index 5d066b2..a91b573 100644 --- a/main.py +++ b/main.py @@ -84,6 +84,9 @@ class MediaGrader: self.current_watch_start = None # Frame where current viewing session started self.last_frame_position = 0 # Track last known frame position + # Bisection navigation tracking + self.last_jump_position = {} # Dict[file_path: last_frame] for bisection reference + def find_media_files(self) -> List[Path]: """Find all media files recursively in the directory""" media_files = [] @@ -307,6 +310,9 @@ class MediaGrader: target_frame = sample_points[current_jump] target_frame = min(target_frame, self.total_frames - 1) + # Track last position for bisection + self.last_jump_position[current_file] = self.current_frame + # Jump to the target frame self.current_cap.set(cv2.CAP_PROP_POS_FRAMES, target_frame) self.load_current_frame() @@ -320,6 +326,95 @@ class MediaGrader: print(f"Sample {current_jump + 1}/{len(sample_points)}: jumped to frame {target_frame} ({percentage:.1f}% through video)") return True + def bisect_backwards(self): + """Bisect backwards between last position and current position""" + if not self.is_video(self.media_files[self.current_index]): + return False + + current_file = str(self.media_files[self.current_index]) + + if current_file not in self.last_jump_position: + print("No previous position to bisect from. Use L first to establish a reference point.") + return False + + last_pos = self.last_jump_position[current_file] + current_pos = self.current_frame + + if last_pos == current_pos: + print("Already at the same position as last jump.") + return False + + # Calculate midpoint + if last_pos < current_pos: + midpoint = (last_pos + current_pos) // 2 + else: + midpoint = (current_pos + last_pos) // 2 + + # Update last position for further bisection + self.last_jump_position[current_file] = current_pos + + # Jump to midpoint + self.current_cap.set(cv2.CAP_PROP_POS_FRAMES, midpoint) + self.load_current_frame() + + percentage = (midpoint / self.total_frames) * 100 + print(f"Bisected backwards to frame {midpoint} ({percentage:.1f}% through video)") + return True + + def bisect_forwards(self): + """Bisect forwards between current position and next sample point""" + if not self.is_video(self.media_files[self.current_index]): + return False + + current_file = str(self.media_files[self.current_index]) + + # Get next sample point + if not hasattr(self, 'jump_counters') or current_file not in self.jump_counters: + 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 + ] + + current_jump = self.jump_counters[current_file] + + if current_jump >= len(sample_points): + print("All sample points visited. No forward reference point.") + return False + + next_sample = sample_points[current_jump] + next_sample = min(next_sample, self.total_frames - 1) + current_pos = self.current_frame + + # Calculate midpoint between current and next sample + midpoint = (current_pos + next_sample) // 2 + + if midpoint == current_pos: + print("Already at or very close to next sample point.") + return False + + # Update last position for further bisection + self.last_jump_position[current_file] = current_pos + + # Jump to midpoint + self.current_cap.set(cv2.CAP_PROP_POS_FRAMES, midpoint) + self.load_current_frame() + + percentage = (midpoint / self.total_frames) * 100 + print(f"Bisected forwards to frame {midpoint} ({percentage:.1f}% through video)") + return True + def display_current_frame(self): """Display the current cached frame with overlays""" if self.current_display_frame is None: @@ -624,6 +719,8 @@ class MediaGrader: print(" P: Previous file") print(" U: Undo last grading action") print(" L: Sample video at key points (videos only)") + print(" K: Bisect backwards from current position (videos only)") + print(" J: Bisect forwards toward next sample (videos only)") print(" Q/ESC: Quit") cv2.namedWindow("Media Grader", cv2.WINDOW_NORMAL) @@ -681,6 +778,10 @@ class MediaGrader: elif key == ord("l"): # Jump to largest unwatched region self.jump_to_unwatched_region() + elif key == ord("j"): + self.bisect_backwards() + elif key == ord("k"): + self.bisect_forwards() elif key in [ord("1"), ord("2"), ord("3"), ord("4"), ord("5")]: grade = int(chr(key)) if not self.grade_media(grade):