From 91abf9937091444627836fbdb9b5f3930df5f0a8 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Fri, 30 Aug 2024 15:31:27 -0600 Subject: [PATCH] Handle case where camera is offline when generating previews --- frigate/output/output.py | 10 ++++++++++ frigate/output/preview.py | 32 +++++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/frigate/output/output.py b/frigate/output/output.py index e0e7d0cac..cb3be844c 100644 --- a/frigate/output/output.py +++ b/frigate/output/output.py @@ -64,6 +64,8 @@ def output_frames( jsmpeg_cameras: dict[str, JsmpegCamera] = {} birdseye: Optional[Birdseye] = None preview_recorders: dict[str, PreviewRecorder] = {} + preview_write_times: dict[str, float] = {} + frame_time: float = 0 move_preview_frames("cache") @@ -73,6 +75,7 @@ def output_frames( jsmpeg_cameras[camera] = JsmpegCamera(cam_config, stop_event, websocket_server) preview_recorders[camera] = PreviewRecorder(cam_config) + preview_write_times[camera] = 0 if config.birdseye.enabled: birdseye = Birdseye(config, frame_manager, stop_event, websocket_server) @@ -83,6 +86,13 @@ def output_frames( (topic, data) = detection_subscriber.check_for_update(timeout=1) if not topic: + # all queued images have been written, + # check if any cameras have stale frames + + for camera, time in preview_write_times.items(): + if time != 0 and frame_time - time > 10: + preview_recorders[camera].flag_offline(frame_time) + continue ( diff --git a/frigate/output/preview.py b/frigate/output/preview.py index 650805750..6d08bb785 100644 --- a/frigate/output/preview.py +++ b/frigate/output/preview.py @@ -3,6 +3,7 @@ import datetime import logging import os +import shutil import subprocess as sp import threading import time @@ -326,7 +327,7 @@ class PreviewRecorder: ) self.start_time = frame_time self.last_output_time = frame_time - self.output_frames = [] + self.output_frames: list[float] = [] # include first frame to ensure consistent duration self.output_frames.append(frame_time) @@ -335,6 +336,35 @@ class PreviewRecorder: self.output_frames.append(frame_time) self.write_frame_to_cache(frame_time, frame) + def flag_offline(self, frame_time: float) -> None: + # check if PREVIEW clip should be generated and cached frames reset + if frame_time >= self.segment_end: + if len(self.output_frames) == 0: + return + + old_frame_path = get_cache_image_name(self.config.name, self.output_frames[-1]) + new_frame_path = get_cache_image_name(self.config.name, frame_time) + shutil.copy(old_frame_path, new_frame_path) + + # save last frame to ensure consistent duration + self.output_frames.append(frame_time) + FFMpegConverter( + self.config, + self.output_frames, + self.requestor, + ).start() + + # reset frame cache + self.segment_end = ( + (datetime.datetime.now() + datetime.timedelta(hours=1)) + .astimezone(datetime.timezone.utc) + .replace(minute=0, second=0, microsecond=0) + .timestamp() + ) + self.start_time = frame_time + self.last_output_time = frame_time + self.output_frames = [] + def stop(self) -> None: self.requestor.stop()