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