2023-06-11 06:23:18 -06:00
|
|
|
import datetime
|
2020-11-04 06:28:07 -06:00
|
|
|
import logging
|
|
|
|
|
import multiprocessing as mp
|
|
|
|
|
import os
|
2024-05-18 11:36:13 -05:00
|
|
|
import secrets
|
2023-01-30 16:42:23 -07:00
|
|
|
import shutil
|
2026-07-06 09:28:02 -08:00
|
|
|
from collections.abc import Callable
|
2023-07-16 06:42:56 -06:00
|
|
|
from multiprocessing import Queue
|
2025-06-12 12:12:34 -06:00
|
|
|
from multiprocessing.managers import DictProxy, SyncManager
|
2023-05-29 12:31:17 +02:00
|
|
|
from multiprocessing.synchronize import Event as MpEvent
|
2025-05-29 23:58:31 -03:00
|
|
|
from pathlib import Path
|
2023-05-29 12:31:17 +02:00
|
|
|
|
|
|
|
|
import psutil
|
2024-09-24 14:05:30 +01:00
|
|
|
import uvicorn
|
2020-12-24 07:47:27 -06:00
|
|
|
from peewee_migrate import Router
|
2020-11-04 06:28:07 -06:00
|
|
|
from playhouse.sqlite_ext import SqliteExtDatabase
|
|
|
|
|
|
2024-05-18 11:36:13 -05:00
|
|
|
from frigate.api.auth import hash_password
|
2024-09-24 14:05:30 +01:00
|
|
|
from frigate.api.fastapi_app import create_fastapi_app
|
2024-09-27 15:53:23 +03:00
|
|
|
from frigate.camera import CameraMetrics, PTZMetrics
|
2025-06-11 11:25:30 -06:00
|
|
|
from frigate.camera.maintainer import CameraMaintainer
|
2025-02-10 20:47:15 -06:00
|
|
|
from frigate.comms.base_communicator import Communicator
|
|
|
|
|
from frigate.comms.dispatcher import Dispatcher
|
2025-03-10 16:29:29 -06:00
|
|
|
from frigate.comms.event_metadata_updater import EventMetadataPublisher
|
2023-07-13 19:52:33 -05:00
|
|
|
from frigate.comms.inter_process import InterProcessCommunicator
|
2022-11-23 19:03:20 -07:00
|
|
|
from frigate.comms.mqtt import MqttClient
|
2025-06-12 12:12:34 -06:00
|
|
|
from frigate.comms.object_detector_signaler import DetectorProxy
|
2024-07-22 14:39:15 -06:00
|
|
|
from frigate.comms.webpush import WebPushClient
|
2022-11-23 19:03:20 -07:00
|
|
|
from frigate.comms.ws import WebSocketClient
|
2024-06-21 17:30:19 -04:00
|
|
|
from frigate.comms.zmq_proxy import ZmqProxy
|
2025-05-22 12:16:51 -06:00
|
|
|
from frigate.config.camera.updater import CameraConfigUpdatePublisher
|
2024-09-30 16:45:22 -06:00
|
|
|
from frigate.config.config import FrigateConfig
|
2026-03-19 09:47:57 -05:00
|
|
|
from frigate.config.profile_manager import ProfileManager
|
2023-04-30 13:32:36 -05:00
|
|
|
from frigate.const import (
|
|
|
|
|
CACHE_DIR,
|
|
|
|
|
CLIPS_DIR,
|
|
|
|
|
CONFIG_DIR,
|
2023-06-08 05:32:35 -06:00
|
|
|
EXPORT_DIR,
|
2025-01-04 15:21:47 -06:00
|
|
|
FACE_DIR,
|
2023-04-30 13:32:36 -05:00
|
|
|
MODEL_CACHE_DIR,
|
|
|
|
|
RECORD_DIR,
|
2025-02-18 07:46:29 -07:00
|
|
|
THUMB_DIR,
|
2025-07-07 09:03:57 -05:00
|
|
|
TRIGGER_DIR,
|
2023-04-30 13:32:36 -05:00
|
|
|
)
|
2025-01-10 12:44:30 -07:00
|
|
|
from frigate.data_processing.types import DataProcessorMetrics
|
2024-10-07 15:30:45 -05:00
|
|
|
from frigate.db.sqlitevecq import SqliteVecQueueDatabase
|
2026-03-04 10:07:34 -06:00
|
|
|
from frigate.debug_replay import (
|
|
|
|
|
DebugReplayManager,
|
|
|
|
|
cleanup_replay_cameras,
|
|
|
|
|
)
|
2025-06-12 12:12:34 -06:00
|
|
|
from frigate.embeddings import EmbeddingProcess, EmbeddingsContext
|
2024-09-27 15:53:23 +03:00
|
|
|
from frigate.events.audio import AudioProcessor
|
2023-05-19 04:16:11 -06:00
|
|
|
from frigate.events.cleanup import EventCleanup
|
|
|
|
|
from frigate.events.maintainer import EventProcessor
|
2026-04-14 09:19:50 -05:00
|
|
|
from frigate.jobs.export import reap_stale_exports
|
2026-03-05 17:53:48 -06:00
|
|
|
from frigate.jobs.motion_search import stop_all_motion_search_jobs
|
2025-05-29 10:02:17 -05:00
|
|
|
from frigate.log import _stop_logging
|
2023-12-03 07:16:01 -07:00
|
|
|
from frigate.models import (
|
|
|
|
|
Event,
|
2024-04-19 16:11:41 -06:00
|
|
|
Export,
|
2023-12-03 07:16:01 -07:00
|
|
|
Previews,
|
|
|
|
|
Recordings,
|
|
|
|
|
RecordingsToDelete,
|
|
|
|
|
Regions,
|
2024-02-20 16:26:09 -07:00
|
|
|
ReviewSegment,
|
2023-12-03 07:16:01 -07:00
|
|
|
Timeline,
|
2025-07-07 09:03:57 -05:00
|
|
|
Trigger,
|
2024-05-18 11:36:13 -05:00
|
|
|
User,
|
2023-12-03 07:16:01 -07:00
|
|
|
)
|
2025-04-15 07:55:38 -06:00
|
|
|
from frigate.object_detection.base import ObjectDetectProcess
|
2025-06-12 12:12:34 -06:00
|
|
|
from frigate.output.output import OutputProcess
|
2023-07-08 07:04:47 -05:00
|
|
|
from frigate.ptz.autotrack import PtzAutoTrackerThread
|
|
|
|
|
from frigate.ptz.onvif import OnvifController
|
2023-07-26 04:55:08 -06:00
|
|
|
from frigate.record.cleanup import RecordingCleanup
|
2024-04-19 16:11:41 -06:00
|
|
|
from frigate.record.export import migrate_exports
|
2025-06-12 12:12:34 -06:00
|
|
|
from frigate.record.record import RecordProcess
|
|
|
|
|
from frigate.review.review import ReviewProcess
|
2024-02-21 13:10:28 -07:00
|
|
|
from frigate.stats.emitter import StatsEmitter
|
|
|
|
|
from frigate.stats.util import stats_init
|
2022-10-09 05:28:26 -06:00
|
|
|
from frigate.storage import StorageMaintainer
|
2023-04-23 09:45:19 -06:00
|
|
|
from frigate.timeline import TimelineProcessor
|
2025-03-11 21:31:05 -06:00
|
|
|
from frigate.track.object_processing import TrackedObjectProcessor
|
2024-09-24 15:07:47 +03:00
|
|
|
from frigate.util.builtin import empty_and_close_queue
|
2025-06-11 11:25:30 -06:00
|
|
|
from frigate.util.image import UntrackedSharedMemory
|
2026-03-20 19:02:47 -05:00
|
|
|
from frigate.util.process import FrigateProcess
|
2025-07-11 08:11:35 -05:00
|
|
|
from frigate.util.services import set_file_limit
|
2021-09-13 22:02:23 -05:00
|
|
|
from frigate.version import VERSION
|
2020-11-04 06:28:07 -06:00
|
|
|
from frigate.watchdog import FrigateWatchdog
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
2021-02-17 07:23:32 -06:00
|
|
|
|
|
|
|
|
class FrigateApp:
|
2025-06-24 11:41:11 -06:00
|
|
|
def __init__(
|
|
|
|
|
self, config: FrigateConfig, manager: SyncManager, stop_event: MpEvent
|
|
|
|
|
) -> None:
|
2025-06-12 12:12:34 -06:00
|
|
|
self.metrics_manager = manager
|
2026-07-06 09:28:02 -08:00
|
|
|
self.audio_process: mp.Process | None = None
|
2025-06-24 11:41:11 -06:00
|
|
|
self.stop_event = stop_event
|
2023-07-16 06:42:56 -06:00
|
|
|
self.detection_queue: Queue = mp.Queue()
|
2022-11-03 22:23:09 -04:00
|
|
|
self.detectors: dict[str, ObjectDetectProcess] = {}
|
2022-04-16 17:40:04 +02:00
|
|
|
self.detection_shms: list[mp.shared_memory.SharedMemory] = []
|
2023-07-16 06:42:56 -06:00
|
|
|
self.log_queue: Queue = mp.Queue()
|
2025-06-12 12:12:34 -06:00
|
|
|
self.camera_metrics: DictProxy = self.metrics_manager.dict()
|
2025-01-10 12:44:30 -07:00
|
|
|
self.embeddings_metrics: DataProcessorMetrics | None = (
|
2025-06-12 12:12:34 -06:00
|
|
|
DataProcessorMetrics(
|
|
|
|
|
self.metrics_manager, list(config.classification.custom.keys())
|
|
|
|
|
)
|
2025-02-28 12:43:08 -06:00
|
|
|
if (
|
|
|
|
|
config.semantic_search.enabled
|
2025-12-20 17:30:34 -07:00
|
|
|
or any(
|
|
|
|
|
c.objects.genai.enabled or c.review.genai.enabled
|
|
|
|
|
for c in config.cameras.values()
|
|
|
|
|
)
|
2025-02-28 12:43:08 -06:00
|
|
|
or config.lpr.enabled
|
|
|
|
|
or config.face_recognition.enabled
|
2025-06-06 10:29:44 -06:00
|
|
|
or len(config.classification.custom) > 0
|
2025-02-28 12:43:08 -06:00
|
|
|
)
|
|
|
|
|
else None
|
2025-01-05 08:47:57 -06:00
|
|
|
)
|
2024-09-27 15:53:23 +03:00
|
|
|
self.ptz_metrics: dict[str, PTZMetrics] = {}
|
2023-05-05 01:58:59 +03:00
|
|
|
self.processes: dict[str, int] = {}
|
2026-07-06 09:28:02 -08:00
|
|
|
self.embeddings: EmbeddingsContext | None = None
|
|
|
|
|
self.profile_manager: ProfileManager | None = None
|
2024-10-03 15:33:53 +03:00
|
|
|
self.config = config
|
2021-01-15 21:33:53 -06:00
|
|
|
|
2022-04-16 17:40:04 +02:00
|
|
|
def ensure_dirs(self) -> None:
|
2025-01-04 15:21:47 -06:00
|
|
|
dirs = [
|
2023-06-08 05:32:35 -06:00
|
|
|
CONFIG_DIR,
|
|
|
|
|
RECORD_DIR,
|
2025-02-18 07:46:29 -07:00
|
|
|
THUMB_DIR,
|
2024-06-09 13:45:26 -05:00
|
|
|
f"{CLIPS_DIR}/cache",
|
2023-06-08 05:32:35 -06:00
|
|
|
CACHE_DIR,
|
|
|
|
|
MODEL_CACHE_DIR,
|
|
|
|
|
EXPORT_DIR,
|
2025-01-04 15:21:47 -06:00
|
|
|
]
|
|
|
|
|
|
|
|
|
|
if self.config.face_recognition.enabled:
|
|
|
|
|
dirs.append(FACE_DIR)
|
|
|
|
|
|
2025-07-07 09:03:57 -05:00
|
|
|
if self.config.semantic_search.enabled:
|
|
|
|
|
dirs.append(TRIGGER_DIR)
|
|
|
|
|
|
2025-01-04 15:21:47 -06:00
|
|
|
for d in dirs:
|
2020-12-01 07:22:23 -06:00
|
|
|
if not os.path.exists(d) and not os.path.islink(d):
|
2020-12-03 08:01:22 -06:00
|
|
|
logger.info(f"Creating directory: {d}")
|
2026-05-12 11:34:46 -07:00
|
|
|
os.makedirs(d, exist_ok=True)
|
2020-12-03 08:01:22 -06:00
|
|
|
else:
|
|
|
|
|
logger.debug(f"Skipping directory: {d}")
|
2020-12-21 07:37:42 -06:00
|
|
|
|
2026-03-04 10:07:34 -06:00
|
|
|
def init_debug_replay_manager(self) -> None:
|
|
|
|
|
self.replay_manager = DebugReplayManager()
|
|
|
|
|
|
2024-09-24 15:07:47 +03:00
|
|
|
def init_camera_metrics(self) -> None:
|
2024-09-27 15:53:23 +03:00
|
|
|
# create camera_metrics
|
2023-07-14 18:05:14 -06:00
|
|
|
for camera_name in self.config.cameras.keys():
|
2025-06-12 12:12:34 -06:00
|
|
|
self.camera_metrics[camera_name] = CameraMetrics(self.metrics_manager)
|
2024-09-27 15:53:23 +03:00
|
|
|
self.ptz_metrics[camera_name] = PTZMetrics(
|
|
|
|
|
autotracker_enabled=self.config.cameras[
|
|
|
|
|
camera_name
|
|
|
|
|
].onvif.autotracking.enabled
|
|
|
|
|
)
|
2021-02-17 07:23:32 -06:00
|
|
|
|
2022-04-16 17:40:04 +02:00
|
|
|
def init_queues(self) -> None:
|
2020-11-04 06:28:07 -06:00
|
|
|
# Queue for cameras to push tracked objects to
|
2025-06-11 11:25:30 -06:00
|
|
|
# leaving room for 2 extra cameras to be added
|
2023-07-08 05:46:31 -06:00
|
|
|
self.detected_frames_queue: Queue = mp.Queue(
|
2025-06-11 11:25:30 -06:00
|
|
|
maxsize=(
|
|
|
|
|
sum(
|
|
|
|
|
camera.enabled_in_config == True
|
|
|
|
|
for camera in self.config.cameras.values()
|
|
|
|
|
)
|
|
|
|
|
+ 2
|
|
|
|
|
)
|
|
|
|
|
* 2
|
2021-02-17 07:23:32 -06:00
|
|
|
)
|
2020-11-04 06:28:07 -06:00
|
|
|
|
2023-04-23 09:45:19 -06:00
|
|
|
# Queue for timeline events
|
2023-07-16 06:42:56 -06:00
|
|
|
self.timeline_queue: Queue = mp.Queue()
|
2023-04-23 09:45:19 -06:00
|
|
|
|
2022-04-16 17:40:04 +02:00
|
|
|
def init_database(self) -> None:
|
2023-06-11 06:23:18 -06:00
|
|
|
def vacuum_db(db: SqliteExtDatabase) -> None:
|
2024-01-26 13:18:29 +00:00
|
|
|
logger.info("Running database vacuum")
|
2023-06-11 06:23:18 -06:00
|
|
|
db.execute_sql("VACUUM;")
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
with open(f"{CONFIG_DIR}/.vacuum", "w") as f:
|
|
|
|
|
f.write(str(datetime.datetime.now().timestamp()))
|
|
|
|
|
except PermissionError:
|
|
|
|
|
logger.error("Unable to write to /config to save DB state")
|
|
|
|
|
|
2021-06-06 21:24:36 -04:00
|
|
|
# Migrate DB schema
|
2021-01-24 06:53:01 -06:00
|
|
|
migrate_db = SqliteExtDatabase(self.config.database.path)
|
2020-12-24 07:47:27 -06:00
|
|
|
|
|
|
|
|
# Run migrations
|
2021-02-17 07:23:32 -06:00
|
|
|
del logging.getLogger("peewee_migrate").handlers[:]
|
2021-01-24 06:53:01 -06:00
|
|
|
router = Router(migrate_db)
|
2024-04-20 07:55:51 -06:00
|
|
|
|
|
|
|
|
if len(router.diff) > 0:
|
|
|
|
|
logger.info("Making backup of DB before migrations...")
|
|
|
|
|
shutil.copyfile(
|
|
|
|
|
self.config.database.path,
|
|
|
|
|
self.config.database.path.replace("frigate.db", "backup.db"),
|
|
|
|
|
)
|
|
|
|
|
|
2020-12-24 07:47:27 -06:00
|
|
|
router.run()
|
|
|
|
|
|
2023-06-11 06:23:18 -06:00
|
|
|
# check if vacuum needs to be run
|
|
|
|
|
if os.path.exists(f"{CONFIG_DIR}/.vacuum"):
|
|
|
|
|
with open(f"{CONFIG_DIR}/.vacuum") as f:
|
|
|
|
|
try:
|
2023-06-15 01:19:26 +03:00
|
|
|
timestamp = round(float(f.readline()))
|
2023-06-11 06:23:18 -06:00
|
|
|
except Exception:
|
|
|
|
|
timestamp = 0
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
timestamp
|
|
|
|
|
< (
|
|
|
|
|
datetime.datetime.now() - datetime.timedelta(weeks=2)
|
|
|
|
|
).timestamp()
|
|
|
|
|
):
|
|
|
|
|
vacuum_db(migrate_db)
|
|
|
|
|
else:
|
|
|
|
|
vacuum_db(migrate_db)
|
|
|
|
|
|
2021-01-24 06:53:01 -06:00
|
|
|
migrate_db.close()
|
|
|
|
|
|
2023-05-05 01:58:59 +03:00
|
|
|
def init_go2rtc(self) -> None:
|
|
|
|
|
for proc in psutil.process_iter(["pid", "name"]):
|
|
|
|
|
if proc.info["name"] == "go2rtc":
|
|
|
|
|
logger.info(f"go2rtc process pid: {proc.info['pid']}")
|
|
|
|
|
self.processes["go2rtc"] = proc.info["pid"]
|
|
|
|
|
|
2023-04-26 07:25:26 -06:00
|
|
|
def init_recording_manager(self) -> None:
|
2025-06-24 11:41:11 -06:00
|
|
|
recording_process = RecordProcess(self.config, self.stop_event)
|
2023-04-26 07:25:26 -06:00
|
|
|
self.recording_process = recording_process
|
|
|
|
|
recording_process.start()
|
2023-05-05 01:58:59 +03:00
|
|
|
self.processes["recording"] = recording_process.pid or 0
|
2023-04-26 07:25:26 -06:00
|
|
|
logger.info(f"Recording process started: {recording_process.pid}")
|
|
|
|
|
|
2024-02-20 16:26:09 -07:00
|
|
|
def init_review_segment_manager(self) -> None:
|
2025-06-24 11:41:11 -06:00
|
|
|
review_segment_process = ReviewProcess(self.config, self.stop_event)
|
2024-02-20 16:26:09 -07:00
|
|
|
self.review_segment_process = review_segment_process
|
|
|
|
|
review_segment_process.start()
|
|
|
|
|
self.processes["review_segment"] = review_segment_process.pid or 0
|
2024-06-21 17:30:19 -04:00
|
|
|
logger.info(f"Review process started: {review_segment_process.pid}")
|
|
|
|
|
|
|
|
|
|
def init_embeddings_manager(self) -> None:
|
2025-08-08 16:33:11 -06:00
|
|
|
# always start the embeddings process
|
2025-06-12 12:12:34 -06:00
|
|
|
embedding_process = EmbeddingProcess(
|
2025-06-24 11:41:11 -06:00
|
|
|
self.config, self.embeddings_metrics, self.stop_event
|
2024-06-21 17:30:19 -04:00
|
|
|
)
|
|
|
|
|
self.embedding_process = embedding_process
|
|
|
|
|
embedding_process.start()
|
|
|
|
|
self.processes["embeddings"] = embedding_process.pid or 0
|
|
|
|
|
logger.info(f"Embedding process started: {embedding_process.pid}")
|
2024-02-20 16:26:09 -07:00
|
|
|
|
2023-04-26 07:25:26 -06:00
|
|
|
def bind_database(self) -> None:
|
|
|
|
|
"""Bind db to the main process."""
|
|
|
|
|
# NOTE: all db accessing processes need to be created before the db can be bound to the main process
|
2024-10-07 15:30:45 -05:00
|
|
|
self.db = SqliteVecQueueDatabase(
|
2023-06-11 06:23:18 -06:00
|
|
|
self.config.database.path,
|
|
|
|
|
pragmas={
|
|
|
|
|
"auto_vacuum": "FULL", # Does not defragment database
|
|
|
|
|
"cache_size": -512 * 1000, # 512MB of cache,
|
|
|
|
|
"synchronous": "NORMAL", # Safe when using WAL https://www.sqlite.org/pragma.html#pragma_synchronous
|
|
|
|
|
},
|
2023-07-21 06:29:50 -06:00
|
|
|
timeout=max(
|
2025-06-11 11:25:30 -06:00
|
|
|
60,
|
|
|
|
|
10
|
|
|
|
|
* len([c for c in self.config.cameras.values() if c.enabled_in_config]),
|
2023-07-21 06:29:50 -06:00
|
|
|
),
|
2026-07-21 07:44:33 -05:00
|
|
|
load_vec_extension=True,
|
2023-06-11 06:23:18 -06:00
|
|
|
)
|
2024-02-20 16:26:09 -07:00
|
|
|
models = [
|
|
|
|
|
Event,
|
2024-04-19 16:11:41 -06:00
|
|
|
Export,
|
2024-02-20 16:26:09 -07:00
|
|
|
Previews,
|
|
|
|
|
Recordings,
|
|
|
|
|
RecordingsToDelete,
|
|
|
|
|
Regions,
|
|
|
|
|
ReviewSegment,
|
|
|
|
|
Timeline,
|
2024-05-18 11:36:13 -05:00
|
|
|
User,
|
2025-07-07 09:03:57 -05:00
|
|
|
Trigger,
|
2024-02-20 16:26:09 -07:00
|
|
|
]
|
2020-11-04 06:28:07 -06:00
|
|
|
self.db.bind(models)
|
|
|
|
|
|
2024-04-19 16:11:41 -06:00
|
|
|
def check_db_data_migrations(self) -> None:
|
|
|
|
|
# check if vacuum needs to be run
|
|
|
|
|
if not os.path.exists(f"{CONFIG_DIR}/.exports"):
|
|
|
|
|
try:
|
|
|
|
|
with open(f"{CONFIG_DIR}/.exports", "w") as f:
|
|
|
|
|
f.write(str(datetime.datetime.now().timestamp()))
|
|
|
|
|
except PermissionError:
|
|
|
|
|
logger.error("Unable to write to /config to save export state")
|
|
|
|
|
|
2024-09-30 16:45:22 -06:00
|
|
|
migrate_exports(self.config.ffmpeg, list(self.config.cameras.keys()))
|
2024-04-19 16:11:41 -06:00
|
|
|
|
2024-10-07 15:30:45 -05:00
|
|
|
def init_embeddings_client(self) -> None:
|
2025-08-08 16:33:11 -06:00
|
|
|
# Create a client for other processes to use
|
|
|
|
|
self.embeddings = EmbeddingsContext(self.db)
|
2024-10-07 15:30:45 -05:00
|
|
|
|
2023-07-13 19:52:33 -05:00
|
|
|
def init_inter_process_communicator(self) -> None:
|
2024-02-14 17:24:36 -07:00
|
|
|
self.inter_process_communicator = InterProcessCommunicator()
|
2025-05-22 12:16:51 -06:00
|
|
|
self.inter_config_updater = CameraConfigUpdatePublisher()
|
2025-03-10 16:29:29 -06:00
|
|
|
self.event_metadata_updater = EventMetadataPublisher()
|
2024-06-21 17:30:19 -04:00
|
|
|
self.inter_zmq_proxy = ZmqProxy()
|
2025-06-12 12:12:34 -06:00
|
|
|
self.detection_proxy = DetectorProxy()
|
2023-07-13 19:52:33 -05:00
|
|
|
|
2023-04-26 05:08:53 -06:00
|
|
|
def init_onvif(self) -> None:
|
2023-07-11 06:23:20 -05:00
|
|
|
self.onvif_controller = OnvifController(self.config, self.ptz_metrics)
|
2023-04-26 05:08:53 -06:00
|
|
|
|
2022-11-23 19:03:20 -07:00
|
|
|
def init_dispatcher(self) -> None:
|
|
|
|
|
comms: list[Communicator] = []
|
2020-11-04 06:28:07 -06:00
|
|
|
|
2022-11-23 19:03:20 -07:00
|
|
|
if self.config.mqtt.enabled:
|
|
|
|
|
comms.append(MqttClient(self.config))
|
|
|
|
|
|
2025-02-10 20:47:15 -06:00
|
|
|
notification_cameras = [
|
|
|
|
|
c
|
|
|
|
|
for c in self.config.cameras.values()
|
|
|
|
|
if c.enabled and c.notifications.enabled_in_config
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
if notification_cameras:
|
|
|
|
|
comms.append(WebPushClient(self.config, self.stop_event))
|
2024-07-22 14:39:15 -06:00
|
|
|
|
2023-02-03 20:15:47 -06:00
|
|
|
comms.append(WebSocketClient(self.config))
|
2023-07-13 19:52:33 -05:00
|
|
|
comms.append(self.inter_process_communicator)
|
|
|
|
|
|
2023-04-26 05:08:53 -06:00
|
|
|
self.dispatcher = Dispatcher(
|
2023-04-26 07:25:26 -06:00
|
|
|
self.config,
|
2024-02-19 06:26:59 -07:00
|
|
|
self.inter_config_updater,
|
2023-04-26 07:25:26 -06:00
|
|
|
self.onvif_controller,
|
2023-07-11 06:23:20 -05:00
|
|
|
self.ptz_metrics,
|
2023-04-26 07:25:26 -06:00
|
|
|
comms,
|
2023-04-26 05:08:53 -06:00
|
|
|
)
|
2021-06-14 07:31:13 -05:00
|
|
|
|
2026-03-19 09:47:57 -05:00
|
|
|
def init_profile_manager(self) -> None:
|
|
|
|
|
self.profile_manager = ProfileManager(
|
|
|
|
|
self.config, self.inter_config_updater, self.dispatcher
|
|
|
|
|
)
|
|
|
|
|
self.dispatcher.profile_manager = self.profile_manager
|
|
|
|
|
|
2026-05-30 22:35:03 -05:00
|
|
|
def restore_active_profile(self) -> None:
|
|
|
|
|
"""Re-activate the persisted profile after subscribers are connected.
|
|
|
|
|
|
|
|
|
|
ZMQ PUB/SUB drops messages with no subscribers, so activation must
|
|
|
|
|
run after every config_updater subscriber is up.
|
|
|
|
|
"""
|
|
|
|
|
if self.profile_manager is None:
|
|
|
|
|
return
|
|
|
|
|
|
2026-03-19 09:47:57 -05:00
|
|
|
persisted = ProfileManager.load_persisted_profile()
|
|
|
|
|
if persisted and any(
|
|
|
|
|
persisted in cam.profiles for cam in self.config.cameras.values()
|
|
|
|
|
):
|
|
|
|
|
logger.info("Restoring persisted profile '%s'", persisted)
|
2026-05-30 22:35:03 -05:00
|
|
|
# runtime overrides are layered on top via restore_runtime_state()
|
2026-05-27 13:03:09 -05:00
|
|
|
self.profile_manager.activate_profile(
|
|
|
|
|
persisted, clear_runtime_overrides=False
|
|
|
|
|
)
|
2026-03-19 09:47:57 -05:00
|
|
|
|
2022-04-16 17:40:04 +02:00
|
|
|
def start_detectors(self) -> None:
|
2020-11-04 06:28:07 -06:00
|
|
|
for name in self.config.cameras.keys():
|
2021-06-19 13:15:02 +01:00
|
|
|
try:
|
2022-12-15 07:12:52 -06:00
|
|
|
largest_frame = max(
|
|
|
|
|
[
|
|
|
|
|
det.model.height * det.model.width * 3
|
2024-09-30 16:45:22 -06:00
|
|
|
if det.model is not None
|
|
|
|
|
else 320
|
|
|
|
|
for det in self.config.detectors.values()
|
2022-12-15 07:12:52 -06:00
|
|
|
]
|
|
|
|
|
)
|
2024-11-15 14:14:37 -07:00
|
|
|
shm_in = UntrackedSharedMemory(
|
2021-06-24 01:45:27 -04:00
|
|
|
name=name,
|
|
|
|
|
create=True,
|
2022-12-15 07:12:52 -06:00
|
|
|
size=largest_frame,
|
2021-06-19 13:15:02 +01:00
|
|
|
)
|
|
|
|
|
except FileExistsError:
|
2024-11-15 14:14:37 -07:00
|
|
|
shm_in = UntrackedSharedMemory(name=name)
|
2021-06-19 13:15:02 +01:00
|
|
|
|
|
|
|
|
try:
|
2024-11-15 14:14:37 -07:00
|
|
|
shm_out = UntrackedSharedMemory(
|
2021-06-19 13:15:02 +01:00
|
|
|
name=f"out-{name}", create=True, size=20 * 6 * 4
|
|
|
|
|
)
|
|
|
|
|
except FileExistsError:
|
2024-11-15 14:14:37 -07:00
|
|
|
shm_out = UntrackedSharedMemory(name=f"out-{name}")
|
2021-06-19 13:15:02 +01:00
|
|
|
|
2020-11-04 06:28:07 -06:00
|
|
|
self.detection_shms.append(shm_in)
|
|
|
|
|
self.detection_shms.append(shm_out)
|
|
|
|
|
|
2022-12-15 07:12:52 -06:00
|
|
|
for name, detector_config in self.config.detectors.items():
|
2022-11-03 22:23:09 -04:00
|
|
|
self.detectors[name] = ObjectDetectProcess(
|
|
|
|
|
name,
|
|
|
|
|
self.detection_queue,
|
2025-06-11 11:25:30 -06:00
|
|
|
list(self.config.cameras.keys()),
|
2025-06-13 09:43:38 -05:00
|
|
|
self.config,
|
2022-12-15 07:12:52 -06:00
|
|
|
detector_config,
|
2025-06-24 11:41:11 -06:00
|
|
|
self.stop_event,
|
2022-11-03 22:23:09 -04:00
|
|
|
)
|
2020-11-04 06:28:07 -06:00
|
|
|
|
2023-07-08 07:04:47 -05:00
|
|
|
def start_ptz_autotracker(self) -> None:
|
|
|
|
|
self.ptz_autotracker_thread = PtzAutoTrackerThread(
|
|
|
|
|
self.config,
|
|
|
|
|
self.onvif_controller,
|
2023-07-11 06:23:20 -05:00
|
|
|
self.ptz_metrics,
|
2023-11-01 18:20:26 -05:00
|
|
|
self.dispatcher,
|
2023-07-08 07:04:47 -05:00
|
|
|
self.stop_event,
|
|
|
|
|
)
|
|
|
|
|
self.ptz_autotracker_thread.start()
|
|
|
|
|
|
2022-04-16 17:40:04 +02:00
|
|
|
def start_detected_frames_processor(self) -> None:
|
2021-02-17 07:23:32 -06:00
|
|
|
self.detected_frames_processor = TrackedObjectProcessor(
|
|
|
|
|
self.config,
|
2022-11-23 19:03:20 -07:00
|
|
|
self.dispatcher,
|
2021-02-17 07:23:32 -06:00
|
|
|
self.detected_frames_queue,
|
2023-07-08 07:04:47 -05:00
|
|
|
self.ptz_autotracker_thread,
|
2021-02-17 07:23:32 -06:00
|
|
|
self.stop_event,
|
|
|
|
|
)
|
2020-11-04 06:28:07 -06:00
|
|
|
self.detected_frames_processor.start()
|
|
|
|
|
|
2022-04-16 17:40:04 +02:00
|
|
|
def start_video_output_processor(self) -> None:
|
2025-06-24 11:41:11 -06:00
|
|
|
output_processor = OutputProcess(self.config, self.stop_event)
|
2021-05-29 13:27:00 -05:00
|
|
|
self.output_processor = output_processor
|
|
|
|
|
output_processor.start()
|
2021-05-30 06:45:37 -05:00
|
|
|
logger.info(f"Output process started: {output_processor.pid}")
|
2021-05-29 13:27:00 -05:00
|
|
|
|
2025-06-11 11:25:30 -06:00
|
|
|
def start_camera_processor(self) -> None:
|
|
|
|
|
self.camera_maintainer = CameraMaintainer(
|
|
|
|
|
self.config,
|
|
|
|
|
self.detection_queue,
|
|
|
|
|
self.detected_frames_queue,
|
|
|
|
|
self.camera_metrics,
|
|
|
|
|
self.ptz_metrics,
|
|
|
|
|
self.stop_event,
|
2025-06-21 15:38:34 -05:00
|
|
|
self.metrics_manager,
|
2025-06-11 11:25:30 -06:00
|
|
|
)
|
|
|
|
|
self.camera_maintainer.start()
|
2021-02-17 07:23:32 -06:00
|
|
|
|
2024-09-27 15:53:23 +03:00
|
|
|
def start_audio_processor(self) -> None:
|
2026-05-12 11:20:39 -05:00
|
|
|
self.audio_process = AudioProcessor(
|
|
|
|
|
self.config, self.camera_metrics, self.stop_event
|
|
|
|
|
)
|
|
|
|
|
self.audio_process.start()
|
|
|
|
|
self.processes["audio_detector"] = self.audio_process.pid or 0
|
2023-07-01 07:18:33 -06:00
|
|
|
|
2023-04-23 09:45:19 -06:00
|
|
|
def start_timeline_processor(self) -> None:
|
|
|
|
|
self.timeline_processor = TimelineProcessor(
|
|
|
|
|
self.config, self.timeline_queue, self.stop_event
|
|
|
|
|
)
|
|
|
|
|
self.timeline_processor.start()
|
|
|
|
|
|
2022-04-16 17:40:04 +02:00
|
|
|
def start_event_processor(self) -> None:
|
2021-02-17 07:23:32 -06:00
|
|
|
self.event_processor = EventProcessor(
|
|
|
|
|
self.config,
|
2023-04-23 09:45:19 -06:00
|
|
|
self.timeline_queue,
|
2021-02-17 07:23:32 -06:00
|
|
|
self.stop_event,
|
|
|
|
|
)
|
2020-11-04 06:28:07 -06:00
|
|
|
self.event_processor.start()
|
2021-02-17 07:23:32 -06:00
|
|
|
|
2022-04-16 17:40:04 +02:00
|
|
|
def start_event_cleanup(self) -> None:
|
2024-10-07 15:30:45 -05:00
|
|
|
self.event_cleanup = EventCleanup(self.config, self.stop_event, self.db)
|
2020-11-24 07:27:51 -06:00
|
|
|
self.event_cleanup.start()
|
2021-02-17 07:23:32 -06:00
|
|
|
|
2023-07-26 04:55:08 -06:00
|
|
|
def start_record_cleanup(self) -> None:
|
|
|
|
|
self.record_cleanup = RecordingCleanup(self.config, self.stop_event)
|
|
|
|
|
self.record_cleanup.start()
|
|
|
|
|
|
2022-10-09 05:28:26 -06:00
|
|
|
def start_storage_maintainer(self) -> None:
|
|
|
|
|
self.storage_maintainer = StorageMaintainer(self.config, self.stop_event)
|
|
|
|
|
self.storage_maintainer.start()
|
|
|
|
|
|
2022-04-16 17:40:04 +02:00
|
|
|
def start_stats_emitter(self) -> None:
|
2021-02-17 07:23:32 -06:00
|
|
|
self.stats_emitter = StatsEmitter(
|
|
|
|
|
self.config,
|
2024-02-21 13:10:28 -07:00
|
|
|
stats_init(
|
2025-01-05 08:47:57 -06:00
|
|
|
self.config,
|
|
|
|
|
self.camera_metrics,
|
|
|
|
|
self.embeddings_metrics,
|
|
|
|
|
self.detectors,
|
|
|
|
|
self.processes,
|
2024-02-21 13:10:28 -07:00
|
|
|
),
|
2021-02-17 07:23:32 -06:00
|
|
|
self.stop_event,
|
|
|
|
|
)
|
2021-01-03 23:35:58 +00:00
|
|
|
self.stats_emitter.start()
|
|
|
|
|
|
2022-04-16 17:40:04 +02:00
|
|
|
def start_watchdog(self) -> None:
|
2020-11-04 06:28:07 -06:00
|
|
|
self.frigate_watchdog = FrigateWatchdog(self.detectors, self.stop_event)
|
2026-03-20 19:02:47 -05:00
|
|
|
|
|
|
|
|
# (attribute on self, key in self.processes, factory)
|
|
|
|
|
specs: list[tuple[str, str, Callable[[], FrigateProcess]]] = [
|
|
|
|
|
(
|
|
|
|
|
"embedding_process",
|
|
|
|
|
"embeddings",
|
|
|
|
|
lambda: EmbeddingProcess(
|
|
|
|
|
self.config, self.embeddings_metrics, self.stop_event
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"recording_process",
|
|
|
|
|
"recording",
|
|
|
|
|
lambda: RecordProcess(self.config, self.stop_event),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"review_segment_process",
|
|
|
|
|
"review_segment",
|
|
|
|
|
lambda: ReviewProcess(self.config, self.stop_event),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"output_processor",
|
|
|
|
|
"output",
|
|
|
|
|
lambda: OutputProcess(self.config, self.stop_event),
|
|
|
|
|
),
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
for attr, key, factory in specs:
|
|
|
|
|
if not hasattr(self, attr):
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
def on_restart(
|
|
|
|
|
proc: FrigateProcess, _attr: str = attr, _key: str = key
|
|
|
|
|
) -> None:
|
|
|
|
|
setattr(self, _attr, proc)
|
|
|
|
|
self.processes[_key] = proc.pid or 0
|
|
|
|
|
|
|
|
|
|
self.frigate_watchdog.register(
|
|
|
|
|
key, getattr(self, attr), factory, on_restart
|
|
|
|
|
)
|
|
|
|
|
|
2020-11-04 06:28:07 -06:00
|
|
|
self.frigate_watchdog.start()
|
|
|
|
|
|
2024-05-18 11:36:13 -05:00
|
|
|
def init_auth(self) -> None:
|
2024-06-14 18:02:13 -05:00
|
|
|
if self.config.auth.enabled:
|
2024-05-18 11:36:13 -05:00
|
|
|
if User.select().count() == 0:
|
|
|
|
|
password = secrets.token_hex(16)
|
|
|
|
|
password_hash = hash_password(
|
|
|
|
|
password, iterations=self.config.auth.hash_iterations
|
|
|
|
|
)
|
|
|
|
|
User.insert(
|
|
|
|
|
{
|
|
|
|
|
User.username: "admin",
|
2025-03-09 22:59:07 -05:00
|
|
|
User.role: "admin",
|
2024-05-18 11:36:13 -05:00
|
|
|
User.password_hash: password_hash,
|
2024-10-07 07:18:09 -06:00
|
|
|
User.notification_tokens: [],
|
2024-05-18 11:36:13 -05:00
|
|
|
}
|
|
|
|
|
).execute()
|
|
|
|
|
|
2025-10-22 12:24:53 -05:00
|
|
|
self.config.auth.admin_first_time_login = True
|
|
|
|
|
|
2024-05-18 11:36:13 -05:00
|
|
|
logger.info("********************************************************")
|
|
|
|
|
logger.info("********************************************************")
|
|
|
|
|
logger.info("*** Auth is enabled, but no users exist. ***")
|
|
|
|
|
logger.info("*** Created a default user: ***")
|
|
|
|
|
logger.info("*** User: admin ***")
|
|
|
|
|
logger.info(f"*** Password: {password} ***")
|
|
|
|
|
logger.info("********************************************************")
|
|
|
|
|
logger.info("********************************************************")
|
|
|
|
|
elif self.config.auth.reset_admin_password:
|
|
|
|
|
password = secrets.token_hex(16)
|
|
|
|
|
password_hash = hash_password(
|
|
|
|
|
password, iterations=self.config.auth.hash_iterations
|
|
|
|
|
)
|
2024-10-07 07:18:09 -06:00
|
|
|
User.replace(
|
|
|
|
|
username="admin",
|
2025-03-08 10:01:08 -06:00
|
|
|
role="admin",
|
2024-10-07 07:18:09 -06:00
|
|
|
password_hash=password_hash,
|
|
|
|
|
notification_tokens=[],
|
|
|
|
|
).execute()
|
2024-05-18 11:36:13 -05:00
|
|
|
|
|
|
|
|
logger.info("********************************************************")
|
|
|
|
|
logger.info("********************************************************")
|
|
|
|
|
logger.info("*** Reset admin password set in the config. ***")
|
|
|
|
|
logger.info(f"*** Password: {password} ***")
|
|
|
|
|
logger.info("********************************************************")
|
|
|
|
|
logger.info("********************************************************")
|
|
|
|
|
|
2022-04-16 17:40:04 +02:00
|
|
|
def start(self) -> None:
|
2021-09-13 22:02:23 -05:00
|
|
|
logger.info(f"Starting Frigate ({VERSION})")
|
2024-02-10 05:30:53 -07:00
|
|
|
|
2024-09-24 15:07:47 +03:00
|
|
|
# Ensure global state.
|
|
|
|
|
self.ensure_dirs()
|
|
|
|
|
|
2025-07-11 08:11:35 -05:00
|
|
|
# Set soft file limits.
|
|
|
|
|
set_file_limit()
|
|
|
|
|
|
2024-09-24 15:07:47 +03:00
|
|
|
# Start frigate services.
|
2026-03-04 10:07:34 -06:00
|
|
|
self.init_debug_replay_manager()
|
2024-09-24 15:07:47 +03:00
|
|
|
self.init_camera_metrics()
|
|
|
|
|
self.init_queues()
|
|
|
|
|
self.init_database()
|
|
|
|
|
self.init_onvif()
|
|
|
|
|
self.init_recording_manager()
|
|
|
|
|
self.init_review_segment_manager()
|
|
|
|
|
self.init_go2rtc()
|
2024-10-10 15:37:43 -06:00
|
|
|
self.init_embeddings_manager()
|
2024-09-24 15:07:47 +03:00
|
|
|
self.bind_database()
|
|
|
|
|
self.check_db_data_migrations()
|
2026-03-04 10:07:34 -06:00
|
|
|
|
|
|
|
|
# Clean up any stale replay camera artifacts (filesystem + DB)
|
|
|
|
|
cleanup_replay_cameras()
|
|
|
|
|
|
2026-04-14 09:19:50 -05:00
|
|
|
# Reap any Export rows still marked in_progress from a previous
|
|
|
|
|
# session (crash, kill, broken migration). Runs synchronously before
|
|
|
|
|
# uvicorn binds so no API request can observe a stale row.
|
|
|
|
|
reap_stale_exports()
|
|
|
|
|
|
2024-09-24 15:07:47 +03:00
|
|
|
self.init_inter_process_communicator()
|
2025-06-12 12:12:34 -06:00
|
|
|
self.start_detectors()
|
2024-09-24 15:07:47 +03:00
|
|
|
self.init_dispatcher()
|
2026-03-19 09:47:57 -05:00
|
|
|
self.init_profile_manager()
|
2024-10-07 15:30:45 -05:00
|
|
|
self.init_embeddings_client()
|
2021-05-29 13:27:00 -05:00
|
|
|
self.start_video_output_processor()
|
2023-07-08 07:04:47 -05:00
|
|
|
self.start_ptz_autotracker()
|
2020-11-04 06:28:07 -06:00
|
|
|
self.start_detected_frames_processor()
|
2025-06-11 11:25:30 -06:00
|
|
|
self.start_camera_processor()
|
2024-09-27 15:53:23 +03:00
|
|
|
self.start_audio_processor()
|
2022-11-29 18:59:56 -07:00
|
|
|
self.start_storage_maintainer()
|
2024-02-21 13:10:28 -07:00
|
|
|
self.start_stats_emitter()
|
2023-04-23 09:45:19 -06:00
|
|
|
self.start_timeline_processor()
|
2020-11-04 06:28:07 -06:00
|
|
|
self.start_event_processor()
|
2020-11-24 07:27:51 -06:00
|
|
|
self.start_event_cleanup()
|
2023-07-26 04:55:08 -06:00
|
|
|
self.start_record_cleanup()
|
2020-11-04 06:28:07 -06:00
|
|
|
self.start_watchdog()
|
2024-09-24 15:07:47 +03:00
|
|
|
|
2026-05-27 13:03:09 -05:00
|
|
|
# restore persisted runtime overrides on top of config
|
2026-05-30 22:35:03 -05:00
|
|
|
self.restore_active_profile()
|
2026-05-27 13:03:09 -05:00
|
|
|
self.dispatcher.restore_runtime_state()
|
|
|
|
|
|
2024-05-18 11:36:13 -05:00
|
|
|
self.init_auth()
|
2020-12-05 11:14:18 -06:00
|
|
|
|
2021-05-18 01:52:08 -04:00
|
|
|
try:
|
2024-09-24 14:05:30 +01:00
|
|
|
uvicorn.run(
|
|
|
|
|
create_fastapi_app(
|
|
|
|
|
self.config,
|
|
|
|
|
self.db,
|
|
|
|
|
self.embeddings,
|
|
|
|
|
self.detected_frames_processor,
|
|
|
|
|
self.storage_maintainer,
|
|
|
|
|
self.onvif_controller,
|
|
|
|
|
self.stats_emitter,
|
2024-09-24 09:14:51 -05:00
|
|
|
self.event_metadata_updater,
|
2025-05-22 20:51:23 -06:00
|
|
|
self.inter_config_updater,
|
2026-03-04 10:07:34 -06:00
|
|
|
self.replay_manager,
|
2026-03-19 09:47:57 -05:00
|
|
|
self.dispatcher,
|
|
|
|
|
self.profile_manager,
|
2024-09-24 14:05:30 +01:00
|
|
|
),
|
|
|
|
|
host="127.0.0.1",
|
|
|
|
|
port=5001,
|
|
|
|
|
log_level="error",
|
|
|
|
|
)
|
2024-09-24 15:07:47 +03:00
|
|
|
finally:
|
|
|
|
|
self.stop()
|
2021-02-17 07:23:32 -06:00
|
|
|
|
2022-04-16 17:40:04 +02:00
|
|
|
def stop(self) -> None:
|
2023-05-29 12:31:17 +02:00
|
|
|
logger.info("Stopping...")
|
2024-06-06 18:54:38 -05:00
|
|
|
|
2025-05-29 23:58:31 -03:00
|
|
|
# used by the docker healthcheck
|
|
|
|
|
Path("/dev/shm/.frigate-is-stopping").touch()
|
|
|
|
|
|
2026-03-05 17:53:48 -06:00
|
|
|
# Cancel any running motion search jobs before setting stop_event
|
|
|
|
|
stop_all_motion_search_jobs()
|
|
|
|
|
|
2020-11-04 06:28:07 -06:00
|
|
|
self.stop_event.set()
|
|
|
|
|
|
2024-04-11 06:42:16 -06:00
|
|
|
# set an end_time on entries without an end_time before exiting
|
2024-05-09 07:20:33 -06:00
|
|
|
Event.update(
|
|
|
|
|
end_time=datetime.datetime.now().timestamp(), has_snapshot=False
|
|
|
|
|
).where(Event.end_time == None).execute()
|
2024-04-11 06:42:16 -06:00
|
|
|
ReviewSegment.update(end_time=datetime.datetime.now().timestamp()).where(
|
|
|
|
|
ReviewSegment.end_time == None
|
|
|
|
|
).execute()
|
|
|
|
|
|
2024-11-08 12:47:46 -06:00
|
|
|
# stop the audio process
|
|
|
|
|
if self.audio_process:
|
|
|
|
|
self.audio_process.terminate()
|
|
|
|
|
self.audio_process.join()
|
|
|
|
|
|
2025-05-07 08:53:29 -05:00
|
|
|
# stop the onvif controller
|
|
|
|
|
if self.onvif_controller:
|
|
|
|
|
self.onvif_controller.close()
|
|
|
|
|
|
2024-06-06 18:54:38 -05:00
|
|
|
# ensure the detectors are done
|
2023-02-04 08:58:45 -06:00
|
|
|
for detector in self.detectors.values():
|
|
|
|
|
detector.stop()
|
|
|
|
|
|
2024-06-06 18:54:38 -05:00
|
|
|
empty_and_close_queue(self.detection_queue)
|
|
|
|
|
logger.info("Detection queue closed")
|
|
|
|
|
|
|
|
|
|
self.detected_frames_processor.join()
|
|
|
|
|
empty_and_close_queue(self.detected_frames_queue)
|
|
|
|
|
logger.info("Detected frames queue closed")
|
|
|
|
|
|
|
|
|
|
self.timeline_processor.join()
|
|
|
|
|
self.event_processor.join()
|
|
|
|
|
empty_and_close_queue(self.timeline_queue)
|
|
|
|
|
logger.info("Timeline queue closed")
|
|
|
|
|
|
|
|
|
|
self.output_processor.terminate()
|
|
|
|
|
self.output_processor.join()
|
|
|
|
|
|
|
|
|
|
self.recording_process.terminate()
|
|
|
|
|
self.recording_process.join()
|
|
|
|
|
|
|
|
|
|
self.review_segment_process.terminate()
|
|
|
|
|
self.review_segment_process.join()
|
2023-02-03 20:15:47 -06:00
|
|
|
|
|
|
|
|
self.dispatcher.stop()
|
2023-07-08 07:04:47 -05:00
|
|
|
self.ptz_autotracker_thread.join()
|
2024-06-06 18:54:38 -05:00
|
|
|
|
2020-11-24 07:27:51 -06:00
|
|
|
self.event_cleanup.join()
|
2023-07-26 04:55:08 -06:00
|
|
|
self.record_cleanup.join()
|
2021-01-03 23:35:58 +00:00
|
|
|
self.stats_emitter.join()
|
2020-11-04 06:28:07 -06:00
|
|
|
self.frigate_watchdog.join()
|
2026-03-04 10:07:34 -06:00
|
|
|
self.camera_maintainer.join()
|
2021-02-07 08:38:35 -06:00
|
|
|
self.db.stop()
|
2020-11-04 06:28:07 -06:00
|
|
|
|
2024-06-23 09:13:02 -04:00
|
|
|
# Save embeddings stats to disk
|
2024-08-03 21:06:20 -06:00
|
|
|
if self.embeddings:
|
2024-10-10 09:42:24 -06:00
|
|
|
self.embeddings.stop()
|
2024-06-23 09:13:02 -04:00
|
|
|
|
2024-06-06 18:54:38 -05:00
|
|
|
# Stop Communicators
|
|
|
|
|
self.inter_process_communicator.stop()
|
|
|
|
|
self.inter_config_updater.stop()
|
2024-09-24 09:14:51 -05:00
|
|
|
self.event_metadata_updater.stop()
|
2024-06-21 17:30:19 -04:00
|
|
|
self.inter_zmq_proxy.stop()
|
2025-06-12 12:12:34 -06:00
|
|
|
self.detection_proxy.stop()
|
2024-06-06 18:54:38 -05:00
|
|
|
|
2020-11-04 06:28:07 -06:00
|
|
|
while len(self.detection_shms) > 0:
|
|
|
|
|
shm = self.detection_shms.pop()
|
|
|
|
|
shm.close()
|
|
|
|
|
shm.unlink()
|
2023-02-03 20:15:47 -06:00
|
|
|
|
2025-05-29 10:02:17 -05:00
|
|
|
_stop_logging()
|
2025-06-12 12:12:34 -06:00
|
|
|
self.metrics_manager.shutdown()
|