@@ -278,15 +278,16 @@ def write(
278278 expected_h , expected_w = self ._frame_size
279279 actual_h , actual_w = frame .shape [:2 ]
280280 if (actual_h , actual_w ) != (expected_h , expected_w ):
281- logger .warning (
282- f"Frame size mismatch: expected (h={ expected_h } , w={ expected_w } ), "
283- f"got (h={ actual_h } , w={ actual_w } ). "
284- "Stopping recorder to prevent encoding errors."
281+ message = (
282+ f"Frame size mismatch for recorder { self ._output .name } : "
283+ f"expected_hw=({ expected_h } , { expected_w } ) "
284+ f"actual_hw=({ actual_h } , { actual_w } ) "
285+ f"{ self ._describe_frame (frame )} . "
286+ "Stopping recorder to prevent FFmpeg pipe errors."
285287 )
286- with self ._stats_lock :
287- self ._encode_error = ValueError (
288- f"Frame size changed from (h={ expected_h } , w={ expected_w } ) to (h={ actual_h } , w={ actual_w } )"
289- )
288+
289+ logger .warning (message )
290+ self ._set_encode_error (message )
290291 self ._process_timing .note_error ()
291292 self ._process_timing .maybe_log ()
292293 return False
@@ -426,9 +427,12 @@ def _writer_loop(self) -> None:
426427 break
427428 continue
428429 except Exception as exc :
429- with self ._stats_lock :
430- self ._encode_error = exc
431- logger .exception ("Could not retrieve item from queue" , exc_info = exc )
430+ message = (
431+ f"Could not retrieve frame from recorder queue for { self ._output .name } : "
432+ f"{ type (exc ).__name__ } : { exc !s} "
433+ )
434+ self ._set_encode_error (message , exc )
435+ logger .exception (message )
432436 self ._stop_event .set ()
433437 break
434438
@@ -473,9 +477,28 @@ def _writer_loop(self) -> None:
473477 self ._frame_timestamps .append (record )
474478
475479 except Exception as exc :
480+ queue_size = q .qsize () if q is not None else - 1
481+
476482 with self ._stats_lock :
477- self ._encode_error = exc
478- logger .exception ("Video encoding failed while writing frame" , exc_info = exc )
483+ frames_enqueued = self ._frames_enqueued
484+ frames_written = self ._frames_written
485+ dropped_frames = self ._dropped_frames
486+
487+ message = (
488+ f"Video encoding failed for recorder { self ._output .name } : "
489+ f"{ type (exc ).__name__ } : { exc !s} . "
490+ f"{ self ._describe_frame (frame )} "
491+ f"expected_frame_size={ self ._frame_size } "
492+ f"frames_written={ frames_written } "
493+ f"frames_enqueued={ frames_enqueued } "
494+ f"dropped={ dropped_frames } "
495+ f"queue_size={ queue_size } . "
496+ "The FFmpeg/WriteGear pipe is no longer usable; stopping this recorder."
497+ )
498+
499+ self ._set_encode_error (message , exc )
500+
501+ logger .exception (message )
479502 self ._stop_event .set ()
480503 self ._writer_timing .note_error ()
481504 self ._writer_timing .maybe_log ()
@@ -524,10 +547,34 @@ def _compute_write_fps_locked(self) -> float:
524547 return 0.0
525548 return (len (self ._written_times ) - 1 ) / duration
526549
550+ def _describe_frame (self , frame : np .ndarray | None ) -> str :
551+ if frame is None :
552+ return "frame=None"
553+
554+ try :
555+ return (
556+ f"shape={ frame .shape } "
557+ f"dtype={ frame .dtype } "
558+ f"contiguous={ frame .flags .c_contiguous } "
559+ f"nbytes={ frame .nbytes / (1024 * 1024 ):.2f} MB"
560+ )
561+ except Exception :
562+ return f"frame=<uninspectable type={ type (frame )!r} >"
563+
527564 def _current_error (self ) -> Exception | None :
528565 with self ._stats_lock :
529566 return self ._encode_error
530567
568+ def _set_encode_error (self , message : str , exc : Exception | None = None ) -> Exception :
569+ error = RuntimeError (message )
570+ if exc is not None :
571+ error .__cause__ = exc
572+
573+ with self ._stats_lock :
574+ self ._encode_error = error
575+
576+ return error
577+
531578 def _save_timestamps (self ) -> None :
532579 """Save frame timestamps to a JSON file alongside the video."""
533580 if not self ._frame_timestamps :
0 commit comments