Finalize process

This commit is contained in:
Nicolas Mowen 2025-06-12 11:52:14 -06:00
parent e8dd382280
commit 5ef4f2d440
4 changed files with 214 additions and 245 deletions

View File

@ -14,7 +14,6 @@ import uvicorn
from peewee_migrate import Router from peewee_migrate import Router
from playhouse.sqlite_ext import SqliteExtDatabase from playhouse.sqlite_ext import SqliteExtDatabase
import frigate.util as util
from frigate.api.auth import hash_password from frigate.api.auth import hash_password
from frigate.api.fastapi_app import create_fastapi_app from frigate.api.fastapi_app import create_fastapi_app
from frigate.camera import CameraMetrics, PTZMetrics from frigate.camera import CameraMetrics, PTZMetrics
@ -42,7 +41,7 @@ from frigate.const import (
) )
from frigate.data_processing.types import DataProcessorMetrics from frigate.data_processing.types import DataProcessorMetrics
from frigate.db.sqlitevecq import SqliteVecQueueDatabase from frigate.db.sqlitevecq import SqliteVecQueueDatabase
from frigate.embeddings import EmbeddingsContext, manage_embeddings from frigate.embeddings import EmbeddingProcess, EmbeddingsContext
from frigate.events.audio import AudioProcessor from frigate.events.audio import AudioProcessor
from frigate.events.cleanup import EventCleanup from frigate.events.cleanup import EventCleanup
from frigate.events.maintainer import EventProcessor from frigate.events.maintainer import EventProcessor
@ -59,12 +58,12 @@ from frigate.models import (
User, User,
) )
from frigate.object_detection.base import ObjectDetectProcess from frigate.object_detection.base import ObjectDetectProcess
from frigate.output.output import output_frames from frigate.output.output import OutputProcess
from frigate.ptz.autotrack import PtzAutoTrackerThread from frigate.ptz.autotrack import PtzAutoTrackerThread
from frigate.ptz.onvif import OnvifController from frigate.ptz.onvif import OnvifController
from frigate.record.cleanup import RecordingCleanup from frigate.record.cleanup import RecordingCleanup
from frigate.record.export import migrate_exports from frigate.record.export import migrate_exports
from frigate.record.record import manage_recordings from frigate.record.record import RecordProcess
from frigate.review.review import ReviewProcess from frigate.review.review import ReviewProcess
from frigate.stats.emitter import StatsEmitter from frigate.stats.emitter import StatsEmitter
from frigate.stats.util import stats_init from frigate.stats.util import stats_init
@ -224,11 +223,7 @@ class FrigateApp:
self.processes["go2rtc"] = proc.info["pid"] self.processes["go2rtc"] = proc.info["pid"]
def init_recording_manager(self) -> None: def init_recording_manager(self) -> None:
recording_process = util.Process( recording_process = RecordProcess(self.config)
target=manage_recordings,
name="recording_manager",
args=(self.config,),
)
recording_process.daemon = True recording_process.daemon = True
self.recording_process = recording_process self.recording_process = recording_process
recording_process.start() recording_process.start()
@ -255,13 +250,9 @@ class FrigateApp:
): ):
return return
embedding_process = util.Process( embedding_process = EmbeddingProcess(
target=manage_embeddings,
name="embeddings_manager",
args=(
self.config, self.config,
self.embeddings_metrics, self.embeddings_metrics,
),
) )
embedding_process.daemon = True embedding_process.daemon = True
self.embedding_process = embedding_process self.embedding_process = embedding_process
@ -420,12 +411,7 @@ class FrigateApp:
self.detected_frames_processor.start() self.detected_frames_processor.start()
def start_video_output_processor(self) -> None: def start_video_output_processor(self) -> None:
output_processor = util.Process( output_processor = OutputProcess(self.config)
target=output_frames,
name="output_processor",
args=(self.config,),
)
output_processor.daemon = True
self.output_processor = output_processor self.output_processor = output_processor
output_processor.start() output_processor.start()
logger.info(f"Output process started: {output_processor.pid}") logger.info(f"Output process started: {output_processor.pid}")

