mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52: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( | ||||||
|  | |||||||
							
								
								
									
										151
									
								
								frigate/app.py
									
									
									
									
									
								
							
							
						
						
									
										151
									
								
								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,49 +605,15 @@ 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("*************************************************************") |  | ||||||
|                 print("***    Your config file is not valid!                     ***") |  | ||||||
|                 print("***    Please check the docs at                           ***") |  | ||||||
|                 print("***    https://docs.frigate.video/configuration/index     ***") |  | ||||||
|                 print("*************************************************************") |  | ||||||
|                 print("*************************************************************") |  | ||||||
|                 print("***    Config Validation Errors                           ***") |  | ||||||
|                 print("*************************************************************") |  | ||||||
|                 if isinstance(e, ValidationError): |  | ||||||
|                     for error in e.errors(): |  | ||||||
|                         location = ".".join(str(item) for item in error["loc"]) |  | ||||||
|                         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_queues() | ||||||
|         self.init_database() |         self.init_database() | ||||||
|         self.init_onvif() |         self.init_onvif() | ||||||
| @ -711,42 +625,37 @@ class FrigateApp: | |||||||
|         self.check_db_data_migrations() |         self.check_db_data_migrations() | ||||||
|         self.init_inter_process_communicator() |         self.init_inter_process_communicator() | ||||||
|         self.init_dispatcher() |         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.onvif_controller, | ||||||
|  |                 self.external_event_processor, | ||||||
|  |                 self.stats_emitter, | ||||||
|  |             ).run(host="127.0.0.1", port=5001, debug=False, threaded=True) | ||||||
|  |         finally: | ||||||
|             self.stop() |             self.stop() | ||||||
| 
 | 
 | ||||||
|     def stop(self) -> None: |     def stop(self) -> None: | ||||||
|  | |||||||
| @ -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