diff --git a/main.py b/main.py index 2f73cac..65b3bfc 100644 --- a/main.py +++ b/main.py @@ -202,13 +202,44 @@ class MediaGrader: self.current_cap.release() if self.is_video(file_path): - # Suppress OpenCV error messages for unsupported codecs - self.current_cap = cv2.VideoCapture(str(file_path)) - if not self.current_cap.isOpened(): + # Try different backends for better performance + # Order of preference: DirectShow (Windows), FFmpeg, any available + 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 + + self.current_cap = None + for backend in backends_to_try: + try: + self.current_cap = cv2.VideoCapture(str(file_path), backend) + if self.current_cap.isOpened(): + # Optimize buffer settings for better performance + self.current_cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) # Minimize buffer to reduce latency + # Try to set hardware acceleration if available + if hasattr(cv2, 'CAP_PROP_HW_ACCELERATION'): + self.current_cap.set(cv2.CAP_PROP_HW_ACCELERATION, cv2.VIDEO_ACCELERATION_ANY) + break + self.current_cap.release() + except: + continue + + if not self.current_cap or not self.current_cap.isOpened(): print(f"Warning: Could not open video file {file_path.name} (unsupported codec)") return False + self.total_frames = int(self.current_cap.get(cv2.CAP_PROP_FRAME_COUNT)) self.current_frame = 0 + + # Get codec information for debugging + fourcc = int(self.current_cap.get(cv2.CAP_PROP_FOURCC)) + codec = "".join([chr((fourcc >> 8 * i) & 0xFF) for i in range(4)]) + backend = self.current_cap.getBackendName() + + print(f"Loaded: {file_path.name} | Codec: {codec} | Backend: {backend} | Frames: {self.total_frames}") + else: self.current_cap = None self.total_frames = 1 @@ -499,7 +530,29 @@ class MediaGrader: load_start = time.time() shared_cap_start = time.time() - shared_cap = cv2.VideoCapture(str(current_file)) + + # 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 + shared_cap_create_time = (time.time() - shared_cap_start) * 1000 print(f"Capture creation: {shared_cap_create_time:.1f}ms") @@ -585,7 +638,29 @@ class MediaGrader: if self.current_cap: # Create a temporary capture to avoid interfering with main playback current_file = self.media_files[self.current_index] - temp_cap = cv2.VideoCapture(str(current_file)) + + # 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'): + backends_to_try.append(cv2.CAP_FFMPEG) + backends_to_try.append(cv2.CAP_ANY) + + for backend in backends_to_try: + try: + temp_cap = cv2.VideoCapture(str(current_file), backend) + if temp_cap.isOpened(): + temp_cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) + break + temp_cap.release() + except: + continue + + if not temp_cap: + temp_cap = cv2.VideoCapture(str(current_file)) # Fallback + if temp_cap.isOpened(): temp_cap.set(cv2.CAP_PROP_POS_FRAMES, frame_number) ret, frame = temp_cap.read() @@ -609,9 +684,31 @@ class MediaGrader: """Get or create a capture for a specific segment (lazy loading)""" if segment_index >= len(self.segment_caps) or self.segment_caps[segment_index] is None: if segment_index < len(self.segment_caps): - # Create capture on demand + # Create capture on demand with optimized backend current_file = self.media_files[self.current_index] - cap = cv2.VideoCapture(str(current_file)) + + # 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'): + backends_to_try.append(cv2.CAP_FFMPEG) + backends_to_try.append(cv2.CAP_ANY) + + for backend in backends_to_try: + try: + cap = cv2.VideoCapture(str(current_file), backend) + if cap.isOpened(): + cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) + break + cap.release() + except: + continue + + if not cap: + cap = cv2.VideoCapture(str(current_file)) # Fallback + if cap.isOpened(): cap.set(cv2.CAP_PROP_POS_FRAMES, self.segment_positions[segment_index]) self.segment_caps[segment_index] = cap @@ -1060,6 +1157,11 @@ class MediaGrader: for _ in range(frames_to_skip + 1): ret, frame = self.current_cap.read() if not ret: + # Hit actual end of video - check if frame count was wrong + actual_frame = int(self.current_cap.get(cv2.CAP_PROP_POS_FRAMES)) + if actual_frame < self.total_frames - 5: # Allow some tolerance + print(f"Frame count mismatch! Reported: {self.total_frames}, Actual: {actual_frame}") + self.total_frames = actual_frame return False self.current_display_frame = frame