Skip to content

Commit 790aeef

Browse files
committed
Improve recorder error logging and handling
Enhance error reporting and handling for video recording. recording_manager now logs exception type, message, and frame shape/dtype when a write fails. VideoRecorder adds detailed messages for frame-size mismatches, queue retrieval errors, and encoding failures (including frame description, expected size, frames_written/frames_enqueued/dropped, and queue_size) and stops the recorder to avoid FFmpeg pipe errors. Introduced _describe_frame to summarize frames and _set_encode_error to centralize creation of a RuntimeError (preserving original exception as __cause__) and set _encode_error under the stats lock. Minor test file newline fix.
1 parent 2006142 commit 790aeef

2 files changed

Lines changed: 68 additions & 14 deletions

File tree

dlclivegui/gui/recording_manager.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,14 @@ def write_frame(self, cam_id: str, frame: np.ndarray, timestamp: float | None =
209209
try:
210210
rec.write(frame, timestamp=timestamp if timestamp is not None else time.time())
211211
except Exception as exc:
212-
log.warning("Failed to write frame for %s: %s", cam_id, exc)
212+
log.warning(
213+
"Failed to write frame for %s: %s: %s frame_shape=%s dtype=%s",
214+
cam_id,
215+
type(exc).__name__,
216+
str(exc) or repr(exc),
217+
getattr(frame, "shape", None),
218+
getattr(frame, "dtype", None),
219+
)
213220
try:
214221
rec.stop()
215222
except Exception:

dlclivegui/services/video_recorder.py

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)