diff --git a/frigate/record/maintainer.py b/frigate/record/maintainer.py index 33f721aa3..3dd65909b 100644 --- a/frigate/record/maintainer.py +++ b/frigate/record/maintainer.py @@ -97,6 +97,7 @@ class RecordingMaintainer(threading.Thread): self.object_recordings_info: dict[str, list] = defaultdict(list) self.audio_recordings_info: dict[str, list] = defaultdict(list) self.end_time_cache: dict[str, Tuple[datetime.datetime, float]] = {} + self.unexpected_cache_files_seen: set[str] = set() async def move_files(self) -> None: cache_files = [ @@ -115,7 +116,9 @@ class RecordingMaintainer(threading.Thread): try: camera, date = basename.rsplit("@", maxsplit=1) except ValueError: - logger.warning(f"Skipping unexpected file in cache: {cache}") + if cache not in self.unexpected_cache_files_seen: + logger.warning(f"Skipping unexpected file in cache: {cache}") + self.unexpected_cache_files_seen.add(cache) continue start_time = datetime.datetime.strptime( @@ -172,7 +175,9 @@ class RecordingMaintainer(threading.Thread): try: camera, date = basename.rsplit("@", maxsplit=1) except ValueError: - logger.warning(f"Skipping unexpected file in cache: {cache}") + if cache not in self.unexpected_cache_files_seen: + logger.warning(f"Skipping unexpected file in cache: {cache}") + self.unexpected_cache_files_seen.add(cache) continue # important that start_time is utc because recordings are stored and compared in utc diff --git a/frigate/test/test_maintainer.py b/frigate/test/test_maintainer.py index f3556a0ee..1d036150a 100644 --- a/frigate/test/test_maintainer.py +++ b/frigate/test/test_maintainer.py @@ -32,18 +32,34 @@ class TestMaintainer(unittest.IsolatedAsyncioTestCase): with patch('os.listdir', return_value=files): with patch('os.path.isfile', return_value=True): with patch('frigate.record.maintainer.psutil.process_iter', return_value=[]): - # Mock validate_and_move_segment to avoid further logic - maintainer.validate_and_move_segment = MagicMock() - - try: - await maintainer.move_files() - except ValueError as e: - if "not enough values to unpack" in str(e): - self.fail("move_files() crashed on bad filename!") - raise e - except Exception: - # Ignore other errors (like DB connection) as we only care about the unpack crash - pass + with patch('frigate.record.maintainer.logger.warning') as warn: + # Mock validate_and_move_segment to avoid further logic + maintainer.validate_and_move_segment = MagicMock() + + try: + await maintainer.move_files() + except ValueError as e: + if "not enough values to unpack" in str(e): + self.fail("move_files() crashed on bad filename!") + raise e + except Exception: + # Ignore other errors (like DB connection) as we only care about the unpack crash + pass + + # The bad filename is encountered in multiple loops, but should only warn once. + matching = [ + c + for c in warn.call_args_list + if c.args + and isinstance(c.args[0], str) + and "Skipping unexpected file in cache: bad_filename.mp4" + in c.args[0] + ] + self.assertEqual( + 1, + len(matching), + f"Expected a single warning for bad filename, got {len(matching)}", + ) if __name__ == '__main__': unittest.main()