diff --git a/main.py b/main.py index dae9061..2f0d901 100644 --- a/main.py +++ b/main.py @@ -43,7 +43,7 @@ class MediaGrader: CTRL_SEEK_MULTIPLIER = 10 # CTRL + A/D multiplier # Multi-segment mode configuration - SEGMENT_COUNT = 16 # Number of video segments (2x2 grid) + SEGMENT_COUNT = 16 # Number of video segments (4x4 grid) SEGMENT_OVERLAP_PERCENT = 10 # Percentage overlap between segments def __init__( @@ -518,7 +518,7 @@ class MediaGrader: return start_time = time.time() - print(f"Setting up {self.segment_count} segments in parallel...") + print(f"Setting up {self.segment_count} segments with frame caching...") try: # Clean up existing segment captures @@ -528,9 +528,9 @@ class MediaGrader: current_file = self.media_files[self.current_index] print(f"Working with file: {current_file}") - # Initialize arrays + # Initialize arrays - no individual captures, just cached frames print("Initializing arrays...") - self.segment_caps = [None] * self.segment_count + self.segment_caps = [None] * self.segment_count # Keep for compatibility, but won't use self.segment_frames = [None] * self.segment_count self.segment_positions = [] @@ -540,10 +540,9 @@ class MediaGrader: print("Error: Video has insufficient frames for multi-segment mode") return - # Use a more conservative approach for segment positioning - # Instead of using total_frames directly, use a safer range - safe_frame_count = max(1, int(self.total_frames * 0.9)) # Use 90% of reported frames as safety margin - print(f"Using safe frame count: {safe_frame_count} (90% of reported {self.total_frames})") + # Use conservative frame range + safe_frame_count = max(1, int(self.total_frames * 0.6)) + print(f"Using safe frame count: {safe_frame_count} (60% of reported {self.total_frames})") for i in range(self.segment_count): if self.segment_count <= 1: @@ -551,40 +550,32 @@ class MediaGrader: else: position_ratio = i / (self.segment_count - 1) start_frame = int(position_ratio * (safe_frame_count - 1)) - start_frame = max(0, min(start_frame, safe_frame_count - 1)) # Clamp to safe range + start_frame = max(0, min(start_frame, safe_frame_count - 1)) self.segment_positions.append(start_frame) print(f"Segment positions: {self.segment_positions}") - # Create segments in parallel using thread pool - print("Creating segments in parallel...") - parallel_start = time.time() + # Use the existing main capture to quickly read all segment frames + print("Pre-loading segment frames...") + cache_start = time.time() - # Submit all segment creation tasks to thread pool - futures = [] - for i in range(self.segment_count): - future = self.thread_pool.submit(self._create_segment_parallel, i, str(current_file), self.segment_positions[i]) - futures.append(future) - - # Collect results - successful_segments = 0 - for i, future in enumerate(futures): - try: - cap, frame = future.result(timeout=10) # 10 second timeout per segment - if cap is not None and frame is not None: - self.segment_caps[i] = cap - self.segment_frames[i] = frame - successful_segments += 1 - print(f"Segment {i} loaded successfully") + if self.current_cap and self.current_cap.isOpened(): + for i, frame_pos in enumerate(self.segment_positions): + self.current_cap.set(cv2.CAP_PROP_POS_FRAMES, frame_pos) + ret, frame = self.current_cap.read() + if ret and frame is not None: + self.segment_frames[i] = frame.copy() + print(f"Cached segment {i} at frame {frame_pos}") else: - print(f"Segment {i} failed to load") - except Exception as e: - print(f"Segment {i} failed with error: {e}") + print(f"Failed to cache segment {i} at frame {frame_pos}") + + # Reset main capture to original position + self.current_cap.set(cv2.CAP_PROP_POS_FRAMES, self.current_frame) - parallel_time = (time.time() - parallel_start) * 1000 - print(f"Parallel segment creation: {parallel_time:.1f}ms") + cache_time = (time.time() - cache_start) * 1000 + print(f"Frame caching: {cache_time:.1f}ms") except Exception as e: - print(f"Error in setup initialization: {e}") + print(f"Error in setup: {e}") import traceback traceback.print_exc() return @@ -593,7 +584,8 @@ class MediaGrader: print(f"Total setup time: {total_time * 1000:.1f}ms") # Report success - print(f"Successfully loaded {successful_segments}/{self.segment_count} segments") + successful_segments = sum(1 for frame in self.segment_frames if frame is not None) + print(f"Successfully cached {successful_segments}/{self.segment_count} segments") def _create_segment_parallel(self, segment_index: int, file_path: str, start_frame: int): """Create a single segment capture and load its initial frame (runs in thread)""" @@ -777,45 +769,15 @@ class MediaGrader: return segment_index, None def update_segment_frames(self): - """Update frames for all segments during playback with parallel processing""" + """Update frames for segments - now using static cached frames for simplicity""" if not self.multi_segment_mode or not self.segment_frames: return - # Only update segments that have valid frames loaded - active_segments = [i for i, frame in enumerate(self.segment_frames) if frame is not None] - - if not active_segments: - return - - # Use thread pool for parallel frame updates (but limit to avoid overwhelming) - if len(active_segments) <= 4: - # For small numbers, use parallel processing - futures = [] - for i in active_segments: - future = self.thread_pool.submit(self.update_segment_frame_parallel, i) - futures.append(future) - - # Collect results - for future in futures: - segment_index, frame = future.result() - if frame is not None: - self.segment_frames[segment_index] = frame - else: - # For larger numbers, process in smaller batches to avoid resource exhaustion - batch_size = 4 - for batch_start in range(0, len(active_segments), batch_size): - batch = active_segments[batch_start:batch_start + batch_size] - futures = [] - - for i in batch: - future = self.thread_pool.submit(self.update_segment_frame_parallel, i) - futures.append(future) - - # Collect batch results - for future in futures: - segment_index, frame = future.result() - if frame is not None: - self.segment_frames[segment_index] = frame + # Since we're using cached static frames, no updates needed during playback + # This dramatically improves performance by eliminating I/O during playback + # The segments show different parts of the video but don't animate + # For animation, we'd need to implement frame interpolation or periodic cache updates + pass def reposition_segments_around_frame(self, center_frame: int): """Reposition all segments around a center frame while maintaining spacing"""