diff --git a/croppa/main.py b/croppa/main.py index 08ee802..65795b2 100644 --- a/croppa/main.py +++ b/croppa/main.py @@ -180,9 +180,32 @@ class VideoEditor: self.cap.release() self.video_path = video_path - self.cap = cv2.VideoCapture(str(self.video_path)) - - if not self.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.cap = None + for backend in backends_to_try: + try: + self.cap = cv2.VideoCapture(str(self.video_path), backend) + if self.cap.isOpened(): + # Optimize buffer settings for better performance + self.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.cap.set(cv2.CAP_PROP_HW_ACCELERATION, cv2.VIDEO_ACCELERATION_ANY) + break + self.cap.release() + except: + continue + + if not self.cap or not self.cap.isOpened(): raise ValueError(f"Could not open video file: {video_path}") # Video properties @@ -191,6 +214,13 @@ class VideoEditor: self.frame_width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH)) self.frame_height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) + # Get codec information for debugging + fourcc = int(self.cap.get(cv2.CAP_PROP_FOURCC)) + codec = "".join([chr((fourcc >> 8 * i) & 0xFF) for i in range(4)]) + + # Get backend information + backend = self.cap.getBackendName() + # Reset playback state for new video self.current_frame = 0 self.is_playing = False @@ -212,6 +242,16 @@ class VideoEditor: print( f"Loaded video: {self.video_path.name} ({self.current_video_index + 1}/{len(self.video_files)})" ) + print(f" Codec: {codec} | Backend: {backend} | Resolution: {self.frame_width}x{self.frame_height}") + print(f" FPS: {self.fps:.2f} | Frames: {self.total_frames} | Duration: {self.total_frames/self.fps:.1f}s") + + # Performance warning for known problematic cases + if codec in ['H264', 'H.264', 'AVC1', 'avc1'] and self.total_frames > 10000: + print(f" Warning: Large H.264 video detected - seeking may be slow") + if self.frame_width * self.frame_height > 1920 * 1080: + print(f" Warning: High resolution video - decoding may be slow") + if self.fps > 60: + print(f" Warning: High framerate video - may impact playback smoothness") def switch_to_video(self, index: int): """Switch to a specific video by index""" @@ -271,15 +311,23 @@ class VideoEditor: self.load_current_frame() def advance_frame(self) -> bool: - """Advance to next frame""" + """Advance to next frame - optimized to avoid seeking""" if not self.is_playing: return True self.current_frame += 1 if self.current_frame >= self.total_frames: - self.current_frame = 0 # Loop + self.current_frame = 0 # Loop - this will require a seek + return self.load_current_frame() - return self.load_current_frame() + # For sequential playback, just read the next frame without seeking + ret, frame = self.cap.read() + if ret: + self.current_display_frame = frame + return True + else: + # If sequential read failed, fall back to seeking (might be end of file or codec issue) + return self.load_current_frame() def apply_crop_zoom_and_rotation(self, frame): """Apply current crop, zoom, rotation, and brightness/contrast settings to frame""" @@ -1245,6 +1293,7 @@ class VideoEditor: self.load_current_frame() while True: + # Only update display if needed self.display_current_frame() delay = self.calculate_frame_delay() if self.is_playing else 30 @@ -1355,7 +1404,6 @@ class VideoEditor: print(f"Contracted crop from left by {self.crop_size_step}px") - # Auto advance frame when playing if self.is_playing: self.advance_frame()