blakeblackshear.frigate/frigate/config.py

1073 lines
32 KiB
Python
Raw Normal View History

2020-11-03 15:15:58 +01:00
import base64
import json
2020-12-21 15:03:27 +01:00
import logging
2020-11-03 15:15:58 +01:00
import os
from typing import Dict
import cv2
import matplotlib.pyplot as plt
import numpy as np
2020-11-01 13:17:44 +01:00
import voluptuous as vol
2020-11-04 13:31:25 +01:00
import yaml
2020-11-01 13:17:44 +01:00
2020-12-01 14:22:23 +01:00
from frigate.const import RECORD_DIR, CLIPS_DIR, CACHE_DIR
2021-01-14 14:19:12 +01:00
from frigate.util import create_mask
2020-12-01 14:22:23 +01:00
2020-12-21 15:03:27 +01:00
logger = logging.getLogger(__name__)
DEFAULT_TRACKED_OBJECTS = ['person']
2020-11-01 13:17:44 +01:00
DETECTORS_SCHEMA = vol.Schema(
{
vol.Required(str): {
2021-01-09 18:26:46 +01:00
vol.Required('type', default='edgetpu'): vol.In(['cpu', 'edgetpu']),
2020-12-19 17:04:13 +01:00
vol.Optional('device', default='usb'): str,
vol.Optional('num_threads', default=3): int
2020-11-01 13:17:44 +01:00
}
}
)
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,
vol.Optional('stats_interval', default=60): int,
2020-11-01 13:17:44 +01:00
'user': str,
'password': str
}
)
2021-01-13 13:49:05 +01:00
RETAIN_SCHEMA = vol.Schema(
2020-11-23 15:25:46 +01:00
{
vol.Required('default',default=10): int,
'objects': {
str: int
}
}
)
2020-12-23 14:16:37 +01:00
CLIPS_SCHEMA = vol.Schema(
2020-11-01 13:17:44 +01:00
{
vol.Optional('max_seconds', default=300): int,
2020-12-20 15:00:07 +01:00
'tmpfs_cache_size': str,
2021-01-13 13:49:05 +01:00
vol.Optional('retain', default={}): RETAIN_SCHEMA
2020-11-01 13:17:44 +01:00
}
)
2020-12-05 01:23:54 +01:00
FFMPEG_GLOBAL_ARGS_DEFAULT = ['-hide_banner','-loglevel','fatal']
2020-11-01 13:17:44 +01:00
FFMPEG_INPUT_ARGS_DEFAULT = ['-avoid_negative_ts', 'make_zero',
2020-12-05 16:47:29 +01:00
'-fflags', '+genpts+discardcorrupt',
2020-11-01 13:17:44 +01:00
'-rtsp_transport', 'tcp',
'-stimeout', '5000000',
'-use_wallclock_as_timestamps', '1']
2020-11-29 22:55:53 +01:00
DETECT_FFMPEG_OUTPUT_ARGS_DEFAULT = ['-f', 'rawvideo',
2020-11-01 13:17:44 +01:00
'-pix_fmt', 'yuv420p']
2020-11-29 22:55:53 +01:00
RTMP_FFMPEG_OUTPUT_ARGS_DEFAULT = ["-c", "copy", "-f", "flv"]
SAVE_CLIPS_FFMPEG_OUTPUT_ARGS_DEFAULT = ["-f", "segment", "-segment_time",
"10", "-segment_format", "mp4", "-reset_timestamps", "1", "-strftime",
"1", "-c", "copy", "-an"]
RECORD_FFMPEG_OUTPUT_ARGS_DEFAULT = ["-f", "segment", "-segment_time",
"60", "-segment_format", "mp4", "-reset_timestamps", "1", "-strftime",
"1", "-c", "copy", "-an"]
2020-11-01 13:17:44 +01:00
GLOBAL_FFMPEG_SCHEMA = vol.Schema(
2021-01-09 18:26:46 +01:00
{
2020-11-29 22:55:53 +01:00
vol.Optional('global_args', default=FFMPEG_GLOBAL_ARGS_DEFAULT): vol.Any(str, [str]),
vol.Optional('hwaccel_args', default=[]): vol.Any(str, [str]),
vol.Optional('input_args', default=FFMPEG_INPUT_ARGS_DEFAULT): vol.Any(str, [str]),
vol.Optional('output_args', default={}): {
vol.Optional('detect', default=DETECT_FFMPEG_OUTPUT_ARGS_DEFAULT): vol.Any(str, [str]),
vol.Optional('record', default=RECORD_FFMPEG_OUTPUT_ARGS_DEFAULT): vol.Any(str, [str]),
vol.Optional('clips', default=SAVE_CLIPS_FFMPEG_OUTPUT_ARGS_DEFAULT): vol.Any(str, [str]),
vol.Optional('rtmp', default=RTMP_FFMPEG_OUTPUT_ARGS_DEFAULT): vol.Any(str, [str]),
}
2020-11-01 13:17:44 +01:00
}
)
MOTION_SCHEMA = vol.Schema(
{
2021-01-14 14:19:12 +01:00
'mask': vol.Any(str, [str]),
'threshold': vol.Range(min=1, max=255),
'contour_area': int,
'delta_alpha': float,
'frame_alpha': float,
'frame_height': int
}
)
DETECT_SCHEMA = vol.Schema(
{
'max_disappeared': int
}
)
2020-11-01 13:17:44 +01:00
FILTER_SCHEMA = vol.Schema(
2021-01-09 18:26:46 +01:00
{
2020-11-01 13:17:44 +01:00
str: {
'min_area': int,
'max_area': int,
'threshold': float,
2020-11-01 13:17:44 +01:00
}
}
)
2020-11-03 15:15:58 +01:00
def filters_for_all_tracked_objects(object_config):
for tracked_object in object_config.get('track', DEFAULT_TRACKED_OBJECTS):
2020-11-03 15:15:58 +01:00
if not 'filters' in object_config:
object_config['filters'] = {}
if not tracked_object in object_config['filters']:
object_config['filters'][tracked_object] = {}
return object_config
OBJECTS_SCHEMA = vol.Schema(vol.All(filters_for_all_tracked_objects,
2020-11-01 13:17:44 +01:00
{
'track': [str],
2021-01-14 14:19:12 +01:00
vol.Optional('filters', default = {}): FILTER_SCHEMA.extend(
{
str: {
'min_score': float,
2021-01-14 14:19:12 +01:00
'mask': vol.Any(str, [str]),
}
})
2020-11-01 13:17:44 +01:00
}
2020-11-03 15:15:58 +01:00
))
2020-11-01 13:17:44 +01:00
2020-11-29 22:55:53 +01:00
def each_role_used_once(inputs):
roles = [role for i in inputs for role in i['roles']]
roles_set = set(roles)
if len(roles) > len(roles_set):
raise ValueError
return inputs
def detect_is_required(inputs):
roles = [role for i in inputs for role in i['roles']]
if not 'detect' in roles:
raise ValueError
return inputs
2020-11-01 13:17:44 +01:00
CAMERA_FFMPEG_SCHEMA = vol.Schema(
2021-01-09 18:26:46 +01:00
{
2020-11-29 22:55:53 +01:00
vol.Required('inputs'): vol.All([{
vol.Required('path'): str,
vol.Required('roles'): ['detect', 'clips', 'record', 'rtmp'],
'global_args': vol.Any(str, [str]),
'hwaccel_args': vol.Any(str, [str]),
'input_args': vol.Any(str, [str]),
}], vol.Msg(each_role_used_once, msg="Each input role may only be used once"),
vol.Msg(detect_is_required, msg="The detect role is required")),
2020-11-29 22:55:53 +01:00
'output_args': {
vol.Optional('detect', default=DETECT_FFMPEG_OUTPUT_ARGS_DEFAULT): vol.Any(str, [str]),
vol.Optional('record', default=RECORD_FFMPEG_OUTPUT_ARGS_DEFAULT): vol.Any(str, [str]),
vol.Optional('clips', default=SAVE_CLIPS_FFMPEG_OUTPUT_ARGS_DEFAULT): vol.Any(str, [str]),
vol.Optional('rtmp', default=RTMP_FFMPEG_OUTPUT_ARGS_DEFAULT): vol.Any(str, [str]),
}
2020-11-01 13:17:44 +01:00
}
)
def ensure_zones_and_cameras_have_different_names(cameras):
zones = [zone for camera in cameras.values() for zone in camera['zones'].keys()]
for zone in zones:
if zone in cameras.keys():
raise ValueError
return cameras
CAMERAS_SCHEMA = vol.Schema(vol.All(
2020-11-01 13:17:44 +01:00
{
str: {
vol.Required('ffmpeg'): CAMERA_FFMPEG_SCHEMA,
2020-11-03 15:15:58 +01:00
vol.Required('height'): int,
vol.Required('width'): int,
2020-11-01 13:17:44 +01:00
'fps': int,
vol.Optional('best_image_timeout', default=60): int,
vol.Optional('zones', default={}): {
str: {
vol.Required('coordinates'): vol.Any(str, [str]),
2020-11-09 14:30:44 +01:00
vol.Optional('filters', default={}): FILTER_SCHEMA
2020-11-01 13:17:44 +01:00
}
2020-11-30 02:39:33 +01:00
},
2020-12-23 14:16:37 +01:00
vol.Optional('clips', default={}): {
2020-11-01 13:17:44 +01:00
vol.Optional('enabled', default=False): bool,
2020-12-19 16:06:06 +01:00
vol.Optional('pre_capture', default=5): int,
vol.Optional('post_capture', default=5): int,
2020-11-01 13:17:44 +01:00
'objects': [str],
2021-01-13 13:49:05 +01:00
vol.Optional('retain', default={}): RETAIN_SCHEMA,
2020-11-30 02:39:33 +01:00
},
vol.Optional('record', default={}): {
'enabled': bool,
'retain_days': int,
},
vol.Optional('rtmp', default={}): {
vol.Required('enabled', default=True): bool,
},
vol.Optional('snapshots', default={}): {
vol.Optional('enabled', default=False): bool,
vol.Optional('timestamp', default=False): bool,
vol.Optional('bounding_box', default=False): bool,
vol.Optional('crop', default=False): bool,
2021-01-13 13:49:05 +01:00
'height': int,
vol.Optional('retain', default={}): RETAIN_SCHEMA,
},
vol.Optional('mqtt', default={}): {
vol.Optional('enabled', default=True): bool,
vol.Optional('timestamp', default=True): bool,
vol.Optional('bounding_box', default=True): bool,
vol.Optional('crop', default=True): bool,
vol.Optional('height', default=270): int
2020-11-30 02:39:33 +01:00
},
vol.Optional('objects', default={}): OBJECTS_SCHEMA,
vol.Optional('motion', default={}): MOTION_SCHEMA,
vol.Optional('detect', default={}): DETECT_SCHEMA.extend({
vol.Optional('enabled', default=True): bool
})
2020-11-01 13:17:44 +01:00
}
}, vol.Msg(ensure_zones_and_cameras_have_different_names, msg='Zones cannot share names with cameras'))
2020-11-01 13:17:44 +01:00
)
FRIGATE_CONFIG_SCHEMA = vol.Schema(
{
2020-12-13 17:04:55 +01:00
vol.Optional('database', default={}): {
vol.Optional('path', default=os.path.join(CLIPS_DIR, 'frigate.db')): str
},
vol.Optional('model', default={'width': 320, 'height': 320}): {
2020-12-09 02:39:46 +01:00
vol.Required('width'): int,
vol.Required('height'): int
},
2020-11-01 13:17:44 +01:00
vol.Optional('detectors', default=DEFAULT_DETECTORS): DETECTORS_SCHEMA,
'mqtt': MQTT_SCHEMA,
2020-12-04 13:59:03 +01:00
vol.Optional('logger', default={'default': 'info', 'logs': {}}): {
vol.Optional('default', default='info'): vol.In(['info', 'debug', 'warning', 'error', 'critical']),
vol.Optional('logs', default={}): {str: vol.In(['info', 'debug', 'warning', 'error', 'critical']) }
},
2021-01-13 13:49:05 +01:00
vol.Optional('snapshots', default={}): {
vol.Optional('retain', default={}): RETAIN_SCHEMA
},
2020-12-23 14:16:37 +01:00
vol.Optional('clips', default={}): CLIPS_SCHEMA,
2020-11-30 02:39:33 +01:00
vol.Optional('record', default={}): {
vol.Optional('enabled', default=False): bool,
vol.Optional('retain_days', default=30): int,
},
2020-11-01 13:17:44 +01:00
vol.Optional('ffmpeg', default={}): GLOBAL_FFMPEG_SCHEMA,
vol.Optional('objects', default={}): OBJECTS_SCHEMA,
vol.Optional('motion', default={}): MOTION_SCHEMA,
vol.Optional('detect', default={}): DETECT_SCHEMA,
2021-01-16 04:33:53 +01:00
vol.Required('cameras', default={}): CAMERAS_SCHEMA,
vol.Optional('environment_vars', default={}): { str: str }
2020-11-01 13:17:44 +01:00
}
)
2020-11-03 15:15:58 +01:00
2020-12-13 17:04:55 +01:00
class DatabaseConfig():
def __init__(self, config):
self._path = config['path']
@property
def path(self):
return self._path
2021-01-09 18:26:46 +01:00
2020-12-13 17:04:55 +01:00
def to_dict(self):
return {
'path': self.path
}
2020-12-09 02:39:46 +01:00
class ModelConfig():
def __init__(self, config):
self._width = config['width']
self._height = config['height']
2021-01-09 18:26:46 +01:00
2020-12-09 02:39:46 +01:00
@property
def width(self):
return self._width
2021-01-09 18:26:46 +01:00
2020-12-09 02:39:46 +01:00
@property
def height(self):
return self._height
2021-01-09 18:26:46 +01:00
2020-12-09 02:39:46 +01:00
def to_dict(self):
return {
'width': self.width,
'height': self.height
}
2020-11-03 15:15:58 +01:00
class DetectorConfig():
def __init__(self, config):
self._type = config['type']
self._device = config['device']
2020-12-19 17:04:13 +01:00
self._num_threads = config['num_threads']
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
@property
def type(self):
return self._type
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
@property
def device(self):
return self._device
2021-01-09 18:26:46 +01:00
2020-12-19 17:04:13 +01:00
@property
def num_threads(self):
return self._num_threads
2021-01-09 18:26:46 +01:00
2020-11-18 04:11:19 +01:00
def to_dict(self):
return {
'type': self.type,
2020-12-19 17:04:13 +01:00
'device': self.device,
'num_threads': self.num_threads
2020-11-18 04:11:19 +01:00
}
2020-11-03 15:15:58 +01:00
2020-12-04 13:59:03 +01:00
class LoggerConfig():
def __init__(self, config):
self._default = config['default'].upper()
self._logs = {k: v.upper() for k, v in config['logs'].items()}
2021-01-09 18:26:46 +01:00
2020-12-04 13:59:03 +01:00
@property
def default(self):
return self._default
2021-01-09 18:26:46 +01:00
2020-12-04 13:59:03 +01:00
@property
def logs(self):
return self._logs
2021-01-09 18:26:46 +01:00
2020-12-04 13:59:03 +01:00
def to_dict(self):
return {
'default': self.default,
'logs': self.logs
}
2020-11-03 15:15:58 +01:00
class MqttConfig():
def __init__(self, config):
self._host = config['host']
self._port = config['port']
self._topic_prefix = config['topic_prefix']
self._client_id = config['client_id']
self._user = config.get('user')
self._password = config.get('password')
self._stats_interval = config.get('stats_interval')
2020-11-03 15:15:58 +01:00
@property
def host(self):
return self._host
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
@property
def port(self):
return self._port
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
@property
def topic_prefix(self):
return self._topic_prefix
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
@property
def client_id(self):
return self._client_id
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
@property
def user(self):
return self._user
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
@property
def password(self):
return self._password
@property
def stats_interval(self):
return self._stats_interval
2020-11-18 04:11:19 +01:00
def to_dict(self):
return {
'host': self.host,
'port': self.port,
'topic_prefix': self.topic_prefix,
'client_id': self.client_id,
'user': self.user,
'stats_interval': self.stats_interval
2020-11-18 04:11:19 +01:00
}
2020-11-29 22:55:53 +01:00
class CameraInput():
def __init__(self, global_config, ffmpeg_input):
self._path = ffmpeg_input['path']
self._roles = ffmpeg_input['roles']
self._global_args = ffmpeg_input.get('global_args', global_config['global_args'])
self._hwaccel_args = ffmpeg_input.get('hwaccel_args', global_config['hwaccel_args'])
self._input_args = ffmpeg_input.get('input_args', global_config['input_args'])
2021-01-09 18:26:46 +01:00
2020-11-29 22:55:53 +01:00
@property
def path(self):
return self._path
2021-01-09 18:26:46 +01:00
2020-11-29 22:55:53 +01:00
@property
def roles(self):
return self._roles
2021-01-09 18:26:46 +01:00
2020-11-29 22:55:53 +01:00
@property
def global_args(self):
return self._global_args if isinstance(self._global_args, list) else self._global_args.split(' ')
2021-01-09 18:26:46 +01:00
2020-11-29 22:55:53 +01:00
@property
def hwaccel_args(self):
return self._hwaccel_args if isinstance(self._hwaccel_args, list) else self._hwaccel_args.split(' ')
@property
def input_args(self):
return self._input_args if isinstance(self._input_args, list) else self._input_args.split(' ')
class CameraFfmpegConfig():
def __init__(self, global_config, config):
self._inputs = [CameraInput(global_config, i) for i in config['inputs']]
self._output_args = config.get('output_args', global_config['output_args'])
2021-01-09 18:26:46 +01:00
2020-11-29 22:55:53 +01:00
@property
def inputs(self):
return self._inputs
2021-01-09 18:26:46 +01:00
2020-11-29 22:55:53 +01:00
@property
def output_args(self):
return {k: v if isinstance(v, list) else v.split(' ') for k, v in self._output_args.items()}
2021-01-13 13:49:05 +01:00
class RetainConfig():
2020-11-23 15:25:46 +01:00
def __init__(self, global_config, config):
self._default = config.get('default', global_config.get('default'))
2020-11-26 03:33:33 +01:00
self._objects = config.get('objects', global_config.get('objects', {}))
2021-01-09 18:26:46 +01:00
2020-11-23 15:25:46 +01:00
@property
def default(self):
return self._default
2021-01-09 18:26:46 +01:00
2020-11-23 15:25:46 +01:00
@property
def objects(self):
return self._objects
2021-01-09 18:26:46 +01:00
2020-11-23 15:25:46 +01:00
def to_dict(self):
return {
'default': self.default,
'objects': self.objects
}
2020-12-23 14:16:37 +01:00
class ClipsConfig():
2020-11-03 15:15:58 +01:00
def __init__(self, config):
self._max_seconds = config['max_seconds']
2020-12-20 15:00:07 +01:00
self._tmpfs_cache_size = config.get('tmpfs_cache_size', '').strip()
2021-01-13 13:49:05 +01:00
self._retain = RetainConfig(config['retain'], config['retain'])
2020-11-03 15:15:58 +01:00
@property
def max_seconds(self):
return self._max_seconds
2021-01-09 18:26:46 +01:00
2020-12-20 15:00:07 +01:00
@property
def tmpfs_cache_size(self):
return self._tmpfs_cache_size
2020-12-01 14:22:23 +01:00
2020-11-24 15:09:16 +01:00
@property
def retain(self):
return self._retain
2021-01-09 18:26:46 +01:00
2020-11-18 04:11:19 +01:00
def to_dict(self):
return {
'max_seconds': self.max_seconds,
2020-12-20 15:00:07 +01:00
'tmpfs_cache_size': self.tmpfs_cache_size,
2020-11-24 15:09:16 +01:00
'retain': self.retain.to_dict()
2020-11-18 04:11:19 +01:00
}
2020-11-03 15:15:58 +01:00
2021-01-14 13:38:13 +01:00
class SnapshotsConfig():
def __init__(self, config):
self._retain = RetainConfig(config['retain'], config['retain'])
@property
def retain(self):
return self._retain
def to_dict(self):
return {
'retain': self.retain.to_dict()
}
2020-11-30 02:39:33 +01:00
class RecordConfig():
def __init__(self, global_config, config):
self._enabled = config.get('enabled', global_config['enabled'])
self._retain_days = config.get('retain_days', global_config['retain_days'])
@property
def enabled(self):
return self._enabled
2021-01-09 18:26:46 +01:00
2020-11-30 02:39:33 +01:00
@property
def retain_days(self):
return self._retain_days
2021-01-09 18:26:46 +01:00
2020-11-30 02:39:33 +01:00
def to_dict(self):
return {
'enabled': self.enabled,
'retain_days': self.retain_days,
}
2020-11-03 15:15:58 +01:00
class FilterConfig():
def __init__(self, global_config, config, frame_shape=None):
self._min_area = config.get('min_area', global_config.get('min_area', 0))
self._max_area = config.get('max_area', global_config.get('max_area', 24000000))
self._threshold = config.get('threshold', global_config.get('threshold', 0.7))
self._min_score = config.get('min_score', global_config.get('min_score', 0.5))
2021-01-14 14:19:12 +01:00
self._raw_mask = config.get('mask')
2021-01-15 14:52:28 +01:00
self._mask = create_mask(frame_shape, self._raw_mask) if self._raw_mask else None
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
@property
def min_area(self):
return self._min_area
@property
def max_area(self):
return self._max_area
@property
def threshold(self):
return self._threshold
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
@property
def min_score(self):
return self._min_score
2021-01-09 18:26:46 +01:00
2021-01-14 14:19:12 +01:00
@property
def mask(self):
return self._mask
2020-11-18 04:11:19 +01:00
def to_dict(self):
return {
'min_area': self.min_area,
'max_area': self.max_area,
'threshold': self.threshold,
2021-01-14 14:19:12 +01:00
'min_score': self.min_score,
'mask': self._raw_mask
2020-11-18 04:11:19 +01:00
}
2020-11-03 15:15:58 +01:00
class ObjectConfig():
2021-01-14 14:19:12 +01:00
def __init__(self, global_config, config, frame_shape):
self._track = config.get('track', global_config.get('track', DEFAULT_TRACKED_OBJECTS))
self._filters = { name: FilterConfig(global_config.get('filters').get(name, {}), config.get('filters').get(name, {}), frame_shape) for name in self._track }
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
@property
def track(self):
return self._track
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
@property
def filters(self) -> Dict[str, FilterConfig]:
return self._filters
2020-11-18 04:11:19 +01:00
def to_dict(self):
return {
'track': self.track,
'filters': { k: f.to_dict() for k, f in self.filters.items() }
}
2020-11-03 15:15:58 +01:00
class CameraSnapshotsConfig():
2021-01-13 13:49:05 +01:00
def __init__(self, global_config, config):
self._enabled = config['enabled']
self._timestamp = config['timestamp']
self._bounding_box = config['bounding_box']
self._crop = config['crop']
self._height = config.get('height')
2021-01-13 13:49:05 +01:00
self._retain = RetainConfig(global_config['snapshots']['retain'], config['retain'])
@property
def enabled(self):
return self._enabled
2021-01-09 18:26:46 +01:00
@property
def timestamp(self):
return self._timestamp
@property
def bounding_box(self):
return self._bounding_box
@property
def crop(self):
return self._crop
@property
def height(self):
return self._height
2021-01-13 13:49:05 +01:00
@property
def retain(self):
return self._retain
def to_dict(self):
return {
'enabled': self.enabled,
'timestamp': self.timestamp,
'bounding_box': self.bounding_box,
'crop': self.crop,
2021-01-13 13:49:05 +01:00
'height': self.height,
'retain': self.retain.to_dict()
}
class CameraMqttConfig():
def __init__(self, config):
self._enabled = config['enabled']
self._timestamp = config['timestamp']
self._bounding_box = config['bounding_box']
self._crop = config['crop']
self._height = config.get('height')
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
@property
def enabled(self):
return self._enabled
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
@property
def timestamp(self):
return self._timestamp
2020-11-03 15:15:58 +01:00
@property
def bounding_box(self):
return self._bounding_box
2020-11-03 15:15:58 +01:00
@property
def crop(self):
return self._crop
@property
def height(self):
return self._height
2021-01-09 18:26:46 +01:00
2020-11-18 04:11:19 +01:00
def to_dict(self):
return {
'enabled': self.enabled,
'timestamp': self.timestamp,
'bounding_box': self.bounding_box,
'crop': self.crop,
2020-11-18 04:11:19 +01:00
'height': self.height
}
2020-12-23 14:16:37 +01:00
class CameraClipsConfig():
2020-11-23 15:25:46 +01:00
def __init__(self, global_config, config):
2020-11-03 15:15:58 +01:00
self._enabled = config['enabled']
self._pre_capture = config['pre_capture']
2020-12-19 16:06:06 +01:00
self._post_capture = config['post_capture']
self._objects = config.get('objects')
2021-01-13 13:49:05 +01:00
self._retain = RetainConfig(global_config['clips']['retain'], config['retain'])
2020-11-03 15:15:58 +01:00
@property
def enabled(self):
return self._enabled
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
@property
def pre_capture(self):
return self._pre_capture
2021-01-09 18:26:46 +01:00
2020-12-19 16:06:06 +01:00
@property
def post_capture(self):
return self._post_capture
2020-11-03 15:15:58 +01:00
@property
def objects(self):
return self._objects
2021-01-09 18:26:46 +01:00
2020-11-23 15:25:46 +01:00
@property
def retain(self):
return self._retain
2021-01-09 18:26:46 +01:00
2020-11-18 04:11:19 +01:00
def to_dict(self):
return {
'enabled': self.enabled,
'pre_capture': self.pre_capture,
2020-12-19 16:06:06 +01:00
'post_capture': self.post_capture,
2020-11-23 15:25:46 +01:00
'objects': self.objects,
'retain': self.retain.to_dict()
2020-11-18 04:11:19 +01:00
}
2020-11-29 22:55:53 +01:00
2020-11-28 14:58:27 +01:00
class CameraRtmpConfig():
2020-11-29 22:55:53 +01:00
def __init__(self, global_config, config):
2020-11-28 14:58:27 +01:00
self._enabled = config['enabled']
2021-01-09 18:26:46 +01:00
2020-11-28 14:58:27 +01:00
@property
def enabled(self):
return self._enabled
2021-01-09 18:26:46 +01:00
2020-11-28 14:58:27 +01:00
def to_dict(self):
return {
2020-11-29 22:55:53 +01:00
'enabled': self.enabled,
2020-11-28 14:58:27 +01:00
}
2020-11-03 15:15:58 +01:00
class MotionConfig():
2021-01-14 14:19:12 +01:00
def __init__(self, global_config, config, frame_shape):
self._raw_mask = config.get('mask')
2021-01-15 14:52:28 +01:00
if self._raw_mask:
self._mask = create_mask(frame_shape, self._raw_mask)
else:
default_mask = np.zeros(frame_shape, np.uint8)
default_mask[:] = 255
self._mask = default_mask
self._threshold = config.get('threshold', global_config.get('threshold', 25))
self._contour_area = config.get('contour_area', global_config.get('contour_area', 100))
self._delta_alpha = config.get('delta_alpha', global_config.get('delta_alpha', 0.2))
self._frame_alpha = config.get('frame_alpha', global_config.get('frame_alpha', 0.2))
2021-01-14 14:19:12 +01:00
self._frame_height = config.get('frame_height', global_config.get('frame_height', frame_shape[0]//6))
@property
def mask(self):
return self._mask
2021-01-09 18:26:46 +01:00
@property
def threshold(self):
return self._threshold
@property
def contour_area(self):
return self._contour_area
@property
def delta_alpha(self):
return self._delta_alpha
@property
def frame_alpha(self):
return self._frame_alpha
@property
def frame_height(self):
return self._frame_height
2021-01-09 18:26:46 +01:00
def to_dict(self):
return {
2021-01-14 14:19:12 +01:00
'mask': self._raw_mask,
'threshold': self.threshold,
'contour_area': self.contour_area,
'delta_alpha': self.delta_alpha,
'frame_alpha': self.frame_alpha,
'frame_height': self.frame_height,
}
class DetectConfig():
def __init__(self, global_config, config, camera_fps):
self._enabled = config['enabled']
self._max_disappeared = config.get('max_disappeared', global_config.get('max_disappeared', camera_fps*2))
2021-01-09 18:26:46 +01:00
@property
def enabled(self):
return self._enabled
@property
def max_disappeared(self):
return self._max_disappeared
2021-01-09 18:26:46 +01:00
def to_dict(self):
return {
'enabled': self.enabled,
'max_disappeared': self._max_disappeared,
}
2020-11-03 15:15:58 +01:00
class ZoneConfig():
def __init__(self, name, config):
self._coordinates = config['coordinates']
2021-01-18 13:38:26 +01:00
self._filters = { name: FilterConfig(c, c) for name, c in config['filters'].items() }
2020-11-03 15:15:58 +01:00
if isinstance(self._coordinates, list):
self._contour = np.array([[int(p.split(',')[0]), int(p.split(',')[1])] for p in self._coordinates])
elif isinstance(self._coordinates, str):
points = self._coordinates.split(',')
self._contour = np.array([[int(points[i]), int(points[i+1])] for i in range(0, len(points), 2)])
else:
print(f"Unable to parse zone coordinates for {name}")
self._contour = np.array([])
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
self._color = (0,0,0)
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
@property
def coordinates(self):
return self._coordinates
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
@property
def contour(self):
return self._contour
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
@contour.setter
def contour(self, val):
self._contour = val
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
@property
def color(self):
return self._color
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
@color.setter
def color(self, val):
self._color = val
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
@property
def filters(self):
return self._filters
2021-01-09 18:26:46 +01:00
2020-11-18 04:11:19 +01:00
def to_dict(self):
return {
2021-01-09 18:26:46 +01:00
'filters': {k: f.to_dict() for k, f in self.filters.items()},
'coordinates': self._coordinates
2020-11-18 04:11:19 +01:00
}
2020-11-03 15:15:58 +01:00
class CameraConfig():
2020-12-01 14:22:23 +01:00
def __init__(self, name, config, global_config):
2020-11-03 15:15:58 +01:00
self._name = name
2020-11-29 22:55:53 +01:00
self._ffmpeg = CameraFfmpegConfig(global_config['ffmpeg'], config['ffmpeg'])
2020-11-03 15:15:58 +01:00
self._height = config.get('height')
self._width = config.get('width')
self._frame_shape = (self._height, self._width)
self._frame_shape_yuv = (self._frame_shape[0]*3//2, self._frame_shape[1])
self._fps = config.get('fps')
self._best_image_timeout = config['best_image_timeout']
self._zones = { name: ZoneConfig(name, z) for name, z in config['zones'].items() }
2020-12-23 14:16:37 +01:00
self._clips = CameraClipsConfig(global_config, config['clips'])
2020-11-30 02:39:33 +01:00
self._record = RecordConfig(global_config['record'], config['record'])
2020-11-29 22:55:53 +01:00
self._rtmp = CameraRtmpConfig(global_config, config['rtmp'])
2021-01-13 13:49:05 +01:00
self._snapshots = CameraSnapshotsConfig(global_config, config['snapshots'])
self._mqtt = CameraMqttConfig(config['mqtt'])
2021-01-14 14:19:12 +01:00
self._objects = ObjectConfig(global_config['objects'], config.get('objects', {}), self._frame_shape)
self._motion = MotionConfig(global_config['motion'], config['motion'], self._frame_shape)
self._detect = DetectConfig(global_config['detect'], config['detect'], config.get('fps', 5))
2020-11-03 15:15:58 +01:00
2020-11-29 22:55:53 +01:00
self._ffmpeg_cmds = []
for ffmpeg_input in self._ffmpeg.inputs:
ffmpeg_cmd = self._get_ffmpeg_cmd(ffmpeg_input)
if ffmpeg_cmd is None:
continue
2020-11-29 22:55:53 +01:00
self._ffmpeg_cmds.append({
'roles': ffmpeg_input.roles,
'cmd': ffmpeg_cmd
2020-11-29 22:55:53 +01:00
})
2020-11-03 15:15:58 +01:00
self._set_zone_colors(self._zones)
2020-12-01 14:22:23 +01:00
def _get_ffmpeg_cmd(self, ffmpeg_input):
2020-11-29 22:55:53 +01:00
ffmpeg_output_args = []
if 'detect' in ffmpeg_input.roles:
ffmpeg_output_args = self.ffmpeg.output_args['detect'] + ffmpeg_output_args + ['pipe:']
if self.fps:
ffmpeg_output_args = ["-r", str(self.fps)] + ffmpeg_output_args
if 'rtmp' in ffmpeg_input.roles and self.rtmp.enabled:
ffmpeg_output_args = self.ffmpeg.output_args['rtmp'] + [
2020-11-28 14:58:27 +01:00
f"rtmp://127.0.0.1/live/{self.name}"
] + ffmpeg_output_args
if 'clips' in ffmpeg_input.roles:
2020-11-29 22:55:53 +01:00
ffmpeg_output_args = self.ffmpeg.output_args['clips'] + [
2020-12-01 14:22:23 +01:00
f"{os.path.join(CACHE_DIR, self.name)}-%Y%m%d%H%M%S.mp4"
2020-11-03 15:15:58 +01:00
] + ffmpeg_output_args
2020-11-30 04:31:02 +01:00
if 'record' in ffmpeg_input.roles and self.record.enabled:
ffmpeg_output_args = self.ffmpeg.output_args['record'] + [
2020-12-01 14:22:23 +01:00
f"{os.path.join(RECORD_DIR, self.name)}-%Y%m%d%H%M%S.mp4"
2020-11-30 04:31:02 +01:00
] + ffmpeg_output_args
2021-01-09 18:26:46 +01:00
# if there arent any outputs enabled for this input
if len(ffmpeg_output_args) == 0:
return None
2020-12-12 15:46:12 +01:00
cmd = (['ffmpeg'] +
2020-11-29 22:55:53 +01:00
ffmpeg_input.global_args +
ffmpeg_input.hwaccel_args +
ffmpeg_input.input_args +
['-i', ffmpeg_input.path] +
ffmpeg_output_args)
2021-01-09 18:26:46 +01:00
2020-12-12 15:46:12 +01:00
return [part for part in cmd if part != '']
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
def _set_zone_colors(self, zones: Dict[str, ZoneConfig]):
# set colors for zones
all_zone_names = zones.keys()
zone_colors = {}
colors = plt.cm.get_cmap('tab10', len(all_zone_names))
for i, zone in enumerate(all_zone_names):
zone_colors[zone] = tuple(int(round(255 * c)) for c in colors(i)[:3])
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
for name, zone in zones.items():
zone.color = zone_colors[name]
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
@property
def name(self):
return self._name
@property
def ffmpeg(self):
return self._ffmpeg
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
@property
def height(self):
return self._height
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
@property
def width(self):
return self._width
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
@property
def fps(self):
return self._fps
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
@property
def best_image_timeout(self):
return self._best_image_timeout
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
@property
def zones(self)-> Dict[str, ZoneConfig]:
return self._zones
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
@property
2020-12-23 14:16:37 +01:00
def clips(self):
return self._clips
2021-01-09 18:26:46 +01:00
2020-11-30 02:39:33 +01:00
@property
def record(self):
return self._record
2021-01-09 18:26:46 +01:00
2020-11-28 14:58:27 +01:00
@property
def rtmp(self):
return self._rtmp
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
@property
def snapshots(self):
return self._snapshots
2021-01-09 18:26:46 +01:00
@property
def mqtt(self):
return self._mqtt
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
@property
def objects(self):
return self._objects
2021-01-09 18:26:46 +01:00
@property
def motion(self):
return self._motion
2021-01-09 18:26:46 +01:00
@property
def detect(self):
return self._detect
2020-11-03 15:15:58 +01:00
@property
def frame_shape(self):
return self._frame_shape
@property
def frame_shape_yuv(self):
return self._frame_shape_yuv
@property
2020-11-29 22:55:53 +01:00
def ffmpeg_cmds(self):
return self._ffmpeg_cmds
2020-11-03 15:15:58 +01:00
2020-11-18 04:11:19 +01:00
def to_dict(self):
return {
'name': self.name,
'height': self.height,
'width': self.width,
'fps': self.fps,
'best_image_timeout': self.best_image_timeout,
'zones': {k: z.to_dict() for k, z in self.zones.items()},
2020-12-23 14:16:37 +01:00
'clips': self.clips.to_dict(),
2020-11-30 02:39:33 +01:00
'record': self.record.to_dict(),
2020-11-28 14:58:27 +01:00
'rtmp': self.rtmp.to_dict(),
2020-11-18 04:11:19 +01:00
'snapshots': self.snapshots.to_dict(),
'mqtt': self.mqtt.to_dict(),
2020-11-18 04:11:19 +01:00
'objects': self.objects.to_dict(),
'motion': self.motion.to_dict(),
'detect': self.detect.to_dict(),
2020-11-18 04:11:19 +01:00
'frame_shape': self.frame_shape,
2020-11-29 22:55:53 +01:00
'ffmpeg_cmds': [{'roles': c['roles'], 'cmd': ' '.join(c['cmd'])} for c in self.ffmpeg_cmds],
2020-11-18 04:11:19 +01:00
}
2020-11-03 15:15:58 +01:00
class FrigateConfig():
def __init__(self, config_file=None, config=None):
if config is None and config_file is None:
raise ValueError('config or config_file must be defined')
elif not config_file is None:
config = self._load_file(config_file)
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
config = FRIGATE_CONFIG_SCHEMA(config)
config = self._sub_env_vars(config)
2020-12-13 17:04:55 +01:00
self._database = DatabaseConfig(config['database'])
2020-12-09 02:39:46 +01:00
self._model = ModelConfig(config['model'])
2020-11-03 15:15:58 +01:00
self._detectors = { name: DetectorConfig(d) for name, d in config['detectors'].items() }
self._mqtt = MqttConfig(config['mqtt'])
2020-12-23 14:16:37 +01:00
self._clips = ClipsConfig(config['clips'])
2021-01-16 15:30:18 +01:00
self._snapshots = SnapshotsConfig(config['snapshots'])
2020-12-01 14:22:23 +01:00
self._cameras = { name: CameraConfig(name, c, config) for name, c in config['cameras'].items() }
2020-12-04 13:59:03 +01:00
self._logger = LoggerConfig(config['logger'])
2021-01-16 04:33:53 +01:00
self._environment_vars = config['environment_vars']
2020-11-03 15:15:58 +01:00
def _sub_env_vars(self, config):
frigate_env_vars = {k: v for k, v in os.environ.items() if k.startswith('FRIGATE_')}
if 'password' in config['mqtt']:
2021-01-09 18:26:46 +01:00
config['mqtt']['password'] = config['mqtt']['password'].format(**frigate_env_vars)
2020-11-03 15:15:58 +01:00
for camera in config['cameras'].values():
2020-11-29 22:55:53 +01:00
for i in camera['ffmpeg']['inputs']:
i['path'] = i['path'].format(**frigate_env_vars)
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
return config
def _load_file(self, config_file):
with open(config_file) as f:
raw_config = f.read()
2021-01-09 18:26:46 +01:00
if config_file.endswith(".yml"):
2020-11-03 15:15:58 +01:00
config = yaml.safe_load(raw_config)
elif config_file.endswith(".json"):
config = json.loads(raw_config)
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
return config
2021-01-09 18:26:46 +01:00
2020-11-18 04:11:19 +01:00
def to_dict(self):
return {
2020-12-13 17:04:55 +01:00
'database': self.database.to_dict(),
2020-12-09 02:39:46 +01:00
'model': self.model.to_dict(),
2020-11-18 04:11:19 +01:00
'detectors': {k: d.to_dict() for k, d in self.detectors.items()},
'mqtt': self.mqtt.to_dict(),
2020-12-23 14:16:37 +01:00
'clips': self.clips.to_dict(),
2021-01-14 13:38:13 +01:00
'snapshots': self.snapshots.to_dict(),
2020-12-04 13:59:03 +01:00
'cameras': {k: c.to_dict() for k, c in self.cameras.items()},
2021-01-16 04:33:53 +01:00
'logger': self.logger.to_dict(),
'environment_vars': self._environment_vars
2020-11-18 04:11:19 +01:00
}
2021-01-09 18:26:46 +01:00
2020-12-13 17:04:55 +01:00
@property
def database(self):
return self._database
2021-01-09 18:26:46 +01:00
2020-12-09 02:39:46 +01:00
@property
def model(self):
return self._model
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
@property
def detectors(self) -> Dict[str, DetectorConfig]:
return self._detectors
2021-01-09 18:26:46 +01:00
2020-12-04 13:59:03 +01:00
@property
def logger(self):
return self._logger
2020-11-03 15:15:58 +01:00
@property
def mqtt(self):
return self._mqtt
2021-01-09 18:26:46 +01:00
2020-11-03 15:15:58 +01:00
@property
2020-12-23 14:16:37 +01:00
def clips(self):
return self._clips
2020-11-03 15:15:58 +01:00
2021-01-14 13:38:13 +01:00
@property
def snapshots(self):
return self._snapshots
2020-11-03 15:15:58 +01:00
@property
def cameras(self) -> Dict[str, CameraConfig]:
2020-11-04 13:31:25 +01:00
return self._cameras
2021-01-16 04:33:53 +01:00
@property
def environment_vars(self):
return self._environment_vars