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:
gtsiam 2024-09-24 15:07:47 +03:00 committed by GitHub
parent a7ed90f042
commit dc54981784
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 215 additions and 213 deletions

View File

@ -1,10 +1,16 @@
import argparse
import faulthandler
import logging
import signal
import sys
import threading
from flask import cli
from pydantic import ValidationError
from frigate.app import FrigateApp
from frigate.config import FrigateConfig
from frigate.log import log_thread
def main() -> None:
@ -20,8 +26,50 @@ def main() -> None:
threading.current_thread().name = "frigate"
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.
FrigateApp().start()
FrigateApp(config).start()
if __name__ == "__main__":

View File

@ -28,7 +28,6 @@ from frigate.const import CONFIG_DIR
from frigate.embeddings import EmbeddingsContext
from frigate.events.external import ExternalEventProcessor
from frigate.models import Event, Timeline
from frigate.plus import PlusApi
from frigate.ptz.onvif import OnvifController
from frigate.stats.emitter import StatsEmitter
from frigate.storage import StorageMaintainer
@ -61,7 +60,6 @@ def create_app(
storage_maintainer: StorageMaintainer,
onvif: OnvifController,
external_processor: ExternalEventProcessor,
plus_api: PlusApi,
stats_emitter: StatsEmitter,
):
app = Flask(__name__)
@ -89,7 +87,6 @@ def create_app(
app.storage_maintainer = storage_maintainer
app.onvif = onvif
app.external_processor = external_processor
app.plus_api = plus_api
app.camera_error_image = None
app.stats_emitter = stats_emitter
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():
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
for detector_config in config["detectors"].values():
@ -362,7 +359,7 @@ def config_set():
if json.get("requires_restart", 1) == 0:
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(

View File

@ -612,7 +612,7 @@ def set_retain(id):
@EventBp.route("/events/<id>/plus", methods=("POST",))
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"
logger.error(message)
return make_response(
@ -680,7 +680,7 @@ def send_to_plus(id):
)
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:
logger.exception(ex)
return make_response(
@ -696,7 +696,7 @@ def send_to_plus(id):
box = event.data["box"]
try:
current_app.plus_api.add_annotation(
current_app.frigate_config.plus_api.add_annotation(
event.plus_id,
box,
event.label,
@ -720,7 +720,7 @@ def send_to_plus(id):
@EventBp.route("/events/<id>/false_positive", methods=("PUT",))
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"
logger.error(message)
return make_response(
@ -769,7 +769,7 @@ def false_positive(id):
)
try:
current_app.plus_api.add_false_positive(
current_app.frigate_config.plus_api.add_false_positive(
event.plus_id,
region,
box,

View File

@ -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)
current_app.plus_api.upload_image(nd, camera_name)
current_app.frigate_config.plus_api.upload_image(nd, camera_name)
return make_response(
jsonify(

View File

@ -1,23 +1,17 @@
import argparse
import datetime
import logging
import multiprocessing as mp
import os
import secrets
import shutil
import signal
import sys
import traceback
from multiprocessing import Queue
from multiprocessing.synchronize import Event as MpEvent
from types import FrameType
from typing import Optional
from typing import Any
import psutil
from peewee_migrate import Router
from playhouse.sqlite_ext import SqliteExtDatabase
from playhouse.sqliteq import SqliteQueueDatabase
from pydantic import ValidationError
from frigate.api.app import create_app
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.ws import WebSocketClient
from frigate.comms.zmq_proxy import ZmqProxy
from frigate.config import FrigateConfig
from frigate.const import (
CACHE_DIR,
CLIPS_DIR,
@ -43,7 +36,6 @@ from frigate.events.audio import listen_to_audio
from frigate.events.cleanup import EventCleanup
from frigate.events.external import ExternalEventProcessor
from frigate.events.maintainer import EventProcessor
from frigate.log import log_thread
from frigate.models import (
Event,
Export,
@ -58,7 +50,6 @@ from frigate.models import (
from frigate.object_detection import ObjectDetectProcess
from frigate.object_processing import TrackedObjectProcessor
from frigate.output.output import output_frames
from frigate.plus import PlusApi
from frigate.ptz.autotrack import PtzAutoTrackerThread
from frigate.ptz.onvif import OnvifController
from frigate.record.cleanup import RecordingCleanup
@ -70,8 +61,7 @@ from frigate.stats.util import stats_init
from frigate.storage import StorageMaintainer
from frigate.timeline import TimelineProcessor
from frigate.types import CameraMetricsTypes, PTZMetricsTypes
from frigate.util.builtin import empty_and_close_queue, save_default_config
from frigate.util.config import migrate_frigate_config
from frigate.util.builtin import empty_and_close_queue
from frigate.util.object import get_camera_regions_grid
from frigate.version import VERSION
from frigate.video import capture_camera, track_camera
@ -81,22 +71,19 @@ logger = logging.getLogger(__name__)
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.detection_queue: Queue = mp.Queue()
self.detectors: dict[str, ObjectDetectProcess] = {}
self.detection_out_events: dict[str, MpEvent] = {}
self.detection_shms: list[mp.shared_memory.SharedMemory] = []
self.log_queue: Queue = mp.Queue()
self.plus_api = PlusApi()
self.camera_metrics: dict[str, CameraMetricsTypes] = {}
self.ptz_metrics: dict[str, PTZMetricsTypes] = {}
self.processes: dict[str, int] = {}
self.region_grids: dict[str, list[list[dict[str, int]]]] = {}
def set_environment_vars(self) -> None:
for key, value in self.config.environment_vars.items():
os.environ[key] = value
self.config = config
def ensure_dirs(self) -> None:
for d in [
@ -113,24 +100,7 @@ class FrigateApp:
else:
logger.debug(f"Skipping directory: {d}")
def init_config(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)
def init_camera_metrics(self) -> None:
for camera_name in self.config.cameras.keys():
# create camera_metrics
self.camera_metrics[camera_name] = {
@ -190,17 +160,6 @@ class FrigateApp:
}
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:
# Queue for cameras to push tracked objects to
self.detected_frames_queue: Queue = mp.Queue(
@ -374,19 +333,6 @@ class FrigateApp:
self.inter_config_updater = ConfigPublisher()
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:
self.onvif_controller = OnvifController(self.config, self.ptz_metrics)
@ -527,7 +473,7 @@ class FrigateApp:
capture_process = mp.Process(
target=capture_camera,
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
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.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)
# required for log files + nginx cache
@ -610,17 +556,19 @@ class FrigateApp:
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(
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(
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:
if self.config.auth.enabled:
if User.select().count() == 0:
@ -657,97 +605,58 @@ class FrigateApp:
logger.info("********************************************************")
logger.info("********************************************************")
@log_thread()
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})")
try:
self.ensure_dirs()
try:
self.init_config()
except Exception as e:
print("*************************************************************")
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_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)
# Ensure global state.
self.ensure_dirs()
self.config.install()
# Start frigate services.
self.init_camera_metrics()
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()
self.start_detectors()
self.start_video_output_processor()
self.start_ptz_autotracker()
self.init_historical_regions()
self.start_detected_frames_processor()
self.start_camera_processors()
self.check_shm()
self.start_camera_capture_processes()
self.start_audio_processors()
self.start_storage_maintainer()
self.init_external_event_processor()
self.start_stats_emitter()
self.init_web_server()
self.start_timeline_processor()
self.start_event_processor()
self.start_event_cleanup()
self.start_record_cleanup()
self.start_watchdog()
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:
self.flask_app.run(host="127.0.0.1", port=5001, debug=False, threaded=True)
except KeyboardInterrupt:
pass
logger.info("Flask has exited...")
self.stop()
create_app(
self.config,
self.db,
self.embeddings,
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()
def stop(self) -> None:
logger.info("Stopping...")

View File

@ -45,13 +45,18 @@ from frigate.ffmpeg_presets import (
parse_preset_input,
parse_preset_output_record,
)
from frigate.plus import PlusApi
from frigate.util.builtin import (
deep_merge,
escape_special_characters,
generate_color_palette,
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.services import auto_detect_hwaccel
@ -59,6 +64,25 @@ logger = logging.getLogger(__name__)
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
DEFAULT_TIME_FORMAT = "%m/%d/%Y %H:%M:%S"
# German Style:
@ -1272,6 +1296,19 @@ class LoggerConfig(FrigateBaseModel):
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):
"""Represents a group of cameras."""
@ -1492,11 +1529,22 @@ class FrigateConfig(FrigateBaseModel):
)
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")
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):
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
self.notifications.enabled_in_config = self.notifications.enabled
@ -1691,7 +1739,7 @@ class FrigateConfig(FrigateBaseModel):
enabled_labels.update(camera.objects.track)
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():
adapter = TypeAdapter(DetectorConfig)
@ -1726,7 +1774,7 @@ class FrigateConfig(FrigateBaseModel):
detector_config.model = ModelConfig.model_validate(merged_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()
self.detectors[key] = detector_config
@ -1743,8 +1791,38 @@ class FrigateConfig(FrigateBaseModel):
return v
@classmethod
def parse_file(cls, config_path, **kwargs):
with open(config_path) as f:
def load(cls, **kwargs):
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)
@classmethod
@ -1775,10 +1853,17 @@ class FrigateConfig(FrigateBaseModel):
# Validate and return the config dict.
return cls.parse_object(config, **context)
@classmethod
def parse_object(cls, obj: Any, **context):
return cls.model_validate(obj, context=context)
@classmethod
def parse_yaml(cls, config_yaml, **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

View File

@ -76,16 +76,8 @@ def listen_to_audio(
stop_event = mp.Event()
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:
logger.debug(f"Audio process received signal {signalNumber}")
stop_event.set()
exit_process()
signal.signal(signal.SIGTERM, receiveSignal)
signal.signal(signal.SIGINT, receiveSignal)
@ -104,6 +96,11 @@ def listen_to_audio(
audio_threads.append(audio)
audio.start()
for thread in audio_threads:
thread.join()
logger.info("Exiting audio detector...")
class AudioTfl:
def __init__(self, stop_event: mp.Event, num_threads=2):

View File

@ -2,6 +2,7 @@ import atexit
import logging
import multiprocessing as mp
import os
import sys
import threading
from collections import deque
from contextlib import AbstractContextManager, ContextDecorator
@ -68,6 +69,19 @@ class log_thread(AbstractContextManager, ContextDecorator):
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
class LogPipe(threading.Thread):
def __init__(self, log_name: str):

View File

@ -92,7 +92,6 @@ def run_detector(
stop_event = mp.Event()
def receiveSignal(signalNumber, frame):
logger.info("Signal to exit detection process...")
stop_event.set()
signal.signal(signal.SIGTERM, receiveSignal)

View File

@ -38,7 +38,6 @@ def output_frames(
stop_event = mp.Event()
def receiveSignal(signalNumber, frame):
logger.debug(f"Output frames process received signal {signalNumber}")
stop_event.set()
signal.signal(signal.SIGTERM, receiveSignal)

View File

@ -22,7 +22,6 @@ def manage_recordings(config: FrigateConfig) -> None:
stop_event = mp.Event()
def receiveSignal(signalNumber: int, frame: Optional[FrameType]) -> None:
logger.debug(f"Recording manager process received signal {signalNumber}")
stop_event.set()
signal.signal(signal.SIGTERM, receiveSignal)

View File

@ -20,7 +20,6 @@ def manage_review_segments(config: FrigateConfig) -> None:
stop_event = mp.Event()
def receiveSignal(signalNumber: int, frame: Optional[FrameType]) -> None:
logger.debug(f"Manage review segments process received signal {signalNumber}")
stop_event.set()
signal.signal(signal.SIGTERM, receiveSignal)

View File

@ -13,7 +13,6 @@ from playhouse.sqliteq import SqliteQueueDatabase
from frigate.api.app import create_app
from frigate.config import FrigateConfig
from frigate.models import Event, Recordings
from frigate.plus import PlusApi
from frigate.stats.emitter import StatsEmitter
from frigate.test.const import TEST_DB, TEST_DB_CLEANUPS
@ -121,7 +120,6 @@ class TestHttp(unittest.TestCase):
None,
None,
None,
PlusApi(),
None,
)
id = "123456.random"
@ -158,7 +156,6 @@ class TestHttp(unittest.TestCase):
None,
None,
None,
PlusApi(),
None,
)
id = "123456.random"
@ -180,7 +177,6 @@ class TestHttp(unittest.TestCase):
None,
None,
None,
PlusApi(),
None,
)
id = "123456.random"
@ -201,7 +197,6 @@ class TestHttp(unittest.TestCase):
None,
None,
None,
PlusApi(),
None,
)
id = "123456.random"
@ -224,7 +219,6 @@ class TestHttp(unittest.TestCase):
None,
None,
None,
PlusApi(),
None,
)
id = "123456.random"
@ -251,7 +245,6 @@ class TestHttp(unittest.TestCase):
None,
None,
None,
PlusApi(),
None,
)
morning_id = "123456.random"
@ -290,7 +283,6 @@ class TestHttp(unittest.TestCase):
None,
None,
None,
PlusApi(),
None,
)
id = "123456.random"
@ -326,7 +318,6 @@ class TestHttp(unittest.TestCase):
None,
None,
None,
PlusApi(),
None,
)
id = "123456.random"
@ -352,7 +343,6 @@ class TestHttp(unittest.TestCase):
None,
None,
None,
PlusApi(),
None,
)
@ -370,7 +360,6 @@ class TestHttp(unittest.TestCase):
None,
None,
None,
PlusApi(),
None,
)
id = "123456.random"
@ -392,7 +381,6 @@ class TestHttp(unittest.TestCase):
None,
None,
None,
PlusApi(),
stats,
)

View File

@ -258,37 +258,6 @@ def find_by_key(dictionary, target_key):
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:
"""Returns the datetime of the following day at 2am."""
try:

View File

@ -390,7 +390,6 @@ def capture_camera(name, config: CameraConfig, shm_frame_count: int, process_inf
stop_event = mp.Event()
def receiveSignal(signalNumber, frame):
logger.debug(f"Capture camera received signal {signalNumber}")
stop_event.set()
signal.signal(signal.SIGTERM, receiveSignal)