From bd772ae4b2979e0bb6c05e0a6d6c81e5b5f34075 Mon Sep 17 00:00:00 2001 From: Luis Miranda <161006+luuuis@users.noreply.github.com> Date: Tue, 28 Apr 2026 13:09:57 +0100 Subject: [PATCH] fix: correct invalid segment topic matching in CameraWatchdog `topic.endswith("valid")` also matched `"recordings/invalid"`, routing invalid segment messages to the valid branch. This kept `latest_invalid_segment_time` permanently at 0, preventing the stale recording restart logic from ever triggering on a stream of corrupt segments. Fix by anchoring the suffix with a leading slash so each enum value matches exactly one topic type. Co-authored-by: Claude --- frigate/test/test_camera_watchdog.py | 16 +++++++++++++++- frigate/video/ffmpeg.py | 6 +++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/frigate/test/test_camera_watchdog.py b/frigate/test/test_camera_watchdog.py index ad05e155f1..dd064a3608 100644 --- a/frigate/test/test_camera_watchdog.py +++ b/frigate/test/test_camera_watchdog.py @@ -89,16 +89,30 @@ class TestProcessSegmentUpdates(unittest.TestCase): self.assertEqual(watchdog.latest_invalid_segment_time, 0.0) self.assertEqual(watchdog.latest_cache_segment_time, 0.0) + def test_out_of_order_invalid_segments(self): + """Out-of-order invalid segments still result in the maximum timestamp.""" + watchdog = _make_watchdog() + watchdog.segment_subscriber.check_for_update.side_effect = [ + _msg(RecordingsDataTypeEnum.invalid, "front_door", 5.0), + _msg(RecordingsDataTypeEnum.invalid, "front_door", 15.0), + _msg(RecordingsDataTypeEnum.invalid, "front_door", 8.0), + (None, None), + ] + watchdog._drain_segment_updates() + self.assertEqual(watchdog.latest_invalid_segment_time, 15.0) + def test_mixed_types_tracked_independently(self): - """Valid and cache segment times are each tracked independently.""" + """Valid, invalid, and cache segment times are each tracked independently.""" watchdog = _make_watchdog() watchdog.segment_subscriber.check_for_update.side_effect = [ _msg(RecordingsDataTypeEnum.valid, "front_door", 10.0), + _msg(RecordingsDataTypeEnum.invalid, "front_door", 20.0), _msg(RecordingsDataTypeEnum.latest, "front_door", 30.0), (None, None), ] watchdog._drain_segment_updates() self.assertEqual(watchdog.latest_valid_segment_time, 10.0) + self.assertEqual(watchdog.latest_invalid_segment_time, 20.0) self.assertEqual(watchdog.latest_cache_segment_time, 30.0) diff --git a/frigate/video/ffmpeg.py b/frigate/video/ffmpeg.py index dfd3467e2a..83b7c4efdb 100644 --- a/frigate/video/ffmpeg.py +++ b/frigate/video/ffmpeg.py @@ -218,21 +218,21 @@ class CameraWatchdog(threading.Thread): if camera != self.config.name: continue - if topic.endswith(RecordingsDataTypeEnum.valid.value): + if topic.endswith(f"/{RecordingsDataTypeEnum.valid.value}"): self.logger.debug( f"Latest valid recording segment time on {camera}: {segment_time}" ) self.latest_valid_segment_time = max( self.latest_valid_segment_time, segment_time ) - elif topic.endswith(RecordingsDataTypeEnum.invalid.value): + elif topic.endswith(f"/{RecordingsDataTypeEnum.invalid.value}"): self.logger.warning( f"Invalid recording segment detected for {camera} at {segment_time}" ) self.latest_invalid_segment_time = max( self.latest_invalid_segment_time, segment_time ) - elif topic.endswith(RecordingsDataTypeEnum.latest.value): + elif topic.endswith(f"/{RecordingsDataTypeEnum.latest.value}"): if segment_time is not None: self.latest_cache_segment_time = max( self.latest_cache_segment_time, segment_time