mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-26 19:06:11 +01:00
Move more things out of FrigateApp (#13897)
* Moved FrigateApp.init_config() into FrigateConfig.load() * Move frigate config loading into main * Store PlusApi in FrigateConfig * Register SIGTERM handler in main * Ensure logging is setup during config parsing * Removed pointless try * Moved config initialization out of FrigateApp * Made FrigateApp.shm_frame_count into a function * Removed log calls from signal handlers python's logging calls are not re-entrant, which caused at least one of these to deadlock randomly. * Reopen stdout/err on process fork This helps avoid deadlocks (https://github.com/python/cpython/issues/91776). * Make mypy happy * Whoops. I might have forgotten to save. Truly an amateur mistake. * Always call FrigateApp.stop()
This commit is contained in:
parent
a7ed90f042
commit
dc54981784
@ -1,10 +1,16 @@
|
|||||||
|
import argparse
|
||||||
import faulthandler
|
import faulthandler
|
||||||
import logging
|
import logging
|
||||||
|
import signal
|
||||||
|
import sys
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from flask import cli
|
from flask import cli
|
||||||
|
from pydantic import ValidationError
|
||||||
|
|
||||||
from frigate.app import FrigateApp
|
from frigate.app import FrigateApp
|
||||||
|
from frigate.config import FrigateConfig
|
||||||
|
from frigate.log import log_thread
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
@ -20,8 +26,50 @@ def main() -> None:
|
|||||||
threading.current_thread().name = "frigate"
|
threading.current_thread().name = "frigate"
|
||||||
cli.show_server_banner = lambda *x: None
|
cli.show_server_banner = lambda *x: None
|
||||||
|
|
||||||
|
# Make sure we exit cleanly on SIGTERM.
|
||||||
|
signal.signal(signal.SIGTERM, lambda sig, frame: sys.exit())
|
||||||
|
|
||||||
|
run()
|
||||||
|
|
||||||
|
|
||||||
|
@log_thread()
|
||||||
|
def run() -> None:
|
||||||
|
# Parse the cli arguments.
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog="Frigate",
|
||||||
|
description="An NVR with realtime local object detection for IP cameras.",
|
||||||
|
)
|
||||||
|
parser.add_argument("--validate-config", action="store_true")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Load the configuration.
|
||||||
|
try:
|
||||||
|
config = FrigateConfig.load()
|
||||||
|
except ValidationError as e:
|
||||||
|
print("*************************************************************")
|
||||||
|
print("*************************************************************")
|
||||||
|
print("*** Your config file is not valid! ***")
|
||||||
|
print("*** Please check the docs at ***")
|
||||||
|
print("*** https://docs.frigate.video/configuration/ ***")
|
||||||
|
print("*************************************************************")
|
||||||
|
print("*************************************************************")
|
||||||
|
print("*** Config Validation Errors ***")
|
||||||
|
print("*************************************************************")
|
||||||
|
for error in e.errors():
|
||||||
|
location = ".".join(str(item) for item in error["loc"])
|
||||||
|
print(f"{location}: {error['msg']}")
|
||||||
|
print("*************************************************************")
|
||||||
|
print("*** End Config Validation Errors ***")
|
||||||
|
print("*************************************************************")
|
||||||
|
sys.exit(1)
|
||||||
|
if args.validate_config:
|
||||||
|
print("*************************************************************")
|
||||||
|
print("*** Your config file is valid. ***")
|
||||||
|
print("*************************************************************")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
# Run the main application.
|
# Run the main application.
|
||||||
FrigateApp().start()
|
FrigateApp(config).start()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
@ -28,7 +28,6 @@ from frigate.const import CONFIG_DIR
|
|||||||
from frigate.embeddings import EmbeddingsContext
|
from frigate.embeddings import EmbeddingsContext
|
||||||
from frigate.events.external import ExternalEventProcessor
|
from frigate.events.external import ExternalEventProcessor
|
||||||
from frigate.models import Event, Timeline
|
from frigate.models import Event, Timeline
|
||||||
from frigate.plus import PlusApi
|
|
||||||
from frigate.ptz.onvif import OnvifController
|
from frigate.ptz.onvif import OnvifController
|
||||||
from frigate.stats.emitter import StatsEmitter
|
from frigate.stats.emitter import StatsEmitter
|
||||||
from frigate.storage import StorageMaintainer
|
from frigate.storage import StorageMaintainer
|
||||||
@ -61,7 +60,6 @@ def create_app(
|
|||||||
storage_maintainer: StorageMaintainer,
|
storage_maintainer: StorageMaintainer,
|
||||||
onvif: OnvifController,
|
onvif: OnvifController,
|
||||||
external_processor: ExternalEventProcessor,
|
external_processor: ExternalEventProcessor,
|
||||||
plus_api: PlusApi,
|
|
||||||
stats_emitter: StatsEmitter,
|
stats_emitter: StatsEmitter,
|
||||||
):
|
):
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
@ -89,7 +87,6 @@ def create_app(
|
|||||||
app.storage_maintainer = storage_maintainer
|
app.storage_maintainer = storage_maintainer
|
||||||
app.onvif = onvif
|
app.onvif = onvif
|
||||||
app.external_processor = external_processor
|
app.external_processor = external_processor
|
||||||
app.plus_api = plus_api
|
|
||||||
app.camera_error_image = None
|
app.camera_error_image = None
|
||||||
app.stats_emitter = stats_emitter
|
app.stats_emitter = stats_emitter
|
||||||
app.jwt_token = get_jwt_secret() if frigate_config.auth.enabled else None
|
app.jwt_token = get_jwt_secret() if frigate_config.auth.enabled else None
|
||||||
@ -199,7 +196,7 @@ def config():
|
|||||||
for zone_name, zone in config_obj.cameras[camera_name].zones.items():
|
for zone_name, zone in config_obj.cameras[camera_name].zones.items():
|
||||||
camera_dict["zones"][zone_name]["color"] = zone.color
|
camera_dict["zones"][zone_name]["color"] = zone.color
|
||||||
|
|
||||||
config["plus"] = {"enabled": current_app.plus_api.is_active()}
|
config["plus"] = {"enabled": current_app.frigate_config.plus_api.is_active()}
|
||||||
config["model"]["colormap"] = config_obj.model.colormap
|
config["model"]["colormap"] = config_obj.model.colormap
|
||||||
|
|
||||||
for detector_config in config["detectors"].values():
|
for detector_config in config["detectors"].values():
|
||||||
@ -362,7 +359,7 @@ def config_set():
|
|||||||
|
|
||||||
if json.get("requires_restart", 1) == 0:
|
if json.get("requires_restart", 1) == 0:
|
||||||
current_app.frigate_config = FrigateConfig.parse_object(
|
current_app.frigate_config = FrigateConfig.parse_object(
|
||||||
config_obj, plus_api=current_app.plus_api
|
config_obj, plus_api=current_app.frigate_config.plus_api
|
||||||
)
|
)
|
||||||
|
|
||||||
return make_response(
|
return make_response(
|
||||||
|
@ -612,7 +612,7 @@ def set_retain(id):
|
|||||||
|
|
||||||
@EventBp.route("/events/<id>/plus", methods=("POST",))
|
@EventBp.route("/events/<id>/plus", methods=("POST",))
|
||||||
def send_to_plus(id):
|
def send_to_plus(id):
|
||||||
if not current_app.plus_api.is_active():
|
if not current_app.frigate_config.plus_api.is_active():
|
||||||
message = "PLUS_API_KEY environment variable is not set"
|
message = "PLUS_API_KEY environment variable is not set"
|
||||||
logger.error(message)
|
logger.error(message)
|
||||||
return make_response(
|
return make_response(
|
||||||
@ -680,7 +680,7 @@ def send_to_plus(id):
|
|||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
plus_id = current_app.plus_api.upload_image(image, event.camera)
|
plus_id = current_app.frigate_config.plus_api.upload_image(image, event.camera)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
logger.exception(ex)
|
logger.exception(ex)
|
||||||
return make_response(
|
return make_response(
|
||||||
@ -696,7 +696,7 @@ def send_to_plus(id):
|
|||||||
box = event.data["box"]
|
box = event.data["box"]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
current_app.plus_api.add_annotation(
|
current_app.frigate_config.plus_api.add_annotation(
|
||||||
event.plus_id,
|
event.plus_id,
|
||||||
box,
|
box,
|
||||||
event.label,
|
event.label,
|
||||||
@ -720,7 +720,7 @@ def send_to_plus(id):
|
|||||||
|
|
||||||
@EventBp.route("/events/<id>/false_positive", methods=("PUT",))
|
@EventBp.route("/events/<id>/false_positive", methods=("PUT",))
|
||||||
def false_positive(id):
|
def false_positive(id):
|
||||||
if not current_app.plus_api.is_active():
|
if not current_app.frigate_config.plus_api.is_active():
|
||||||
message = "PLUS_API_KEY environment variable is not set"
|
message = "PLUS_API_KEY environment variable is not set"
|
||||||
logger.error(message)
|
logger.error(message)
|
||||||
return make_response(
|
return make_response(
|
||||||
@ -769,7 +769,7 @@ def false_positive(id):
|
|||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
current_app.plus_api.add_false_positive(
|
current_app.frigate_config.plus_api.add_false_positive(
|
||||||
event.plus_id,
|
event.plus_id,
|
||||||
region,
|
region,
|
||||||
box,
|
box,
|
||||||
|
@ -294,7 +294,7 @@ def submit_recording_snapshot_to_plus(camera_name: str, frame_time: str):
|
|||||||
)
|
)
|
||||||
|
|
||||||
nd = cv2.imdecode(np.frombuffer(image_data, dtype=np.int8), cv2.IMREAD_COLOR)
|
nd = cv2.imdecode(np.frombuffer(image_data, dtype=np.int8), cv2.IMREAD_COLOR)
|
||||||
current_app.plus_api.upload_image(nd, camera_name)
|
current_app.frigate_config.plus_api.upload_image(nd, camera_name)
|
||||||
|
|
||||||
return make_response(
|
return make_response(
|
||||||
jsonify(
|
jsonify(
|
||||||
|
177
frigate/app.py
177
frigate/app.py
@ -1,23 +1,17 @@
|
|||||||
import argparse
|
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
import multiprocessing as mp
|
import multiprocessing as mp
|
||||||
import os
|
import os
|
||||||
import secrets
|
import secrets
|
||||||
import shutil
|
import shutil
|
||||||
import signal
|
|
||||||
import sys
|
|
||||||
import traceback
|
|
||||||
from multiprocessing import Queue
|
from multiprocessing import Queue
|
||||||
from multiprocessing.synchronize import Event as MpEvent
|
from multiprocessing.synchronize import Event as MpEvent
|
||||||
from types import FrameType
|
from typing import Any
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import psutil
|
import psutil
|
||||||
from peewee_migrate import Router
|
from peewee_migrate import Router
|
||||||
from playhouse.sqlite_ext import SqliteExtDatabase
|
from playhouse.sqlite_ext import SqliteExtDatabase
|
||||||
from playhouse.sqliteq import SqliteQueueDatabase
|
from playhouse.sqliteq import SqliteQueueDatabase
|
||||||
from pydantic import ValidationError
|
|
||||||
|
|
||||||
from frigate.api.app import create_app
|
from frigate.api.app import create_app
|
||||||
from frigate.api.auth import hash_password
|
from frigate.api.auth import hash_password
|
||||||
@ -28,7 +22,6 @@ from frigate.comms.mqtt import MqttClient
|
|||||||
from frigate.comms.webpush import WebPushClient
|
from frigate.comms.webpush import WebPushClient
|
||||||
from frigate.comms.ws import WebSocketClient
|
from frigate.comms.ws import WebSocketClient
|
||||||
from frigate.comms.zmq_proxy import ZmqProxy
|
from frigate.comms.zmq_proxy import ZmqProxy
|
||||||
from frigate.config import FrigateConfig
|
|
||||||
from frigate.const import (
|
from frigate.const import (
|
||||||
CACHE_DIR,
|
CACHE_DIR,
|
||||||
CLIPS_DIR,
|
CLIPS_DIR,
|
||||||
@ -43,7 +36,6 @@ from frigate.events.audio import listen_to_audio
|
|||||||
from frigate.events.cleanup import EventCleanup
|
from frigate.events.cleanup import EventCleanup
|
||||||
from frigate.events.external import ExternalEventProcessor
|
from frigate.events.external import ExternalEventProcessor
|
||||||
from frigate.events.maintainer import EventProcessor
|
from frigate.events.maintainer import EventProcessor
|
||||||
from frigate.log import log_thread
|
|
||||||
from frigate.models import (
|
from frigate.models import (
|
||||||
Event,
|
Event,
|
||||||
Export,
|
Export,
|
||||||
@ -58,7 +50,6 @@ from frigate.models import (
|
|||||||
from frigate.object_detection import ObjectDetectProcess
|
from frigate.object_detection import ObjectDetectProcess
|
||||||
from frigate.object_processing import TrackedObjectProcessor
|
from frigate.object_processing import TrackedObjectProcessor
|
||||||
from frigate.output.output import output_frames
|
from frigate.output.output import output_frames
|
||||||
from frigate.plus import PlusApi
|
|
||||||
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
|
||||||
@ -70,8 +61,7 @@ from frigate.stats.util import stats_init
|
|||||||
from frigate.storage import StorageMaintainer
|
from frigate.storage import StorageMaintainer
|
||||||
from frigate.timeline import TimelineProcessor
|
from frigate.timeline import TimelineProcessor
|
||||||
from frigate.types import CameraMetricsTypes, PTZMetricsTypes
|
from frigate.types import CameraMetricsTypes, PTZMetricsTypes
|
||||||
from frigate.util.builtin import empty_and_close_queue, save_default_config
|
from frigate.util.builtin import empty_and_close_queue
|
||||||
from frigate.util.config import migrate_frigate_config
|
|
||||||
from frigate.util.object import get_camera_regions_grid
|
from frigate.util.object import get_camera_regions_grid
|
||||||
from frigate.version import VERSION
|
from frigate.version import VERSION
|
||||||
from frigate.video import capture_camera, track_camera
|
from frigate.video import capture_camera, track_camera
|
||||||
@ -81,22 +71,19 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class FrigateApp:
|
class FrigateApp:
|
||||||
def __init__(self) -> None:
|
# TODO: Fix FrigateConfig usage, so we can properly annotate it here without mypy erroring out.
|
||||||
|
def __init__(self, config: Any) -> None:
|
||||||
self.stop_event: MpEvent = mp.Event()
|
self.stop_event: MpEvent = mp.Event()
|
||||||
self.detection_queue: Queue = mp.Queue()
|
self.detection_queue: Queue = mp.Queue()
|
||||||
self.detectors: dict[str, ObjectDetectProcess] = {}
|
self.detectors: dict[str, ObjectDetectProcess] = {}
|
||||||
self.detection_out_events: dict[str, MpEvent] = {}
|
self.detection_out_events: dict[str, MpEvent] = {}
|
||||||
self.detection_shms: list[mp.shared_memory.SharedMemory] = []
|
self.detection_shms: list[mp.shared_memory.SharedMemory] = []
|
||||||
self.log_queue: Queue = mp.Queue()
|
self.log_queue: Queue = mp.Queue()
|
||||||
self.plus_api = PlusApi()
|
|
||||||
self.camera_metrics: dict[str, CameraMetricsTypes] = {}
|
self.camera_metrics: dict[str, CameraMetricsTypes] = {}
|
||||||
self.ptz_metrics: dict[str, PTZMetricsTypes] = {}
|
self.ptz_metrics: dict[str, PTZMetricsTypes] = {}
|
||||||
self.processes: dict[str, int] = {}
|
self.processes: dict[str, int] = {}
|
||||||
self.region_grids: dict[str, list[list[dict[str, int]]]] = {}
|
self.region_grids: dict[str, list[list[dict[str, int]]]] = {}
|
||||||
|
self.config = config
|
||||||
def set_environment_vars(self) -> None:
|
|
||||||
for key, value in self.config.environment_vars.items():
|
|
||||||
os.environ[key] = value
|
|
||||||
|
|
||||||
def ensure_dirs(self) -> None:
|
def ensure_dirs(self) -> None:
|
||||||
for d in [
|
for d in [
|
||||||
@ -113,24 +100,7 @@ class FrigateApp:
|
|||||||
else:
|
else:
|
||||||
logger.debug(f"Skipping directory: {d}")
|
logger.debug(f"Skipping directory: {d}")
|
||||||
|
|
||||||
def init_config(self) -> None:
|
def init_camera_metrics(self) -> None:
|
||||||
config_file = os.environ.get("CONFIG_FILE", "/config/config.yml")
|
|
||||||
|
|
||||||
# Check if we can use .yaml instead of .yml
|
|
||||||
config_file_yaml = config_file.replace(".yml", ".yaml")
|
|
||||||
if os.path.isfile(config_file_yaml):
|
|
||||||
config_file = config_file_yaml
|
|
||||||
|
|
||||||
if not os.path.isfile(config_file):
|
|
||||||
print("No config file found, saving default config")
|
|
||||||
config_file = config_file_yaml
|
|
||||||
save_default_config(config_file)
|
|
||||||
|
|
||||||
# check if the config file needs to be migrated
|
|
||||||
migrate_frigate_config(config_file)
|
|
||||||
|
|
||||||
self.config = FrigateConfig.parse_file(config_file, plus_api=self.plus_api)
|
|
||||||
|
|
||||||
for camera_name in self.config.cameras.keys():
|
for camera_name in self.config.cameras.keys():
|
||||||
# create camera_metrics
|
# create camera_metrics
|
||||||
self.camera_metrics[camera_name] = {
|
self.camera_metrics[camera_name] = {
|
||||||
@ -190,17 +160,6 @@ class FrigateApp:
|
|||||||
}
|
}
|
||||||
self.ptz_metrics[camera_name]["ptz_motor_stopped"].set()
|
self.ptz_metrics[camera_name]["ptz_motor_stopped"].set()
|
||||||
|
|
||||||
def set_log_levels(self) -> None:
|
|
||||||
logging.getLogger().setLevel(self.config.logger.default.value.upper())
|
|
||||||
for log, level in self.config.logger.logs.items():
|
|
||||||
logging.getLogger(log).setLevel(level.value.upper())
|
|
||||||
|
|
||||||
if "werkzeug" not in self.config.logger.logs:
|
|
||||||
logging.getLogger("werkzeug").setLevel("ERROR")
|
|
||||||
|
|
||||||
if "ws4py" not in self.config.logger.logs:
|
|
||||||
logging.getLogger("ws4py").setLevel("ERROR")
|
|
||||||
|
|
||||||
def init_queues(self) -> None:
|
def init_queues(self) -> None:
|
||||||
# Queue for cameras to push tracked objects to
|
# Queue for cameras to push tracked objects to
|
||||||
self.detected_frames_queue: Queue = mp.Queue(
|
self.detected_frames_queue: Queue = mp.Queue(
|
||||||
@ -374,19 +333,6 @@ class FrigateApp:
|
|||||||
self.inter_config_updater = ConfigPublisher()
|
self.inter_config_updater = ConfigPublisher()
|
||||||
self.inter_zmq_proxy = ZmqProxy()
|
self.inter_zmq_proxy = ZmqProxy()
|
||||||
|
|
||||||
def init_web_server(self) -> None:
|
|
||||||
self.flask_app = create_app(
|
|
||||||
self.config,
|
|
||||||
self.db,
|
|
||||||
self.embeddings,
|
|
||||||
self.detected_frames_processor,
|
|
||||||
self.storage_maintainer,
|
|
||||||
self.onvif_controller,
|
|
||||||
self.external_event_processor,
|
|
||||||
self.plus_api,
|
|
||||||
self.stats_emitter,
|
|
||||||
)
|
|
||||||
|
|
||||||
def init_onvif(self) -> None:
|
def init_onvif(self) -> None:
|
||||||
self.onvif_controller = OnvifController(self.config, self.ptz_metrics)
|
self.onvif_controller = OnvifController(self.config, self.ptz_metrics)
|
||||||
|
|
||||||
@ -527,7 +473,7 @@ class FrigateApp:
|
|||||||
capture_process = mp.Process(
|
capture_process = mp.Process(
|
||||||
target=capture_camera,
|
target=capture_camera,
|
||||||
name=f"camera_capture:{name}",
|
name=f"camera_capture:{name}",
|
||||||
args=(name, config, self.shm_frame_count, self.camera_metrics[name]),
|
args=(name, config, self.shm_frame_count(), self.camera_metrics[name]),
|
||||||
)
|
)
|
||||||
capture_process.daemon = True
|
capture_process.daemon = True
|
||||||
self.camera_metrics[name]["capture_process"] = capture_process
|
self.camera_metrics[name]["capture_process"] = capture_process
|
||||||
@ -590,7 +536,7 @@ class FrigateApp:
|
|||||||
self.frigate_watchdog = FrigateWatchdog(self.detectors, self.stop_event)
|
self.frigate_watchdog = FrigateWatchdog(self.detectors, self.stop_event)
|
||||||
self.frigate_watchdog.start()
|
self.frigate_watchdog.start()
|
||||||
|
|
||||||
def check_shm(self) -> None:
|
def shm_frame_count(self) -> int:
|
||||||
total_shm = round(shutil.disk_usage("/dev/shm").total / pow(2, 20), 1)
|
total_shm = round(shutil.disk_usage("/dev/shm").total / pow(2, 20), 1)
|
||||||
|
|
||||||
# required for log files + nginx cache
|
# required for log files + nginx cache
|
||||||
@ -610,17 +556,19 @@ class FrigateApp:
|
|||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.shm_frame_count = min(50, int(available_shm / (cam_total_frame_size)))
|
shm_frame_count = min(50, int(available_shm / (cam_total_frame_size)))
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Calculated total camera size {available_shm} / {cam_total_frame_size} :: {self.shm_frame_count} frames for each camera in SHM"
|
f"Calculated total camera size {available_shm} / {cam_total_frame_size} :: {shm_frame_count} frames for each camera in SHM"
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.shm_frame_count < 10:
|
if shm_frame_count < 10:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"The current SHM size of {total_shm}MB is too small, recommend increasing it to at least {round(min_req_shm + cam_total_frame_size)}MB."
|
f"The current SHM size of {total_shm}MB is too small, recommend increasing it to at least {round(min_req_shm + cam_total_frame_size)}MB."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return shm_frame_count
|
||||||
|
|
||||||
def init_auth(self) -> None:
|
def init_auth(self) -> None:
|
||||||
if self.config.auth.enabled:
|
if self.config.auth.enabled:
|
||||||
if User.select().count() == 0:
|
if User.select().count() == 0:
|
||||||
@ -657,97 +605,58 @@ class FrigateApp:
|
|||||||
logger.info("********************************************************")
|
logger.info("********************************************************")
|
||||||
logger.info("********************************************************")
|
logger.info("********************************************************")
|
||||||
|
|
||||||
@log_thread()
|
|
||||||
def start(self) -> None:
|
def start(self) -> None:
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
prog="Frigate",
|
|
||||||
description="An NVR with realtime local object detection for IP cameras.",
|
|
||||||
)
|
|
||||||
parser.add_argument("--validate-config", action="store_true")
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
logger.info(f"Starting Frigate ({VERSION})")
|
logger.info(f"Starting Frigate ({VERSION})")
|
||||||
|
|
||||||
try:
|
# Ensure global state.
|
||||||
self.ensure_dirs()
|
self.ensure_dirs()
|
||||||
try:
|
self.config.install()
|
||||||
self.init_config()
|
|
||||||
except Exception as e:
|
# Start frigate services.
|
||||||
print("*************************************************************")
|
self.init_camera_metrics()
|
||||||
print("*************************************************************")
|
self.init_queues()
|
||||||
print("*** Your config file is not valid! ***")
|
self.init_database()
|
||||||
print("*** Please check the docs at ***")
|
self.init_onvif()
|
||||||
print("*** https://docs.frigate.video/configuration/index ***")
|
self.init_recording_manager()
|
||||||
print("*************************************************************")
|
self.init_review_segment_manager()
|
||||||
print("*************************************************************")
|
self.init_embeddings_manager()
|
||||||
print("*** Config Validation Errors ***")
|
self.init_go2rtc()
|
||||||
print("*************************************************************")
|
self.bind_database()
|
||||||
if isinstance(e, ValidationError):
|
self.check_db_data_migrations()
|
||||||
for error in e.errors():
|
self.init_inter_process_communicator()
|
||||||
location = ".".join(str(item) for item in error["loc"])
|
self.init_dispatcher()
|
||||||
print(f"{location}: {error['msg']}")
|
|
||||||
else:
|
|
||||||
print(e)
|
|
||||||
print(traceback.format_exc())
|
|
||||||
print("*************************************************************")
|
|
||||||
print("*** End Config Validation Errors ***")
|
|
||||||
print("*************************************************************")
|
|
||||||
sys.exit(1)
|
|
||||||
if args.validate_config:
|
|
||||||
print("*************************************************************")
|
|
||||||
print("*** Your config file is valid. ***")
|
|
||||||
print("*************************************************************")
|
|
||||||
sys.exit(0)
|
|
||||||
self.set_environment_vars()
|
|
||||||
self.set_log_levels()
|
|
||||||
self.init_queues()
|
|
||||||
self.init_database()
|
|
||||||
self.init_onvif()
|
|
||||||
self.init_recording_manager()
|
|
||||||
self.init_review_segment_manager()
|
|
||||||
self.init_embeddings_manager()
|
|
||||||
self.init_go2rtc()
|
|
||||||
self.bind_database()
|
|
||||||
self.check_db_data_migrations()
|
|
||||||
self.init_inter_process_communicator()
|
|
||||||
self.init_dispatcher()
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
sys.exit(1)
|
|
||||||
self.start_detectors()
|
self.start_detectors()
|
||||||
self.start_video_output_processor()
|
self.start_video_output_processor()
|
||||||
self.start_ptz_autotracker()
|
self.start_ptz_autotracker()
|
||||||
self.init_historical_regions()
|
self.init_historical_regions()
|
||||||
self.start_detected_frames_processor()
|
self.start_detected_frames_processor()
|
||||||
self.start_camera_processors()
|
self.start_camera_processors()
|
||||||
self.check_shm()
|
|
||||||
self.start_camera_capture_processes()
|
self.start_camera_capture_processes()
|
||||||
self.start_audio_processors()
|
self.start_audio_processors()
|
||||||
self.start_storage_maintainer()
|
self.start_storage_maintainer()
|
||||||
self.init_external_event_processor()
|
self.init_external_event_processor()
|
||||||
self.start_stats_emitter()
|
self.start_stats_emitter()
|
||||||
self.init_web_server()
|
|
||||||
self.start_timeline_processor()
|
self.start_timeline_processor()
|
||||||
self.start_event_processor()
|
self.start_event_processor()
|
||||||
self.start_event_cleanup()
|
self.start_event_cleanup()
|
||||||
self.start_record_cleanup()
|
self.start_record_cleanup()
|
||||||
self.start_watchdog()
|
self.start_watchdog()
|
||||||
|
|
||||||
self.init_auth()
|
self.init_auth()
|
||||||
|
|
||||||
# Flask only listens for SIGINT, so we need to catch SIGTERM and send SIGINT
|
|
||||||
def receiveSignal(signalNumber: int, frame: Optional[FrameType]) -> None:
|
|
||||||
os.kill(os.getpid(), signal.SIGINT)
|
|
||||||
|
|
||||||
signal.signal(signal.SIGTERM, receiveSignal)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.flask_app.run(host="127.0.0.1", port=5001, debug=False, threaded=True)
|
create_app(
|
||||||
except KeyboardInterrupt:
|
self.config,
|
||||||
pass
|
self.db,
|
||||||
|
self.embeddings,
|
||||||
logger.info("Flask has exited...")
|
self.detected_frames_processor,
|
||||||
|
self.storage_maintainer,
|
||||||
self.stop()
|
self.onvif_controller,
|
||||||
|
self.external_event_processor,
|
||||||
|
self.stats_emitter,
|
||||||
|
).run(host="127.0.0.1", port=5001, debug=False, threaded=True)
|
||||||
|
finally:
|
||||||
|
self.stop()
|
||||||
|
|
||||||
def stop(self) -> None:
|
def stop(self) -> None:
|
||||||
logger.info("Stopping...")
|
logger.info("Stopping...")
|
||||||
|
@ -45,13 +45,18 @@ from frigate.ffmpeg_presets import (
|
|||||||
parse_preset_input,
|
parse_preset_input,
|
||||||
parse_preset_output_record,
|
parse_preset_output_record,
|
||||||
)
|
)
|
||||||
|
from frigate.plus import PlusApi
|
||||||
from frigate.util.builtin import (
|
from frigate.util.builtin import (
|
||||||
deep_merge,
|
deep_merge,
|
||||||
escape_special_characters,
|
escape_special_characters,
|
||||||
generate_color_palette,
|
generate_color_palette,
|
||||||
get_ffmpeg_arg_list,
|
get_ffmpeg_arg_list,
|
||||||
)
|
)
|
||||||
from frigate.util.config import StreamInfoRetriever, get_relative_coordinates
|
from frigate.util.config import (
|
||||||
|
StreamInfoRetriever,
|
||||||
|
get_relative_coordinates,
|
||||||
|
migrate_frigate_config,
|
||||||
|
)
|
||||||
from frigate.util.image import create_mask
|
from frigate.util.image import create_mask
|
||||||
from frigate.util.services import auto_detect_hwaccel
|
from frigate.util.services import auto_detect_hwaccel
|
||||||
|
|
||||||
@ -59,6 +64,25 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
yaml = YAML()
|
yaml = YAML()
|
||||||
|
|
||||||
|
DEFAULT_CONFIG_FILES = ["/config/config.yaml", "/config/config.yml"]
|
||||||
|
DEFAULT_CONFIG = """
|
||||||
|
mqtt:
|
||||||
|
enabled: False
|
||||||
|
|
||||||
|
cameras:
|
||||||
|
name_of_your_camera: # <------ Name the camera
|
||||||
|
enabled: True
|
||||||
|
ffmpeg:
|
||||||
|
inputs:
|
||||||
|
- path: rtsp://10.0.10.10:554/rtsp # <----- The stream you want to use for detection
|
||||||
|
roles:
|
||||||
|
- detect
|
||||||
|
detect:
|
||||||
|
enabled: False # <---- disable detection until you have a working camera feed
|
||||||
|
width: 1280
|
||||||
|
height: 720
|
||||||
|
"""
|
||||||
|
|
||||||
# TODO: Identify what the default format to display timestamps is
|
# TODO: Identify what the default format to display timestamps is
|
||||||
DEFAULT_TIME_FORMAT = "%m/%d/%Y %H:%M:%S"
|
DEFAULT_TIME_FORMAT = "%m/%d/%Y %H:%M:%S"
|
||||||
# German Style:
|
# German Style:
|
||||||
@ -1272,6 +1296,19 @@ class LoggerConfig(FrigateBaseModel):
|
|||||||
default_factory=dict, title="Log level for specified processes."
|
default_factory=dict, title="Log level for specified processes."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def install(self):
|
||||||
|
"""Install global logging state."""
|
||||||
|
logging.getLogger().setLevel(self.default.value.upper())
|
||||||
|
|
||||||
|
log_levels = {
|
||||||
|
"werkzeug": LogLevelEnum.error,
|
||||||
|
"ws4py": LogLevelEnum.error,
|
||||||
|
**self.logs,
|
||||||
|
}
|
||||||
|
|
||||||
|
for log, level in log_levels.items():
|
||||||
|
logging.getLogger(log).setLevel(level.value.upper())
|
||||||
|
|
||||||
|
|
||||||
class CameraGroupConfig(FrigateBaseModel):
|
class CameraGroupConfig(FrigateBaseModel):
|
||||||
"""Represents a group of cameras."""
|
"""Represents a group of cameras."""
|
||||||
@ -1492,11 +1529,22 @@ class FrigateConfig(FrigateBaseModel):
|
|||||||
)
|
)
|
||||||
version: Optional[str] = Field(default=None, title="Current config version.")
|
version: Optional[str] = Field(default=None, title="Current config version.")
|
||||||
|
|
||||||
|
_plus_api: PlusApi
|
||||||
|
|
||||||
|
@property
|
||||||
|
def plus_api(self) -> PlusApi:
|
||||||
|
return self._plus_api
|
||||||
|
|
||||||
@model_validator(mode="after")
|
@model_validator(mode="after")
|
||||||
def post_validation(self, info: ValidationInfo) -> Self:
|
def post_validation(self, info: ValidationInfo) -> Self:
|
||||||
plus_api = None
|
# Load plus api from context, if possible.
|
||||||
|
self._plus_api = None
|
||||||
if isinstance(info.context, dict):
|
if isinstance(info.context, dict):
|
||||||
plus_api = info.context.get("plus_api")
|
self._plus_api = info.context.get("plus_api")
|
||||||
|
|
||||||
|
# Ensure self._plus_api is set, if no explicit value is provided.
|
||||||
|
if self._plus_api is None:
|
||||||
|
self._plus_api = PlusApi()
|
||||||
|
|
||||||
# set notifications state
|
# set notifications state
|
||||||
self.notifications.enabled_in_config = self.notifications.enabled
|
self.notifications.enabled_in_config = self.notifications.enabled
|
||||||
@ -1691,7 +1739,7 @@ class FrigateConfig(FrigateBaseModel):
|
|||||||
enabled_labels.update(camera.objects.track)
|
enabled_labels.update(camera.objects.track)
|
||||||
|
|
||||||
self.model.create_colormap(sorted(enabled_labels))
|
self.model.create_colormap(sorted(enabled_labels))
|
||||||
self.model.check_and_load_plus_model(plus_api)
|
self.model.check_and_load_plus_model(self.plus_api)
|
||||||
|
|
||||||
for key, detector in self.detectors.items():
|
for key, detector in self.detectors.items():
|
||||||
adapter = TypeAdapter(DetectorConfig)
|
adapter = TypeAdapter(DetectorConfig)
|
||||||
@ -1726,7 +1774,7 @@ class FrigateConfig(FrigateBaseModel):
|
|||||||
|
|
||||||
detector_config.model = ModelConfig.model_validate(merged_model)
|
detector_config.model = ModelConfig.model_validate(merged_model)
|
||||||
detector_config.model.check_and_load_plus_model(
|
detector_config.model.check_and_load_plus_model(
|
||||||
plus_api, detector_config.type
|
self.plus_api, detector_config.type
|
||||||
)
|
)
|
||||||
detector_config.model.compute_model_hash()
|
detector_config.model.compute_model_hash()
|
||||||
self.detectors[key] = detector_config
|
self.detectors[key] = detector_config
|
||||||
@ -1743,8 +1791,38 @@ class FrigateConfig(FrigateBaseModel):
|
|||||||
return v
|
return v
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_file(cls, config_path, **kwargs):
|
def load(cls, **kwargs):
|
||||||
with open(config_path) as f:
|
config_path = os.environ.get("CONFIG_FILE")
|
||||||
|
|
||||||
|
# No explicit configuration file, try to find one in the default paths.
|
||||||
|
if config_path is None:
|
||||||
|
for path in DEFAULT_CONFIG_FILES:
|
||||||
|
if os.path.isfile(path):
|
||||||
|
config_path = path
|
||||||
|
break
|
||||||
|
|
||||||
|
# No configuration file found, create one.
|
||||||
|
new_config = False
|
||||||
|
if config_path is None:
|
||||||
|
logger.info("No config file found, saving default config")
|
||||||
|
config_path = DEFAULT_CONFIG_FILES[-1]
|
||||||
|
new_config = True
|
||||||
|
else:
|
||||||
|
# Check if the config file needs to be migrated.
|
||||||
|
migrate_frigate_config(config_path)
|
||||||
|
|
||||||
|
# Finally, load the resulting configuration file.
|
||||||
|
with open(config_path, "a+") as f:
|
||||||
|
# Only write the default config if the opened file is non-empty. This can happen as
|
||||||
|
# a race condition. It's extremely unlikely, but eh. Might as well check it.
|
||||||
|
if new_config and f.tell() == 0:
|
||||||
|
f.write(DEFAULT_CONFIG)
|
||||||
|
logger.info(
|
||||||
|
"Created default config file, see the getting started docs \
|
||||||
|
for configuration https://docs.frigate.video/guides/getting_started"
|
||||||
|
)
|
||||||
|
|
||||||
|
f.seek(0)
|
||||||
return FrigateConfig.parse(f, **kwargs)
|
return FrigateConfig.parse(f, **kwargs)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -1775,10 +1853,17 @@ class FrigateConfig(FrigateBaseModel):
|
|||||||
# Validate and return the config dict.
|
# Validate and return the config dict.
|
||||||
return cls.parse_object(config, **context)
|
return cls.parse_object(config, **context)
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def parse_object(cls, obj: Any, **context):
|
|
||||||
return cls.model_validate(obj, context=context)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_yaml(cls, config_yaml, **context):
|
def parse_yaml(cls, config_yaml, **context):
|
||||||
return cls.parse(config_yaml, is_json=False, **context)
|
return cls.parse(config_yaml, is_json=False, **context)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse_object(cls, obj: Any, *, plus_api: Optional[PlusApi] = None):
|
||||||
|
return cls.model_validate(obj, context={"plus_api": plus_api})
|
||||||
|
|
||||||
|
def install(self):
|
||||||
|
"""Install global state from the config."""
|
||||||
|
self.logger.install()
|
||||||
|
|
||||||
|
for key, value in self.environment_vars.items():
|
||||||
|
os.environ[key] = value
|
||||||
|
@ -76,16 +76,8 @@ def listen_to_audio(
|
|||||||
stop_event = mp.Event()
|
stop_event = mp.Event()
|
||||||
audio_threads: list[threading.Thread] = []
|
audio_threads: list[threading.Thread] = []
|
||||||
|
|
||||||
def exit_process() -> None:
|
|
||||||
for thread in audio_threads:
|
|
||||||
thread.join()
|
|
||||||
|
|
||||||
logger.info("Exiting audio detector...")
|
|
||||||
|
|
||||||
def receiveSignal(signalNumber: int, frame: Optional[FrameType]) -> None:
|
def receiveSignal(signalNumber: int, frame: Optional[FrameType]) -> None:
|
||||||
logger.debug(f"Audio process received signal {signalNumber}")
|
|
||||||
stop_event.set()
|
stop_event.set()
|
||||||
exit_process()
|
|
||||||
|
|
||||||
signal.signal(signal.SIGTERM, receiveSignal)
|
signal.signal(signal.SIGTERM, receiveSignal)
|
||||||
signal.signal(signal.SIGINT, receiveSignal)
|
signal.signal(signal.SIGINT, receiveSignal)
|
||||||
@ -104,6 +96,11 @@ def listen_to_audio(
|
|||||||
audio_threads.append(audio)
|
audio_threads.append(audio)
|
||||||
audio.start()
|
audio.start()
|
||||||
|
|
||||||
|
for thread in audio_threads:
|
||||||
|
thread.join()
|
||||||
|
|
||||||
|
logger.info("Exiting audio detector...")
|
||||||
|
|
||||||
|
|
||||||
class AudioTfl:
|
class AudioTfl:
|
||||||
def __init__(self, stop_event: mp.Event, num_threads=2):
|
def __init__(self, stop_event: mp.Event, num_threads=2):
|
||||||
|
@ -2,6 +2,7 @@ import atexit
|
|||||||
import logging
|
import logging
|
||||||
import multiprocessing as mp
|
import multiprocessing as mp
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
import threading
|
import threading
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from contextlib import AbstractContextManager, ContextDecorator
|
from contextlib import AbstractContextManager, ContextDecorator
|
||||||
@ -68,6 +69,19 @@ class log_thread(AbstractContextManager, ContextDecorator):
|
|||||||
self._stop_thread()
|
self._stop_thread()
|
||||||
|
|
||||||
|
|
||||||
|
# When a multiprocessing.Process exits, python tries to flush stdout and stderr. However, if the
|
||||||
|
# process is created after a thread (for example a logging thread) is created and the process fork
|
||||||
|
# happens while an internal lock is held, the stdout/err flush can cause a deadlock.
|
||||||
|
#
|
||||||
|
# https://github.com/python/cpython/issues/91776
|
||||||
|
def reopen_std_streams() -> None:
|
||||||
|
sys.stdout = os.fdopen(1, "w")
|
||||||
|
sys.stderr = os.fdopen(2, "w")
|
||||||
|
|
||||||
|
|
||||||
|
os.register_at_fork(after_in_child=reopen_std_streams)
|
||||||
|
|
||||||
|
|
||||||
# based on https://codereview.stackexchange.com/a/17959
|
# based on https://codereview.stackexchange.com/a/17959
|
||||||
class LogPipe(threading.Thread):
|
class LogPipe(threading.Thread):
|
||||||
def __init__(self, log_name: str):
|
def __init__(self, log_name: str):
|
||||||
|
@ -92,7 +92,6 @@ def run_detector(
|
|||||||
stop_event = mp.Event()
|
stop_event = mp.Event()
|
||||||
|
|
||||||
def receiveSignal(signalNumber, frame):
|
def receiveSignal(signalNumber, frame):
|
||||||
logger.info("Signal to exit detection process...")
|
|
||||||
stop_event.set()
|
stop_event.set()
|
||||||
|
|
||||||
signal.signal(signal.SIGTERM, receiveSignal)
|
signal.signal(signal.SIGTERM, receiveSignal)
|
||||||
|
@ -38,7 +38,6 @@ def output_frames(
|
|||||||
stop_event = mp.Event()
|
stop_event = mp.Event()
|
||||||
|
|
||||||
def receiveSignal(signalNumber, frame):
|
def receiveSignal(signalNumber, frame):
|
||||||
logger.debug(f"Output frames process received signal {signalNumber}")
|
|
||||||
stop_event.set()
|
stop_event.set()
|
||||||
|
|
||||||
signal.signal(signal.SIGTERM, receiveSignal)
|
signal.signal(signal.SIGTERM, receiveSignal)
|
||||||
|
@ -22,7 +22,6 @@ def manage_recordings(config: FrigateConfig) -> None:
|
|||||||
stop_event = mp.Event()
|
stop_event = mp.Event()
|
||||||
|
|
||||||
def receiveSignal(signalNumber: int, frame: Optional[FrameType]) -> None:
|
def receiveSignal(signalNumber: int, frame: Optional[FrameType]) -> None:
|
||||||
logger.debug(f"Recording manager process received signal {signalNumber}")
|
|
||||||
stop_event.set()
|
stop_event.set()
|
||||||
|
|
||||||
signal.signal(signal.SIGTERM, receiveSignal)
|
signal.signal(signal.SIGTERM, receiveSignal)
|
||||||
|
@ -20,7 +20,6 @@ def manage_review_segments(config: FrigateConfig) -> None:
|
|||||||
stop_event = mp.Event()
|
stop_event = mp.Event()
|
||||||
|
|
||||||
def receiveSignal(signalNumber: int, frame: Optional[FrameType]) -> None:
|
def receiveSignal(signalNumber: int, frame: Optional[FrameType]) -> None:
|
||||||
logger.debug(f"Manage review segments process received signal {signalNumber}")
|
|
||||||
stop_event.set()
|
stop_event.set()
|
||||||
|
|
||||||
signal.signal(signal.SIGTERM, receiveSignal)
|
signal.signal(signal.SIGTERM, receiveSignal)
|
||||||
|
@ -13,7 +13,6 @@ from playhouse.sqliteq import SqliteQueueDatabase
|
|||||||
from frigate.api.app import create_app
|
from frigate.api.app import create_app
|
||||||
from frigate.config import FrigateConfig
|
from frigate.config import FrigateConfig
|
||||||
from frigate.models import Event, Recordings
|
from frigate.models import Event, Recordings
|
||||||
from frigate.plus import PlusApi
|
|
||||||
from frigate.stats.emitter import StatsEmitter
|
from frigate.stats.emitter import StatsEmitter
|
||||||
from frigate.test.const import TEST_DB, TEST_DB_CLEANUPS
|
from frigate.test.const import TEST_DB, TEST_DB_CLEANUPS
|
||||||
|
|
||||||
@ -121,7 +120,6 @@ class TestHttp(unittest.TestCase):
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
PlusApi(),
|
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
id = "123456.random"
|
id = "123456.random"
|
||||||
@ -158,7 +156,6 @@ class TestHttp(unittest.TestCase):
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
PlusApi(),
|
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
id = "123456.random"
|
id = "123456.random"
|
||||||
@ -180,7 +177,6 @@ class TestHttp(unittest.TestCase):
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
PlusApi(),
|
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
id = "123456.random"
|
id = "123456.random"
|
||||||
@ -201,7 +197,6 @@ class TestHttp(unittest.TestCase):
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
PlusApi(),
|
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
id = "123456.random"
|
id = "123456.random"
|
||||||
@ -224,7 +219,6 @@ class TestHttp(unittest.TestCase):
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
PlusApi(),
|
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
id = "123456.random"
|
id = "123456.random"
|
||||||
@ -251,7 +245,6 @@ class TestHttp(unittest.TestCase):
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
PlusApi(),
|
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
morning_id = "123456.random"
|
morning_id = "123456.random"
|
||||||
@ -290,7 +283,6 @@ class TestHttp(unittest.TestCase):
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
PlusApi(),
|
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
id = "123456.random"
|
id = "123456.random"
|
||||||
@ -326,7 +318,6 @@ class TestHttp(unittest.TestCase):
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
PlusApi(),
|
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
id = "123456.random"
|
id = "123456.random"
|
||||||
@ -352,7 +343,6 @@ class TestHttp(unittest.TestCase):
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
PlusApi(),
|
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -370,7 +360,6 @@ class TestHttp(unittest.TestCase):
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
PlusApi(),
|
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
id = "123456.random"
|
id = "123456.random"
|
||||||
@ -392,7 +381,6 @@ class TestHttp(unittest.TestCase):
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
PlusApi(),
|
|
||||||
stats,
|
stats,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -258,37 +258,6 @@ def find_by_key(dictionary, target_key):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def save_default_config(location: str) -> None:
|
|
||||||
try:
|
|
||||||
with open(location, "w") as f:
|
|
||||||
f.write(
|
|
||||||
"""
|
|
||||||
mqtt:
|
|
||||||
enabled: False
|
|
||||||
|
|
||||||
cameras:
|
|
||||||
name_of_your_camera: # <------ Name the camera
|
|
||||||
enabled: True
|
|
||||||
ffmpeg:
|
|
||||||
inputs:
|
|
||||||
- path: rtsp://10.0.10.10:554/rtsp # <----- The stream you want to use for detection
|
|
||||||
roles:
|
|
||||||
- detect
|
|
||||||
detect:
|
|
||||||
enabled: False # <---- disable detection until you have a working camera feed
|
|
||||||
width: 1280
|
|
||||||
height: 720
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
except PermissionError:
|
|
||||||
logger.error("Unable to write default config to /config")
|
|
||||||
return
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
"Created default config file, see the getting started docs for configuration https://docs.frigate.video/guides/getting_started"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_tomorrow_at_time(hour: int) -> datetime.datetime:
|
def get_tomorrow_at_time(hour: int) -> datetime.datetime:
|
||||||
"""Returns the datetime of the following day at 2am."""
|
"""Returns the datetime of the following day at 2am."""
|
||||||
try:
|
try:
|
||||||
|
@ -390,7 +390,6 @@ def capture_camera(name, config: CameraConfig, shm_frame_count: int, process_inf
|
|||||||
stop_event = mp.Event()
|
stop_event = mp.Event()
|
||||||
|
|
||||||
def receiveSignal(signalNumber, frame):
|
def receiveSignal(signalNumber, frame):
|
||||||
logger.debug(f"Capture camera received signal {signalNumber}")
|
|
||||||
stop_event.set()
|
stop_event.set()
|
||||||
|
|
||||||
signal.signal(signal.SIGTERM, receiveSignal)
|
signal.signal(signal.SIGTERM, receiveSignal)
|
||||||
|
Loading…
Reference in New Issue
Block a user