diff --git a/frigate/api/event.py b/frigate/api/event.py index 7cdb933c9..abe1095f1 100644 --- a/frigate/api/event.py +++ b/frigate/api/event.py @@ -18,9 +18,8 @@ from flask import ( from peewee import DoesNotExist, fn, operator from playhouse.shortcuts import model_to_dict -from frigate.const import ( - CLIPS_DIR, -) +from frigate.config import SnapshotsConfig +from frigate.const import CLIPS_DIR from frigate.models import Event, Timeline from frigate.object_processing import TrackedObject from frigate.util.builtin import get_tz_modifiers @@ -350,8 +349,9 @@ def send_to_plus(id): # load clean.png try: + snapshot_config: SnapshotsConfig = current_app.frigate_config.cameras[event.camera] filename = f"{event.camera}-{event.id}-clean.png" - image = cv2.imread(os.path.join(CLIPS_DIR, filename)) + image = cv2.imread(os.path.join(snapshot_config.path, filename)) except Exception: logger.error(f"Unable to load clean png for event: {event.id}") return make_response( @@ -601,9 +601,10 @@ def delete_event(id): media_name = f"{event.camera}-{event.id}" if event.has_snapshot: - media = Path(f"{os.path.join(CLIPS_DIR, media_name)}.jpg") + snapshot_config: SnapshotsConfig = current_app.frigate_config.cameras[event.camera].snapshots + media = Path(f"{os.path.join(snapshot_config.path, media_name)}.jpg") media.unlink(missing_ok=True) - media = Path(f"{os.path.join(CLIPS_DIR, media_name)}-clean.png") + media = Path(f"{os.path.join(snapshot_config.path, media_name)}-clean.png") media.unlink(missing_ok=True) if event.has_clip: media = Path(f"{os.path.join(CLIPS_DIR, media_name)}.mp4") diff --git a/frigate/api/media.py b/frigate/api/media.py index 78e8c711e..8be597c39 100644 --- a/frigate/api/media.py +++ b/frigate/api/media.py @@ -25,6 +25,7 @@ from peewee import DoesNotExist, fn from tzlocal import get_localzone_name from werkzeug.utils import secure_filename +from frigate.config import SnapshotsConfig from frigate.const import ( CACHE_DIR, CLIPS_DIR, @@ -978,8 +979,9 @@ def event_snapshot_clean(id): ) if png_bytes is None: try: + snapshot_config: SnapshotsConfig = current_app.frigate_config.cameras[event.camera].snapshots clean_snapshot_path = os.path.join( - CLIPS_DIR, f"{event.camera}-{event.id}-clean.png" + snapshot_config.path, f"{event.camera}-{event.id}-clean.png" ) if not os.path.exists(clean_snapshot_path): return make_response( @@ -989,7 +991,7 @@ def event_snapshot_clean(id): 404, ) with open( - os.path.join(CLIPS_DIR, f"{event.camera}-{event.id}-clean.png"), "rb" + os.path.join(snapshot_config.path, f"{event.camera}-{event.id}-clean.png"), "rb" ) as image_file: png_bytes = image_file.read() except Exception: @@ -1023,8 +1025,9 @@ def event_snapshot(id): jsonify({"success": False, "message": "Snapshot not available"}), 404 ) # read snapshot from disk + snapshot_config: SnapshotsConfig = current_app.frigate_config.cameras[event.camera].snapshots with open( - os.path.join(CLIPS_DIR, f"{event.camera}-{event.id}.jpg"), "rb" + os.path.join(snapshot_config.path, f"{event.camera}-{event.id}.jpg"), "rb" ) as image_file: jpg_bytes = image_file.read() except DoesNotExist: diff --git a/frigate/config.py b/frigate/config.py index 55acc1a44..213a356ec 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -26,6 +26,7 @@ from frigate.const import ( AUDIO_MIN_CONFIDENCE, CACHE_DIR, CACHE_SEGMENT_FORMAT, + CLIPS_DIR, DEFAULT_DB_PATH, MAX_PRE_CAPTURE, REGEX_CAMERA_NAME, @@ -722,6 +723,7 @@ class CameraFfmpegConfig(FfmpegConfig): class SnapshotsConfig(FrigateBaseModel): enabled: bool = Field(default=False, title="Snapshots enabled.") + path: str = Field(default=CLIPS_DIR, title="Snapshots path.") clean_copy: bool = Field( default=True, title="Create a clean copy of the snapshot image." ) diff --git a/frigate/events/cleanup.py b/frigate/events/cleanup.py index 98da72f6c..57612a1e4 100644 --- a/frigate/events/cleanup.py +++ b/frigate/events/cleanup.py @@ -8,7 +8,7 @@ from enum import Enum from multiprocessing.synchronize import Event as MpEvent from pathlib import Path -from frigate.config import FrigateConfig +from frigate.config import FrigateConfig, SnapshotsConfig from frigate.const import CLIPS_DIR from frigate.models import Event, Timeline @@ -64,10 +64,12 @@ class EventCleanup(threading.Thread): def expire(self, media_type: EventCleanupType) -> list[str]: ## Expire events from unlisted cameras based on the global config if media_type == EventCleanupType.clips: + base_dir = CLIPS_DIR retain_config = self.config.record.events.retain file_extension = None # mp4 clips are no longer stored in /clips update_params = {"has_clip": False} else: + base_dir = self.config.snapshots.path retain_config = self.config.snapshots.retain file_extension = "jpg" update_params = {"has_snapshot": False} @@ -101,12 +103,12 @@ class EventCleanup(threading.Thread): for expired in expired_events: media_name = f"{expired.camera}-{expired.id}" media_path = Path( - f"{os.path.join(CLIPS_DIR, media_name)}.{file_extension}" + f"{os.path.join(base_dir, media_name)}.{file_extension}" ) media_path.unlink(missing_ok=True) if file_extension == "jpg": media_path = Path( - f"{os.path.join(CLIPS_DIR, media_name)}-clean.png" + f"{os.path.join(base_dir, media_name)}-clean.png" ) media_path.unlink(missing_ok=True) @@ -124,8 +126,10 @@ class EventCleanup(threading.Thread): ## Expire events from cameras based on the camera config for name, camera in self.config.cameras.items(): if media_type == EventCleanupType.clips: + base_dir = CLIPS_DIR retain_config = camera.record.events.retain else: + base_dir = camera.snapshots.path retain_config = camera.snapshots.retain # get distinct objects in database for this camera @@ -165,11 +169,11 @@ class EventCleanup(threading.Thread): if media_type == EventCleanupType.snapshots: media_name = f"{event.camera}-{event.id}" media_path = Path( - f"{os.path.join(CLIPS_DIR, media_name)}.{file_extension}" + f"{os.path.join(base_dir, media_name)}.{file_extension}" ) media_path.unlink(missing_ok=True) media_path = Path( - f"{os.path.join(CLIPS_DIR, media_name)}-clean.png" + f"{os.path.join(base_dir, media_name)}-clean.png" ) media_path.unlink(missing_ok=True) @@ -198,10 +202,11 @@ class EventCleanup(threading.Thread): duplicate_events = Event.raw(duplicate_query) for event in duplicate_events: logger.debug(f"Removing duplicate: {event.id}") + snapshot_config: SnapshotsConfig = self.config.cameras[event.camera].snapshots media_name = f"{event.camera}-{event.id}" - media_path = Path(f"{os.path.join(CLIPS_DIR, media_name)}.jpg") + media_path = Path(f"{os.path.join(snapshot_config.path, media_name)}.jpg") media_path.unlink(missing_ok=True) - media_path = Path(f"{os.path.join(CLIPS_DIR, media_name)}-clean.png") + media_path = Path(f"{os.path.join(snapshot_config.path, media_name)}-clean.png") media_path.unlink(missing_ok=True) ( diff --git a/frigate/events/external.py b/frigate/events/external.py index 7bae21071..f4fc941bf 100644 --- a/frigate/events/external.py +++ b/frigate/events/external.py @@ -11,8 +11,7 @@ from typing import Optional import cv2 from frigate.comms.events_updater import EventUpdatePublisher -from frigate.config import CameraConfig, FrigateConfig -from frigate.const import CLIPS_DIR +from frigate.config import CameraConfig, FrigateConfig, SnapshotsConfig from frigate.events.types import EventStateEnum, EventTypeEnum from frigate.util.image import draw_box_with_label @@ -94,6 +93,8 @@ class ExternalEventProcessor: draw: dict[str, any], img_frame: any, ) -> str: + snapshot_config: SnapshotsConfig = camera_config.snapshots + # write clean snapshot if enabled if camera_config.snapshots.clean_copy: ret, png = cv2.imencode(".png", img_frame) @@ -101,7 +102,7 @@ class ExternalEventProcessor: if ret: with open( os.path.join( - CLIPS_DIR, + snapshot_config.path, f"{camera_config.name}-{event_id}-clean.png", ), "wb", @@ -130,7 +131,7 @@ class ExternalEventProcessor: ret, jpg = cv2.imencode(".jpg", img_frame) with open( - os.path.join(CLIPS_DIR, f"{camera_config.name}-{event_id}.jpg"), + os.path.join(snapshot_config.path, f"{camera_config.name}-{event_id}.jpg"), "wb", ) as j: j.write(jpg.tobytes()) diff --git a/frigate/object_processing.py b/frigate/object_processing.py index c5e8101dc..b3e11e067 100644 --- a/frigate/object_processing.py +++ b/frigate/object_processing.py @@ -23,7 +23,6 @@ from frigate.config import ( SnapshotsConfig, ZoomingModeEnum, ) -from frigate.const import CLIPS_DIR from frigate.events.types import EventStateEnum, EventTypeEnum from frigate.ptz.autotrack import PtzAutoTrackerThread from frigate.util.image import ( @@ -897,7 +896,7 @@ class TrackedObjectProcessor(threading.Thread): logger.warning(f"Unable to save snapshot for {obj.obj_data['id']}.") else: with open( - os.path.join(CLIPS_DIR, f"{camera}-{obj.obj_data['id']}.jpg"), + os.path.join(snapshot_config.path, f"{camera}-{obj.obj_data['id']}.jpg"), "wb", ) as j: j.write(jpg_bytes) @@ -912,7 +911,7 @@ class TrackedObjectProcessor(threading.Thread): else: with open( os.path.join( - CLIPS_DIR, + snapshot_config.path, f"{camera}-{obj.obj_data['id']}-clean.png", ), "wb",