Skip to content

Commit a800c08

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 c3f8854 commit a800c08

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
@@ -215,7 +215,14 @@ def write_frame(
215215
timestamp_metadata=timestamp_metadata,
216216
)
217217
except Exception as exc:
218-
log.warning("Failed to write frame for %s: %s", cam_id, exc)
218+
log.warning(
219+
"Failed to write frame for %s: %s: %s frame_shape=%s dtype=%s",
220+
cam_id,
221+
type(exc).__name__,
222+
str(exc) or repr(exc),
223+
getattr(frame, "shape", None),
224+
getattr(frame, "dtype", None),
225+
)
219226
try:
220227
rec.stop()
221228
except Exception:

dlclivegui/services/video_recorder.py

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

Comments
 (0)