@@ -274,15 +274,16 @@ def write(self, frame: np.ndarray, timestamp: float | None = None) -> bool:
274274 expected_h , expected_w = self ._frame_size
275275 actual_h , actual_w = frame .shape [:2 ]
276276 if (actual_h , actual_w ) != (expected_h , expected_w ):
277- logger .warning (
278- f"Frame size mismatch: expected (h={ expected_h } , w={ expected_w } ), "
279- f"got (h={ actual_h } , w={ actual_w } ). "
280- "Stopping recorder to prevent encoding errors."
277+ message = (
278+ f"Frame size mismatch for recorder { self ._output .name } : "
279+ f"expected_hw=({ expected_h } , { expected_w } ) "
280+ f"actual_hw=({ actual_h } , { actual_w } ) "
281+ f"{ self ._describe_frame (frame )} . "
282+ "Stopping recorder to prevent FFmpeg pipe errors."
281283 )
282- with self ._stats_lock :
283- self ._encode_error = ValueError (
284- f"Frame size changed from (h={ expected_h } , w={ expected_w } ) to (h={ actual_h } , w={ actual_w } )"
285- )
284+
285+ logger .warning (message )
286+ self ._set_encode_error (message )
286287 self ._process_timing .note_error ()
287288 self ._process_timing .maybe_log ()
288289 return False
@@ -422,9 +423,12 @@ def _writer_loop(self) -> None:
422423 break
423424 continue
424425 except Exception as exc :
425- with self ._stats_lock :
426- self ._encode_error = exc
427- logger .exception ("Could not retrieve item from queue" , exc_info = exc )
426+ message = (
427+ f"Could not retrieve frame from recorder queue for { self ._output .name } : "
428+ f"{ type (exc ).__name__ } : { exc !s} "
429+ )
430+ self ._set_encode_error (message , exc )
431+ logger .exception (message )
428432 self ._stop_event .set ()
429433 break
430434
@@ -444,9 +448,28 @@ def _writer_loop(self) -> None:
444448 writer .write (frame )
445449
446450 except Exception as exc :
451+ queue_size = q .qsize () if q is not None else - 1
452+
447453 with self ._stats_lock :
448- self ._encode_error = exc
449- logger .exception ("Video encoding failed while writing frame" , exc_info = exc )
454+ frames_enqueued = self ._frames_enqueued
455+ frames_written = self ._frames_written
456+ dropped_frames = self ._dropped_frames
457+
458+ message = (
459+ f"Video encoding failed for recorder { self ._output .name } : "
460+ f"{ type (exc ).__name__ } : { exc !s} . "
461+ f"{ self ._describe_frame (frame )} "
462+ f"expected_frame_size={ self ._frame_size } "
463+ f"frames_written={ frames_written } "
464+ f"frames_enqueued={ frames_enqueued } "
465+ f"dropped={ dropped_frames } "
466+ f"queue_size={ queue_size } . "
467+ "The FFmpeg/WriteGear pipe is no longer usable; stopping this recorder."
468+ )
469+
470+ self ._set_encode_error (message , exc )
471+
472+ logger .exception (message )
450473 self ._stop_event .set ()
451474 self ._writer_timing .note_error ()
452475 self ._writer_timing .maybe_log ()
@@ -496,10 +519,34 @@ def _compute_write_fps_locked(self) -> float:
496519 return 0.0
497520 return (len (self ._written_times ) - 1 ) / duration
498521
522+ def _describe_frame (self , frame : np .ndarray | None ) -> str :
523+ if frame is None :
524+ return "frame=None"
525+
526+ try :
527+ return (
528+ f"shape={ frame .shape } "
529+ f"dtype={ frame .dtype } "
530+ f"contiguous={ frame .flags .c_contiguous } "
531+ f"nbytes={ frame .nbytes / (1024 * 1024 ):.2f} MB"
532+ )
533+ except Exception :
534+ return f"frame=<uninspectable type={ type (frame )!r} >"
535+
499536 def _current_error (self ) -> Exception | None :
500537 with self ._stats_lock :
501538 return self ._encode_error
502539
540+ def _set_encode_error (self , message : str , exc : Exception | None = None ) -> Exception :
541+ error = RuntimeError (message )
542+ if exc is not None :
543+ error .__cause__ = exc
544+
545+ with self ._stats_lock :
546+ self ._encode_error = error
547+
548+ return error
549+
503550 def _save_timestamps (self ) -> None :
504551 """Save frame timestamps to a JSON file alongside the video."""
505552 if not self ._frame_timestamps :
0 commit comments