diff --git a/frigate/output/output.py b/frigate/output/output.py index e717463b1..b7a918cd2 100644 --- a/frigate/output/output.py +++ b/frigate/output/output.py @@ -2,6 +2,8 @@ import logging import multiprocessing as mp +import os +import shutil import signal import threading from typing import Optional @@ -18,6 +20,7 @@ from ws4py.server.wsgiutils import WebSocketWSGIApplication from frigate.comms.detections_updater import DetectionSubscriber, DetectionTypeEnum from frigate.comms.ws import WebSocket from frigate.config import FrigateConfig +from frigate.const import CACHE_DIR, CLIPS_DIR from frigate.output.birdseye import Birdseye from frigate.output.camera import JsmpegCamera from frigate.output.preview import PreviewRecorder @@ -61,6 +64,8 @@ def output_frames( birdseye: Optional[Birdseye] = None preview_recorders: dict[str, PreviewRecorder] = {} + move_preview_frames("cache") + for camera, cam_config in config.cameras.items(): if not cam_config.enabled: continue @@ -151,6 +156,8 @@ def output_frames( for preview in preview_recorders.values(): preview.stop() + move_preview_frames("clips") + if birdseye is not None: birdseye.stop() @@ -160,3 +167,16 @@ def output_frames( websocket_server.shutdown() websocket_thread.join() logger.info("exiting output process...") + + +def move_preview_frames(loc: str): + preview_holdover = os.path.join(CLIPS_DIR, "preview_restart_cache") + preview_cache = os.path.join(CACHE_DIR, "preview_frames") + + if loc == "clips": + shutil.move(preview_cache, preview_holdover) + elif loc == "cache": + if not os.path.exists(preview_holdover): + return + + shutil.move(preview_holdover, preview_cache) diff --git a/frigate/output/preview.py b/frigate/output/preview.py index 33d0de884..de9dccce7 100644 --- a/frigate/output/preview.py +++ b/frigate/output/preview.py @@ -3,7 +3,6 @@ import datetime import logging import os -import shutil import subprocess as sp import threading from pathlib import Path @@ -26,6 +25,7 @@ from frigate.util.image import copy_yuv_to_position, get_yuv_crop logger = logging.getLogger(__name__) FOLDER_PREVIEW_FRAMES = "preview_frames" +PREVIEW_CACHE_DIR = os.path.join(CACHE_DIR, FOLDER_PREVIEW_FRAMES) PREVIEW_SEGMENT_DURATION = 3600 # one hour # important to have lower keyframe to maintain scrubbing performance PREVIEW_KEYFRAME_INTERVAL = 60 @@ -163,11 +163,37 @@ class PreviewRecorder: .timestamp() ) - Path(os.path.join(CACHE_DIR, "preview_frames")).mkdir(exist_ok=True) + Path(PREVIEW_CACHE_DIR).mkdir(exist_ok=True) Path(os.path.join(CLIPS_DIR, f"previews/{config.name}")).mkdir( parents=True, exist_ok=True ) + # check for existing items in cache + start_ts = ( + datetime.datetime.now() + .replace(minute=0, second=0, microsecond=0) + .timestamp() + ) + + file_start = f"preview_{config.name}" + start_file = f"{file_start}-{start_ts}.jpg" + + for file in sorted(os.listdir(os.path.join(CACHE_DIR, FOLDER_PREVIEW_FRAMES))): + if not file.startswith(file_start): + continue + + if file < start_file: + os.unlink(os.path.join(PREVIEW_CACHE_DIR, file)) + continue + + ts = float(file.split("-")[1][:-4]) + + if self.start_time == 0: + self.start_time = ts + + self.last_output_time = ts + self.output_frames.append(ts) + def should_write_frame( self, current_tracked_objects: list[dict[str, any]], @@ -269,11 +295,6 @@ class PreviewRecorder: self.write_frame_to_cache(frame_time, frame) def stop(self) -> None: - try: - shutil.rmtree(os.path.join(CACHE_DIR, FOLDER_PREVIEW_FRAMES)) - except FileNotFoundError: - pass - self.requestor.stop()