diff --git a/README.md b/README.md index af6aa219a..b778b6497 100644 --- a/README.md +++ b/README.md @@ -231,7 +231,7 @@ objects: person: # Optional: minimum width*height of the bounding box for the detected object (default: 0) min_area: 5000 - # Optional: maximum width*height of the bounding box for the detected object (default: max_int) + # Optional: maximum width*height of the bounding box for the detected object (default: 24000000) max_area: 100000 # Optional: minimum score for the object to initiate tracking (default: shown below) min_score: 0.5 diff --git a/docker/Dockerfile.wheels b/docker/Dockerfile.wheels index b2b14bb97..46126002b 100644 --- a/docker/Dockerfile.wheels +++ b/docker/Dockerfile.wheels @@ -33,7 +33,8 @@ RUN pip3 wheel --wheel-dir=/wheels \ PyYAML \ matplotlib \ click \ - tinydb + peewee \ + voluptuous FROM scratch diff --git a/docker/Dockerfile.wheels.aarch64 b/docker/Dockerfile.wheels.aarch64 index 17e048954..6637e741a 100644 --- a/docker/Dockerfile.wheels.aarch64 +++ b/docker/Dockerfile.wheels.aarch64 @@ -43,7 +43,8 @@ RUN pip3 wheel --wheel-dir=/wheels \ PyYAML \ matplotlib \ click \ - tinydb + peewee \ + voluptuous FROM scratch diff --git a/frigate/__init__.py b/frigate/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/frigate/__main__.py b/frigate/__main__.py index 7342b5e4c..004ad05ff 100644 --- a/frigate/__main__.py +++ b/frigate/__main__.py @@ -1,13 +1,3 @@ -# load config -# init database -# connect to mqtt -# start detection processes -# start frame processor -# start camera processes -# start event processor -# start capture processes -# start web app - import faulthandler; faulthandler.enable() import os import signal @@ -37,6 +27,7 @@ from frigate.object_processing import TrackedObjectProcessor from frigate.events import EventProcessor from frigate.util import EventsPerSecond from frigate.edgetpu import EdgeTPUProcess +from frigate.config import FRIGATE_CONFIG_SCHEMA FRIGATE_VARS = {k: v for k, v in os.environ.items() if k.startswith('FRIGATE_')} @@ -326,7 +317,6 @@ def main(): frigate_watchdog.start() def receiveSignal(signalNumber, frame): - print('Received:', signalNumber) stop_event.set() event_processor.join() object_processor.join() @@ -477,6 +467,59 @@ def main(): object_processor.join() +class FrigateApp(): + def __init__(self, stop: mp.Event): + self.stop = stop + self.config = None + + def init_config(self): + config_file = os.environ.get('CONFIG_FILE', '/config/config.yml') + + if config_file.endswith(".yml"): + with open(config_file) as f: + config = yaml.safe_load(f) + elif config_file.endswith(".json"): + with open(config_file) as f: + config = json.load(f) + + self.config = FRIGATE_CONFIG_SCHEMA(config) + + def init_web_server(self): + pass + + def init_database(self): + pass + + def init_mqtt(self): + pass + + def start_detectors(self): + pass + + def start_detection_processor(self): + pass + + def start_frame_processors(self): + pass + + def start_camera_capture_processes(self): + pass + + def start_watchdog(self): + pass + + def start(self): + self.init_config() + self.init_web_server() + self.init_database() + self.init_mqtt() + self.start_detectors() + self.start_detection_processor() + self.start_frame_processors() + self.start_camera_capture_processes() + self.start_watchdog() + if __name__ == '__main__': + # register stop handler init_db() main() diff --git a/frigate/config.py b/frigate/config.py new file mode 100644 index 000000000..2611783f1 --- /dev/null +++ b/frigate/config.py @@ -0,0 +1,142 @@ +import voluptuous as vol + +DETECTORS_SCHEMA = vol.Schema( + { + vol.Required(str): { + vol.Required('type', default='edgetpu'): vol.In(['cpu', 'edgetpu']), + vol.Optional('device', default='usb'): str + } + } +) + +DEFAULT_DETECTORS = { + 'coral': { + 'type': 'edgetpu', + 'device': 'usb' + } +} + +MQTT_SCHEMA = vol.Schema( + { + vol.Required('host'): str, + vol.Optional('port', default=1883): int, + vol.Optional('topic_prefix', default='frigate'): str, + vol.Optional('client_id', default='frigate'): str, + 'user': str, + 'password': str + } +) + +SAVE_CLIPS_SCHEMA = vol.Schema( + { + vol.Optional('max_seconds', default=300): int, + vol.Optional('clips_dir', default='/clips'): str, + vol.Optional('cache_dir', default='/cache'): str + } +) + +FFMPEG_GLOBAL_ARGS_DEFAULT = ['-hide_banner','-loglevel','panic'] +FFMPEG_INPUT_ARGS_DEFAULT = ['-avoid_negative_ts', 'make_zero', + '-fflags', 'nobuffer', + '-flags', 'low_delay', + '-strict', 'experimental', + '-fflags', '+genpts+discardcorrupt', + '-rtsp_transport', 'tcp', + '-stimeout', '5000000', + '-use_wallclock_as_timestamps', '1'] +FFMPEG_OUTPUT_ARGS_DEFAULT = ['-f', 'rawvideo', + '-pix_fmt', 'yuv420p'] + +GLOBAL_FFMPEG_SCHEMA = vol.Schema( + { + vol.Optional('global_args', default=FFMPEG_GLOBAL_ARGS_DEFAULT): [str], + vol.Optional('hwaccel_args', default=[]): [str], + vol.Optional('input_args', default=FFMPEG_INPUT_ARGS_DEFAULT): [str], + vol.Optional('output_args', default=FFMPEG_OUTPUT_ARGS_DEFAULT): [str] + } +) + +FILTER_SCHEMA = vol.Schema( + { + str: { + vol.Optional('min_area', default=0): int, + vol.Optional('max_area', default=24000000): int, + vol.Optional('threshold', default=0.85): float + } + } +) + +OBJECTS_SCHEMA = vol.Schema( + { + vol.Optional('track', default=['person']): [str], + 'filters': FILTER_SCHEMA.extend({vol.Optional('min_score', default=0.5): float}) + } +) + +DEFAULT_CAMERA_MQTT = { + 'crop_to_region': True +} +DEFAULT_CAMERA_SAVE_CLIPS = { + 'enabled': False +} +DEFAULT_CAMERA_SNAPSHOTS = { + 'show_timestamp': True, + 'draw_zones': False, + 'draw_bounding_boxes': True +} + +CAMERA_FFMPEG_SCHEMA = vol.Schema( + { + vol.Required('input'): str, + 'global_args': [str], + 'hwaccel_args': [str], + 'input_args': [str], + 'output_args': [str] + } +) + +CAMERAS_SCHEMA = vol.Schema( + { + str: { + vol.Required('ffmpeg'): CAMERA_FFMPEG_SCHEMA, + 'height': int, + 'width': int, + 'fps': int, + 'mask': str, + vol.Optional('best_image_timeout', default=60): int, + vol.Optional('mqtt', default=DEFAULT_CAMERA_MQTT): { + vol.Optional('crop_to_region', default=True): bool, + 'snapshot_height': int + }, + vol.Optional('zones', default={}): { + str: { + vol.Required('coordinates'): vol.Any(str, [str]), + 'filters': FILTER_SCHEMA + } + }, + vol.Optional('save_clips', default=DEFAULT_CAMERA_SAVE_CLIPS): { + vol.Optional('enabled', default=False): bool, + vol.Optional('pre_capture', default=30): int, + 'objects': [str], + }, + vol.Optional('snapshots', default=DEFAULT_CAMERA_SNAPSHOTS): { + vol.Optional('show_timestamp', default=True): bool, + vol.Optional('draw_zones', default=False): bool, + vol.Optional('draw_bounding_boxes', default=True): bool + }, + 'objects': OBJECTS_SCHEMA + } + } +) + +FRIGATE_CONFIG_SCHEMA = vol.Schema( + { + vol.Optional('web_port', default=5000): int, + vol.Optional('detectors', default=DEFAULT_DETECTORS): DETECTORS_SCHEMA, + 'mqtt': MQTT_SCHEMA, + vol.Optional('save_clips', default={}): SAVE_CLIPS_SCHEMA, + vol.Optional('ffmpeg', default={}): GLOBAL_FFMPEG_SCHEMA, + vol.Optional('objects', default={}): OBJECTS_SCHEMA, + vol.Required('cameras', default={}): CAMERAS_SCHEMA + } +) diff --git a/frigate/test/__init__.py b/frigate/test/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/frigate/test/test_config.py b/frigate/test/test_config.py new file mode 100644 index 000000000..9fa858735 --- /dev/null +++ b/frigate/test/test_config.py @@ -0,0 +1,26 @@ +import json +from unittest import TestCase, main +import voluptuous as vol +from frigate.config import FRIGATE_CONFIG_SCHEMA + +class TestConfig(TestCase): + def test_empty(self): + FRIGATE_CONFIG_SCHEMA({}) + + def test_minimal(self): + minimal = { + 'mqtt': { + 'host': 'mqtt' + }, + 'cameras': { + 'back': { + 'ffmpeg': { + 'input': 'rtsp://10.0.0.1:554/video' + } + } + } + } + FRIGATE_CONFIG_SCHEMA(minimal) + +if __name__ == '__main__': + main(verbosity=2)