View File

@ -3,16 +3,12 @@
import base64 import base64
import json import json
import logging import logging
import multiprocessing as mp
import os import os
import signal
import threading import threading
from types import FrameType from typing import Any, Union
from typing import Any, Optional, Union
import regex import regex
from pathvalidate import ValidationError, sanitize_filename from pathvalidate import ValidationError, sanitize_filename
from setproctitle import setproctitle
from frigate.comms.embeddings_updater import EmbeddingsRequestEnum, EmbeddingsRequestor from frigate.comms.embeddings_updater import EmbeddingsRequestEnum, EmbeddingsRequestor
from frigate.config import FrigateConfig from frigate.config import FrigateConfig
@ -20,9 +16,9 @@ from frigate.const import CONFIG_DIR, FACE_DIR
from frigate.data_processing.types import DataProcessorMetrics from frigate.data_processing.types import DataProcessorMetrics
from frigate.db.sqlitevecq import SqliteVecQueueDatabase from frigate.db.sqlitevecq import SqliteVecQueueDatabase
from frigate.models import Event from frigate.models import Event
from frigate.util import Process as FrigateProcess
from frigate.util.builtin import serialize from frigate.util.builtin import serialize
from frigate.util.classification import kickoff_model_training from frigate.util.classification import kickoff_model_training
from frigate.util.services import listen
from .maintainer import EmbeddingMaintainer from .maintainer import EmbeddingMaintainer
from .util import ZScoreNormalization from .util import ZScoreNormalization
@ -30,23 +26,18 @@ from .util import ZScoreNormalization
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def manage_embeddings(config: FrigateConfig, metrics: DataProcessorMetrics) -> None: class EmbeddingProcess(FrigateProcess):
stop_event = mp.Event() def __init__(self, config: FrigateConfig, metrics: DataProcessorMetrics) -> None:
super().__init__(name="frigate.embeddings_manager", daemon=True)
def receiveSignal(signalNumber: int, frame: Optional[FrameType]) -> None: self.config = config
stop_event.set() self.metrics = metrics
signal.signal(signal.SIGTERM, receiveSignal)
signal.signal(signal.SIGINT, receiveSignal)
threading.current_thread().name = "process:embeddings_manager"
setproctitle("frigate.embeddings_manager")
listen()
def run(self) -> None:
self.pre_run_setup()
maintainer = EmbeddingMaintainer( maintainer = EmbeddingMaintainer(
config, self.config,
metrics, self.metrics,
stop_event, self.stop_event,
) )
maintainer.start() maintainer.start()

View File

