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):
|
if self.is_video(file_path):
|
||||||
# Try different backends for better performance
|
# 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 = []
|
backends_to_try = []
|
||||||
if hasattr(cv2, 'CAP_DSHOW'): # Windows DirectShow
|
if hasattr(cv2, 'CAP_FFMPEG'): # FFmpeg - best for video files
|
||||||
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_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
|
self.current_cap = None
|
||||||
for backend in backends_to_try:
|
for backend in backends_to_try:
|
||||||
@@ -488,13 +488,22 @@ class MediaGrader:
|
|||||||
self.multi_segment_mode = not self.multi_segment_mode
|
self.multi_segment_mode = not self.multi_segment_mode
|
||||||
|
|
||||||
if self.multi_segment_mode:
|
if self.multi_segment_mode:
|
||||||
print(f"Enabled multi-segment mode ({self.segment_count} segments)")
|
print(f"Enabling multi-segment mode ({self.segment_count} segments)...")
|
||||||
self.setup_segment_captures()
|
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:
|
else:
|
||||||
print("Disabled multi-segment mode")
|
print("Disabling multi-segment mode...")
|
||||||
self.cleanup_segment_captures()
|
self.cleanup_segment_captures()
|
||||||
# Reload single video
|
# Reload single video
|
||||||
self.load_media(self.media_files[self.current_index])
|
self.load_media(self.media_files[self.current_index])
|
||||||
|
print("Multi-segment mode disabled")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -509,111 +518,123 @@ class MediaGrader:
|
|||||||
return
|
return
|
||||||
|
|
||||||
start_time = time.time()
|
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
|
try:
|
||||||
self.cleanup_segment_captures()
|
# Clean up existing segment captures
|
||||||
|
print("Cleaning up existing captures...")
|
||||||
current_file = self.media_files[self.current_index]
|
self.cleanup_segment_captures()
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
shared_cap_create_time = (time.time() - shared_cap_start) * 1000
|
current_file = self.media_files[self.current_index]
|
||||||
print(f"Capture creation: {shared_cap_create_time:.1f}ms")
|
print(f"Working with file: {current_file}")
|
||||||
|
|
||||||
if shared_cap.isOpened():
|
|
||||||
frames_start = time.time()
|
|
||||||
|
|
||||||
# Strategy: Read a much smaller subset and interpolate/approximate
|
# Initialize arrays
|
||||||
# Only read 4-6 key frames and generate the rest through approximation
|
print("Initializing arrays...")
|
||||||
key_frames_to_read = min(6, self.segment_count)
|
self.segment_caps = [None] * self.segment_count
|
||||||
frames_read = 0
|
self.segment_frames = [None] * self.segment_count
|
||||||
|
self.segment_positions = []
|
||||||
|
|
||||||
for i in range(key_frames_to_read):
|
# Calculate target positions
|
||||||
target_frame = self.segment_positions[i * (self.segment_count // key_frames_to_read)]
|
print("Calculating segment positions...")
|
||||||
|
if self.total_frames <= 1:
|
||||||
|
print("Error: Video has insufficient frames for multi-segment mode")
|
||||||
|
return
|
||||||
|
|
||||||
seek_start = time.time()
|
for i in range(self.segment_count):
|
||||||
shared_cap.set(cv2.CAP_PROP_POS_FRAMES, target_frame)
|
if self.segment_count <= 1:
|
||||||
seek_time = (time.time() - seek_start) * 1000
|
position_ratio = 0
|
||||||
|
|
||||||
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)")
|
|
||||||
else:
|
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
|
# Create segments in parallel using thread pool
|
||||||
last_valid_frame = None
|
print("Creating segments in parallel...")
|
||||||
for frame in self.segment_frames:
|
parallel_start = time.time()
|
||||||
if frame is not None:
|
|
||||||
last_valid_frame = frame
|
|
||||||
break
|
|
||||||
|
|
||||||
if last_valid_frame is not None:
|
# Submit all segment creation tasks to thread pool
|
||||||
for i in range(len(self.segment_frames)):
|
futures = []
|
||||||
if self.segment_frames[i] is None:
|
for i in range(self.segment_count):
|
||||||
self.segment_frames[i] = last_valid_frame.copy()
|
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
|
# Collect results
|
||||||
print(f"Smart frame reading: {frames_time:.1f}ms ({frames_read} key frames for {self.segment_count} segments)")
|
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()
|
parallel_time = (time.time() - parallel_start) * 1000
|
||||||
else:
|
print(f"Parallel segment creation: {parallel_time:.1f}ms")
|
||||||
print("Failed to create shared capture!")
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error in setup initialization: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
return
|
||||||
|
|
||||||
total_time = time.time() - start_time
|
total_time = time.time() - start_time
|
||||||
print(f"Total setup time: {total_time * 1000:.1f}ms")
|
print(f"Total setup time: {total_time * 1000:.1f}ms")
|
||||||
|
|
||||||
# Report success
|
# Report success
|
||||||
successful_segments = sum(1 for frame in self.segment_frames if frame is not None)
|
print(f"Successfully loaded {successful_segments}/{self.segment_count} segments")
|
||||||
print(f"Successfully approximated {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):
|
def cleanup_segment_captures(self):
|
||||||
"""Clean up all segment video captures"""
|
"""Clean up all segment video captures"""
|
||||||
@@ -642,11 +663,11 @@ class MediaGrader:
|
|||||||
# Use optimized backend for temporary capture too
|
# Use optimized backend for temporary capture too
|
||||||
temp_cap = None
|
temp_cap = None
|
||||||
backends_to_try = []
|
backends_to_try = []
|
||||||
if hasattr(cv2, 'CAP_DSHOW'):
|
if hasattr(cv2, 'CAP_FFMPEG'): # FFmpeg - best for video files
|
||||||
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_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:
|
for backend in backends_to_try:
|
||||||
try:
|
try:
|
||||||
@@ -690,11 +711,11 @@ class MediaGrader:
|
|||||||
# Use optimized backend for segment capture
|
# Use optimized backend for segment capture
|
||||||
cap = None
|
cap = None
|
||||||
backends_to_try = []
|
backends_to_try = []
|
||||||
if hasattr(cv2, 'CAP_DSHOW'):
|
if hasattr(cv2, 'CAP_FFMPEG'): # FFmpeg - best for video files
|
||||||
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_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:
|
for backend in backends_to_try:
|
||||||
try:
|
try:
|
||||||
|
Reference in New Issue
Block a user