refactor(main.py): optimize multi-segment mode setup and parallelize segment creation for better performance
This commit is contained in:
		
							
								
								
									
										233
									
								
								main.py
									
									
									
									
									
								
							
							
						
						
									
										233
									
								
								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()
 | 
			
		||||
        try:
 | 
			
		||||
            # Clean up existing segment captures
 | 
			
		||||
            print("Cleaning up existing captures...")
 | 
			
		||||
            self.cleanup_segment_captures()
 | 
			
		||||
            
 | 
			
		||||
        current_file = self.media_files[self.current_index]
 | 
			
		||||
            current_file = self.media_files[self.current_index]
 | 
			
		||||
            print(f"Working with file: {current_file}")
 | 
			
		||||
            
 | 
			
		||||
        # Initialize arrays
 | 
			
		||||
        self.segment_caps = [None] * self.segment_count
 | 
			
		||||
        self.segment_frames = [None] * self.segment_count
 | 
			
		||||
        self.segment_positions = []
 | 
			
		||||
            # Initialize arrays
 | 
			
		||||
            print("Initializing 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)
 | 
			
		||||
            # Calculate target positions
 | 
			
		||||
            print("Calculating segment positions...")
 | 
			
		||||
            if self.total_frames <= 1:
 | 
			
		||||
                print("Error: Video has insufficient frames for multi-segment mode")
 | 
			
		||||
                return
 | 
			
		||||
                
 | 
			
		||||
        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
 | 
			
		||||
            
 | 
			
		||||
        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()
 | 
			
		||||
            
 | 
			
		||||
            # 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
 | 
			
		||||
            
 | 
			
		||||
            for i in range(key_frames_to_read):
 | 
			
		||||
                target_frame = self.segment_positions[i * (self.segment_count // key_frames_to_read)]
 | 
			
		||||
                
 | 
			
		||||
                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:
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user