@@ -125,7 +125,9 @@ def get_initial_thumbnails(
125125) -> dict [int , NDArray [np .uint8 ]]:
126126 """Extract first frame from each camera for thumbnail display.
127127
128- Uses same FrameSource mechanism as processing, just reads frame 0.
128+ Opens each video briefly with PyAV to decode the first frame,
129+ then closes immediately. No keyframe scanning or frame index
130+ construction -- much faster than FrameSource for this use case.
129131
130132 Args:
131133 recording_dir: Directory containing cam_N.mp4 files
@@ -134,22 +136,29 @@ def get_initial_thumbnails(
134136 Returns:
135137 Mapping of cam_id -> first frame (BGR image)
136138 """
139+ import av
140+
137141 thumbnails : dict [int , NDArray [np .uint8 ]] = {}
138142
139143 for cam_id in cameras :
140- try :
141- source = FrameSource (recording_dir , cam_id )
142- frame = source .get_frame (0 )
143- source .close ()
144-
145- if frame is not None :
146- thumbnails [cam_id ] = frame
147- else :
148- logger .warning (f"Could not read first frame for cam_id { cam_id } " )
149- except FileNotFoundError :
144+ video_path = recording_dir / f"cam_{ cam_id } .mp4"
145+ if not video_path .exists ():
150146 logger .warning (f"Video file not found for cam_id { cam_id } " )
151- except ValueError as e :
152- logger .warning (f"Error opening video for cam_id { cam_id } : { e } " )
147+ continue
148+
149+ try :
150+ container = av .open (str (video_path ))
151+ try :
152+ stream = container .streams .video [0 ]
153+ for frame in container .decode (stream ):
154+ # bgr24 always produces uint8; PyAV stubs don't narrow the type
155+ arr : NDArray [np .uint8 ] = frame .to_ndarray (format = "bgr24" ) # type: ignore[assignment]
156+ thumbnails [cam_id ] = arr
157+ break
158+ finally :
159+ container .close ()
160+ except Exception as e :
161+ logger .warning (f"Error reading first frame for cam_id { cam_id } : { e } " )
153162
154163 return thumbnails
155164
0 commit comments