@ -2,14 +2,11 @@
import datetime import datetime
import logging import logging
import multiprocessing as mp
import os import os
import shutil import shutil
import signal
import threading import threading
from wsgiref.simple_server import make_server from wsgiref.simple_server import make_server
from setproctitle import setproctitle
from ws4py.server.wsgirefserver import ( from ws4py.server.wsgirefserver import (
WebSocketWSGIHandler, WebSocketWSGIHandler,
WebSocketWSGIRequestHandler, WebSocketWSGIRequestHandler,
@ -17,6 +14,7 @@ from ws4py.server.wsgirefserver import (
) )
from ws4py.server.wsgiutils import WebSocketWSGIApplication from ws4py.server.wsgiutils import WebSocketWSGIApplication
import frigate.util as util
from frigate.comms.detections_updater import DetectionSubscriber, DetectionTypeEnum from frigate.comms.detections_updater import DetectionSubscriber, DetectionTypeEnum
from frigate.comms.ws import WebSocket from frigate.comms.ws import WebSocket
from frigate.config import FrigateConfig from frigate.config import FrigateConfig
@ -73,19 +71,13 @@ def check_disabled_camera_update(
birdseye.all_cameras_disabled() birdseye.all_cameras_disabled()
def output_frames( class OutputProcess(util.Process):
config: FrigateConfig, def __init__(self, config: FrigateConfig) -> None:
): super().__init__(name="frigate.output", daemon=True)
threading.current_thread().name = "output" self.config = config
setproctitle("frigate.output")
stop_event = mp.Event() def run(self) -> None:
self.pre_run_setup()
def receiveSignal(signalNumber, frame):
stop_event.set()
signal.signal(signal.SIGTERM, receiveSignal)
signal.signal(signal.SIGINT, receiveSignal)
frame_manager = SharedMemoryFrameManager() frame_manager = SharedMemoryFrameManager()
@ -103,8 +95,8 @@ def output_frames(
detection_subscriber = DetectionSubscriber(DetectionTypeEnum.video) detection_subscriber = DetectionSubscriber(DetectionTypeEnum.video)
config_subscriber = CameraConfigUpdateSubscriber( config_subscriber = CameraConfigUpdateSubscriber(
config, self.config,
config.cameras, self.config.cameras,
[ [
CameraConfigUpdateEnum.add, CameraConfigUpdateEnum.add,
CameraConfigUpdateEnum.birdseye, CameraConfigUpdateEnum.birdseye,
@ -122,27 +114,29 @@ def output_frames(
move_preview_frames("cache") move_preview_frames("cache")
for camera, cam_config in config.cameras.items(): for camera, cam_config in self.config.cameras.items():
if not cam_config.enabled_in_config: if not cam_config.enabled_in_config:
continue continue
jsmpeg_cameras[camera] = JsmpegCamera(cam_config, stop_event, websocket_server) jsmpeg_cameras[camera] = JsmpegCamera(
cam_config, self.stop_event, websocket_server
)
preview_recorders[camera] = PreviewRecorder(cam_config) preview_recorders[camera] = PreviewRecorder(cam_config)
preview_write_times[camera] = 0 preview_write_times[camera] = 0
if config.birdseye.enabled: if self.config.birdseye.enabled:
birdseye = Birdseye(config, stop_event, websocket_server) birdseye = Birdseye(self.config, self.stop_event, websocket_server)
websocket_thread.start() websocket_thread.start()
while not stop_event.is_set(): while not self.stop_event.is_set():
# check if there is an updated config # check if there is an updated config
updates = config_subscriber.check_for_updates() updates = config_subscriber.check_for_updates()
if "add" in updates: if "add" in updates:
for camera in updates["add"]: for camera in updates["add"]:
jsmpeg_cameras[camera] = JsmpegCamera( jsmpeg_cameras[camera] = JsmpegCamera(
cam_config, stop_event, websocket_server cam_config, self.stop_event, websocket_server
) )
preview_recorders[camera] = PreviewRecorder(cam_config) preview_recorders[camera] = PreviewRecorder(cam_config)
preview_write_times[camera] = 0 preview_write_times[camera] = 0
@ -154,7 +148,7 @@ def output_frames(
# check disabled cameras every 5 seconds # check disabled cameras every 5 seconds
last_disabled_cam_check = now last_disabled_cam_check = now
check_disabled_camera_update( check_disabled_camera_update(
config, birdseye, preview_recorders, preview_write_times self.config, birdseye, preview_recorders, preview_write_times
) )
if not topic: if not topic:
@ -169,16 +163,21 @@ def output_frames(
_, _,
) = data ) = data
if not config.cameras[camera].enabled: if not self.config.cameras[camera].enabled:
continue continue
frame = frame_manager.get(frame_name, config.cameras[camera].frame_shape_yuv) frame = frame_manager.get(
frame_name, self.config.cameras[camera].frame_shape_yuv
)
if frame is None: if frame is None:
logger.debug(f"Failed to get frame {frame_name} from SHM") logger.debug(f"Failed to get frame {frame_name} from SHM")
failed_frame_requests[camera] = failed_frame_requests.get(camera, 0) + 1 failed_frame_requests[camera] = failed_frame_requests.get(camera, 0) + 1
if failed_frame_requests[camera] > config.cameras[camera].detect.fps: if (
failed_frame_requests[camera]
> self.config.cameras[camera].detect.fps
):
logger.warning( logger.warning(
f"Failed to retrieve many frames for {camera} from SHM, consider increasing SHM size if this continues." f"Failed to retrieve many frames for {camera} from SHM, consider increasing SHM size if this continues."
) )
@ -195,14 +194,15 @@ def output_frames(
# send camera frame to ffmpeg process if websockets are connected # send camera frame to ffmpeg process if websockets are connected
if any( if any(
ws.environ["PATH_INFO"].endswith(camera) for ws in websocket_server.manager ws.environ["PATH_INFO"].endswith(camera)
for ws in websocket_server.manager
): ):
# write to the converter for the camera if clients are listening to the specific camera # write to the converter for the camera if clients are listening to the specific camera
jsmpeg_cameras[camera].write_frame(frame.tobytes()) jsmpeg_cameras[camera].write_frame(frame.tobytes())
# send output data to birdseye if websocket is connected or restreaming # send output data to birdseye if websocket is connected or restreaming
if config.birdseye.enabled and ( if self.config.birdseye.enabled and (
config.birdseye.restream self.config.birdseye.restream
or any( or any(
ws.environ["PATH_INFO"].endswith("birdseye") ws.environ["PATH_INFO"].endswith("birdseye")
for ws in websocket_server.manager for ws in websocket_server.manager
@ -235,7 +235,9 @@ def output_frames(
regions, regions,
) = data ) = data
frame = frame_manager.get(frame_name, config.cameras[camera].frame_shape_yuv) frame = frame_manager.get(
frame_name, self.config.cameras[camera].frame_shape_yuv
)
frame_manager.close(frame_name) frame_manager.close(frame_name)
detection_subscriber.stop() detection_subscriber.stop()

View File

@ -1,50 +1,40 @@
"""Run recording maintainer and cleanup.""" """Run recording maintainer and cleanup."""
import logging import logging
import multiprocessing as mp
import signal
import threading
from types import FrameType
from typing import Optional
from playhouse.sqliteq import SqliteQueueDatabase from playhouse.sqliteq import SqliteQueueDatabase
from setproctitle import setproctitle
from frigate.config import FrigateConfig from frigate.config import FrigateConfig
from frigate.models import Recordings, ReviewSegment from frigate.models import Recordings, ReviewSegment
from frigate.record.maintainer import RecordingMaintainer from frigate.record.maintainer import RecordingMaintainer
from frigate.util.services import listen from frigate.util import Process as FrigateProcess
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def manage_recordings(config: FrigateConfig) -> None: class RecordProcess(FrigateProcess):
stop_event = mp.Event() def __init__(self, config: FrigateConfig) -> None:
super().__init__(name="frigate.recording_manager", daemon=True)
def receiveSignal(signalNumber: int, frame: Optional[FrameType]) -> None: self.config = config
stop_event.set()
signal.signal(signal.SIGTERM, receiveSignal)
signal.signal(signal.SIGINT, receiveSignal)
threading.current_thread().name = "process:recording_manager"
setproctitle("frigate.recording_manager")
listen()
def run(self) -> None:
self.pre_run_setup()
db = SqliteQueueDatabase( db = SqliteQueueDatabase(
config.database.path, self.config.database.path,
pragmas={ pragmas={
"auto_vacuum": "FULL", # Does not defragment database "auto_vacuum": "FULL", # Does not defragment database
"cache_size": -512 * 1000, # 512MB of cache "cache_size": -512 * 1000, # 512MB of cache
"synchronous": "NORMAL", # Safe when using WAL https://www.sqlite.org/pragma.html#pragma_synchronous "synchronous": "NORMAL", # Safe when using WAL https://www.sqlite.org/pragma.html#pragma_synchronous
}, },
timeout=max(60, 10 * len([c for c in config.cameras.values() if c.enabled])), timeout=max(
60, 10 * len([c for c in self.config.cameras.values() if c.enabled])
),
) )
models = [ReviewSegment, Recordings] models = [ReviewSegment, Recordings]
db.bind(models) db.bind(models)
maintainer = RecordingMaintainer( maintainer = RecordingMaintainer(
config, self.config,
stop_event, self.stop_event,
) )
maintainer.start() maintainer.start()