diff --git a/main.py b/main.py index 65b3bfc..ce98732 100644 --- a/main.py +++ b/main.py @@ -203,13 +203,13 @@ class MediaGrader: if self.is_video(file_path): # Try different backends for better performance - # Order of preference: DirectShow (Windows), FFmpeg, any available + # For video files: FFmpeg is usually best, DirectShow is for cameras backends_to_try = [] - if hasattr(cv2, 'CAP_DSHOW'): # Windows DirectShow - backends_to_try.append(cv2.CAP_DSHOW) - if hasattr(cv2, 'CAP_FFMPEG'): # FFmpeg + if hasattr(cv2, 'CAP_FFMPEG'): # FFmpeg - best for video files backends_to_try.append(cv2.CAP_FFMPEG) - backends_to_try.append(cv2.CAP_ANY) # Fallback + if hasattr(cv2, 'CAP_DSHOW'): # DirectShow - usually for cameras, but try as fallback + backends_to_try.append(cv2.CAP_DSHOW) + backends_to_try.append(cv2.CAP_ANY) # Final fallback self.current_cap = None for backend in backends_to_try: @@ -488,13 +488,22 @@ class MediaGrader: self.multi_segment_mode = not self.multi_segment_mode if self.multi_segment_mode: - print(f"Enabled multi-segment mode ({self.segment_count} segments)") - self.setup_segment_captures() + print(f"Enabling multi-segment mode ({self.segment_count} segments)...") + try: + self.setup_segment_captures() + print("Multi-segment mode enabled successfully") + except Exception as e: + print(f"Failed to setup multi-segment mode: {e}") + import traceback + traceback.print_exc() + self.multi_segment_mode = False + return False else: - print("Disabled multi-segment mode") + print("Disabling multi-segment mode...") self.cleanup_segment_captures() # Reload single video self.load_media(self.media_files[self.current_index]) + print("Multi-segment mode disabled") return True @@ -509,111 +518,123 @@ class MediaGrader: return start_time = time.time() - print(f"Setting up {self.segment_count} segments...") + print(f"Setting up {self.segment_count} segments in parallel...") - # Clean up existing segment captures - self.cleanup_segment_captures() - - current_file = self.media_files[self.current_index] - - # Initialize arrays - self.segment_caps = [None] * self.segment_count - self.segment_frames = [None] * self.segment_count - self.segment_positions = [] - - # Calculate target positions - for i in range(self.segment_count): - position_ratio = i / max(1, self.segment_count - 1) - start_frame = int(position_ratio * (self.total_frames - 1)) - self.segment_positions.append(start_frame) - - load_start = time.time() - - shared_cap_start = time.time() - - # Use same backend optimization as main capture - backends_to_try = [] - if hasattr(cv2, 'CAP_DSHOW'): # Windows DirectShow - backends_to_try.append(cv2.CAP_DSHOW) - if hasattr(cv2, 'CAP_FFMPEG'): # FFmpeg - backends_to_try.append(cv2.CAP_FFMPEG) - backends_to_try.append(cv2.CAP_ANY) # Fallback - - shared_cap = None - for backend in backends_to_try: - try: - shared_cap = cv2.VideoCapture(str(current_file), backend) - if shared_cap.isOpened(): - shared_cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) - break - shared_cap.release() - except: - continue - - if not shared_cap: - shared_cap = cv2.VideoCapture(str(current_file)) # Fallback + try: + # Clean up existing segment captures + print("Cleaning up existing captures...") + self.cleanup_segment_captures() - shared_cap_create_time = (time.time() - shared_cap_start) * 1000 - print(f"Capture creation: {shared_cap_create_time:.1f}ms") - - if shared_cap.isOpened(): - frames_start = time.time() + current_file = self.media_files[self.current_index] + print(f"Working with file: {current_file}") - # Strategy: Read a much smaller subset and interpolate/approximate - # Only read 4-6 key frames and generate the rest through approximation - key_frames_to_read = min(6, self.segment_count) - frames_read = 0 + # Initialize arrays + print("Initializing arrays...") + self.segment_caps = [None] * self.segment_count + self.segment_frames = [None] * self.segment_count + self.segment_positions = [] - for i in range(key_frames_to_read): - target_frame = self.segment_positions[i * (self.segment_count // key_frames_to_read)] + # Calculate target positions + print("Calculating segment positions...") + if self.total_frames <= 1: + print("Error: Video has insufficient frames for multi-segment mode") + return - seek_start = time.time() - shared_cap.set(cv2.CAP_PROP_POS_FRAMES, target_frame) - seek_time = (time.time() - seek_start) * 1000 - - read_start = time.time() - ret, frame = shared_cap.read() - read_time = (time.time() - read_start) * 1000 - - if ret: - # Use this frame for multiple segments (approximation) - segments_per_key = self.segment_count // key_frames_to_read - start_seg = i * segments_per_key - end_seg = min(start_seg + segments_per_key, self.segment_count) - - for seg_idx in range(start_seg, end_seg): - self.segment_frames[seg_idx] = frame.copy() - - frames_read += 1 - print(f"Key frame {i}: Frame {target_frame} -> Segments {start_seg}-{end_seg-1} ({seek_time:.1f}ms + {read_time:.1f}ms)") + for i in range(self.segment_count): + if self.segment_count <= 1: + position_ratio = 0 else: - print(f"Failed to read key frame {i} at position {target_frame}") + position_ratio = i / (self.segment_count - 1) + start_frame = int(position_ratio * (self.total_frames - 1)) + start_frame = max(0, min(start_frame, self.total_frames - 1)) # Clamp to valid range + self.segment_positions.append(start_frame) + print(f"Segment positions: {self.segment_positions}") - # Fill any remaining segments with the last valid frame - last_valid_frame = None - for frame in self.segment_frames: - if frame is not None: - last_valid_frame = frame - break + # Create segments in parallel using thread pool + print("Creating segments in parallel...") + parallel_start = time.time() - if last_valid_frame is not None: - for i in range(len(self.segment_frames)): - if self.segment_frames[i] is None: - self.segment_frames[i] = last_valid_frame.copy() + # 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) - frames_time = (time.time() - frames_start) * 1000 - print(f"Smart frame reading: {frames_time:.1f}ms ({frames_read} key frames for {self.segment_count} segments)") + # 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") + else: + print(f"Segment {i} failed to load") + except Exception as e: + print(f"Segment {i} failed with error: {e}") - shared_cap.release() - else: - print("Failed to create shared capture!") + parallel_time = (time.time() - parallel_start) * 1000 + print(f"Parallel segment creation: {parallel_time:.1f}ms") + + except Exception as e: + print(f"Error in setup initialization: {e}") + import traceback + traceback.print_exc() + return total_time = time.time() - start_time print(f"Total setup time: {total_time * 1000:.1f}ms") # Report success - successful_segments = sum(1 for frame in self.segment_frames if frame is not None) - print(f"Successfully approximated {successful_segments}/{self.segment_count} segments") + print(f"Successfully loaded {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)""" + try: + # Create optimized capture for this segment + cap = None + backends_to_try = [] + if hasattr(cv2, 'CAP_FFMPEG'): + backends_to_try.append(cv2.CAP_FFMPEG) + if hasattr(cv2, 'CAP_DSHOW'): + backends_to_try.append(cv2.CAP_DSHOW) + backends_to_try.append(cv2.CAP_ANY) + + for backend in backends_to_try: + try: + cap = cv2.VideoCapture(file_path, backend) + if cap.isOpened(): + cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) + break + cap.release() + except: + continue + + if not cap or not cap.isOpened(): + cap = cv2.VideoCapture(file_path) # Fallback + + if not cap.isOpened(): + return None, None + + # Seek to start frame and read initial frame + cap.set(cv2.CAP_PROP_POS_FRAMES, start_frame) + ret, frame = cap.read() + + if ret and frame is not None: + # Reset to start position for future reads + cap.set(cv2.CAP_PROP_POS_FRAMES, start_frame) + return cap, frame.copy() + else: + cap.release() + return None, None + + except Exception as e: + print(f"Error creating segment {segment_index}: {e}") + if cap: + cap.release() + return None, None def cleanup_segment_captures(self): """Clean up all segment video captures""" @@ -642,11 +663,11 @@ class MediaGrader: # Use optimized backend for temporary capture too temp_cap = None backends_to_try = [] - if hasattr(cv2, 'CAP_DSHOW'): - backends_to_try.append(cv2.CAP_DSHOW) - if hasattr(cv2, 'CAP_FFMPEG'): + if hasattr(cv2, 'CAP_FFMPEG'): # FFmpeg - best for video files backends_to_try.append(cv2.CAP_FFMPEG) - backends_to_try.append(cv2.CAP_ANY) + if hasattr(cv2, 'CAP_DSHOW'): # DirectShow - fallback + backends_to_try.append(cv2.CAP_DSHOW) + backends_to_try.append(cv2.CAP_ANY) # Final fallback for backend in backends_to_try: try: @@ -690,11 +711,11 @@ class MediaGrader: # Use optimized backend for segment capture cap = None backends_to_try = [] - if hasattr(cv2, 'CAP_DSHOW'): - backends_to_try.append(cv2.CAP_DSHOW) - if hasattr(cv2, 'CAP_FFMPEG'): + if hasattr(cv2, 'CAP_FFMPEG'): # FFmpeg - best for video files backends_to_try.append(cv2.CAP_FFMPEG) - backends_to_try.append(cv2.CAP_ANY) + if hasattr(cv2, 'CAP_DSHOW'): # DirectShow - fallback + backends_to_try.append(cv2.CAP_DSHOW) + backends_to_try.append(cv2.CAP_ANY) # Final fallback for backend in backends_to_try: try: