blakeblackshear.frigate/frigate/config.py

1253 lines
35 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__)
2021-02-17 14:23:32 +01:00
DEFAULT_TRACKED_OBJECTS = ["person"]
2020-11-01 13:17:44 +01:00
DETECTORS_SCHEMA = vol.Schema(
{
vol.Required(str): {
2021-02-17 14:23:32 +01:00
vol.Required("type", default="edgetpu"): vol.In(["cpu", "edgetpu"]),
vol.Optional("device", default="usb"): str,
vol.Optional("num_threads", default=3): int,
2020-11-01 13:17:44 +01:00
}
}
)
2021-02-17 14:23:32 +01:00
DEFAULT_DETECTORS = {"coral": {"type": "edgetpu", "device": "usb"}}
2020-11-01 13:17:44 +01:00
MQTT_SCHEMA = vol.Schema(
{
2021-02-17 14:23:32 +01:00
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,
"user": str,
"password": str,
2020-11-01 13:17:44 +01:00
}
)
2021-01-13 13:49:05 +01:00
RETAIN_SCHEMA = vol.Schema(
2021-02-17 14:23:32 +01:00
{vol.Required("default", default=10): int, "objects": {str: int}}
2020-11-23 15:25:46 +01:00
)
2020-12-23 14:16:37 +01:00
CLIPS_SCHEMA = vol.Schema(
2020-11-01 13:17:44 +01:00
{
2021-02-17 14:23:32 +01:00
vol.Optional("max_seconds", default=300): int,
vol.Optional("retain", default={}): RETAIN_SCHEMA,
2020-11-01 13:17:44 +01:00
}
)
2021-02-17 14:23:32 +01:00
FFMPEG_GLOBAL_ARGS_DEFAULT = ["-hide_banner", "-loglevel", "warning"]
FFMPEG_INPUT_ARGS_DEFAULT = [
"-avoid_negative_ts",
"make_zero",
"-fflags",
"+genpts+discardcorrupt",
"-rtsp_transport",
"tcp",
"-stimeout",
"5000000",
"-use_wallclock_as_timestamps",
"1",
]
DETECT_FFMPEG_OUTPUT_ARGS_DEFAULT = ["-f", "rawvideo", "-pix_fmt", "yuv420p"]
2020-11-29 22:55:53 +01:00
RTMP_FFMPEG_OUTPUT_ARGS_DEFAULT = ["-c", "copy", "-f", "flv"]
2021-02-17 14:23:32 +01:00
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
{
2021-02-17 14:23:32 +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-02-17 14:23:32 +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,
}
)
2021-02-17 14:23:32 +01:00
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: {
2021-02-17 14:23:32 +01:00
"min_area": int,
"max_area": int,
"threshold": float,
}
2020-11-01 13:17:44 +01:00
}
)
2021-02-17 14:23:32 +01:00
2020-11-03 15:15:58 +01:00
def filters_for_all_tracked_objects(object_config):
2021-02-17 14:23:32 +01:00
for tracked_object in object_config.get("track", DEFAULT_TRACKED_OBJECTS):
if not "filters" in object_config:
object_config["filters"] = {}
if not tracked_object in object_config["filters"]:
object_config["filters"][tracked_object] = {}
2020-11-03 15:15:58 +01:00
return object_config
2021-02-17 14:23:32 +01:00
OBJECTS_SCHEMA = vol.Schema(
vol.All(
filters_for_all_tracked_objects,
{
"track": [str],
"mask": vol.Any(str, [str]),
vol.Optional("filters", default={}): FILTER_SCHEMA.extend(
{
str: {
"min_score": float,
"mask": vol.Any(str, [str]),
2021-01-14 14:19:12 +01:00
}
2021-02-17 14:23:32 +01:00
}
),
},
)
)
2020-11-01 13:17:44 +01:00
2020-11-29 22:55:53 +01:00
def each_role_used_once(inputs):
2021-02-17 14:23:32 +01:00
roles = [role for i in inputs for role in i["roles"]]
2020-11-29 22:55:53 +01:00
roles_set = set(roles)
if len(roles) > len(roles_set):
raise ValueError
return inputs
2021-02-17 14:23:32 +01:00
def detect_is_required(inputs):
2021-02-17 14:23:32 +01:00
roles = [role for i in inputs for role in i["roles"]]
if not "detect" in roles:
raise ValueError
return inputs
2021-02-17 14:23:32 +01:00
2020-11-01 13:17:44 +01:00
CAMERA_FFMPEG_SCHEMA = vol.Schema(
2021-01-09 18:26:46 +01:00
{
2021-02-17 14:23:32 +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"),
),
"global_args": vol.Any(str, [str]),
"hwaccel_args": vol.Any(str, [str]),
"input_args": vol.Any(str, [str]),
"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
}
)
2021-02-17 14:23:32 +01:00
def ensure_zones_and_cameras_have_different_names(cameras):
2021-02-17 14:23:32 +01:00
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
2021-02-17 14:23:32 +01:00
CAMERAS_SCHEMA = vol.Schema(
vol.All(
{
str: {
vol.Required("ffmpeg"): CAMERA_FFMPEG_SCHEMA,
vol.Required("height"): int,
vol.Required("width"): int,
"fps": int,
vol.Optional("best_image_timeout", default=60): int,
vol.Optional("zones", default={}): {
str: {
vol.Required("coordinates"): vol.Any(str, [str]),
vol.Optional("filters", default={}): FILTER_SCHEMA,
}
},
vol.Optional("clips", default={}): {
vol.Optional("enabled", default=False): bool,
vol.Optional("pre_capture", default=5): int,
vol.Optional("post_capture", default=5): int,
vol.Optional("required_zones", default=[]): [str],
"objects": [str],
vol.Optional("retain", default={}): RETAIN_SCHEMA,
},
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,
vol.Optional("required_zones", default=[]): [str],
"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,
vol.Optional("required_zones", default=[]): [str],
},
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}
),
}
},
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(
{
2021-02-17 14:23:32 +01:00
vol.Optional("database", default={}): {
vol.Optional("path", default=os.path.join(CLIPS_DIR, "frigate.db")): str
2020-12-13 17:04:55 +01:00
},
2021-02-17 14:23:32 +01:00
vol.Optional("model", default={"width": 320, "height": 320}): {
vol.Required("width"): int,
vol.Required("height"): int,
2020-12-09 02:39:46 +01:00
},
2021-02-17 14:23:32 +01:00
vol.Optional("detectors", default=DEFAULT_DETECTORS): DETECTORS_SCHEMA,
"mqtt": MQTT_SCHEMA,
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"])
},
2020-12-04 13:59:03 +01:00
},
2021-02-17 14:23:32 +01:00
vol.Optional("snapshots", default={}): {
vol.Optional("retain", default={}): RETAIN_SCHEMA
2021-01-13 13:49:05 +01:00
},
2021-02-17 14:23:32 +01:00
vol.Optional("clips", default={}): CLIPS_SCHEMA,
vol.Optional("record", default={}): {
vol.Optional("enabled", default=False): bool,
vol.Optional("retain_days", default=30): int,
2020-11-30 02:39:33 +01:00
},
2021-02-17 14:23:32 +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,
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
2021-02-17 14:23:32 +01:00
class DatabaseConfig:
2020-12-13 17:04:55 +01:00
def __init__(self, config):
2021-02-17 14:23:32 +01:00
self._path = config["path"]
2020-12-13 17:04:55 +01:00
@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):
2021-02-17 14:23:32 +01:00
return {"path": self.path}
2020-12-13 17:04:55 +01:00
2021-02-17 14:23:32 +01:00
class ModelConfig:
2020-12-09 02:39:46 +01:00
def __init__(self, config):
2021-02-17 14:23:32 +01:00
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):
2021-02-17 14:23:32 +01:00
return {"width": self.width, "height": self.height}
2020-12-09 02:39:46 +01:00
2021-02-17 14:23:32 +01:00
class DetectorConfig:
2020-11-03 15:15:58 +01:00
def __init__(self, config):
2021-02-17 14:23:32 +01:00
self._type = config["type"]
self._device = config["device"]
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 {
2021-02-17 14:23:32 +01:00
"type": self.type,
"device": self.device,
"num_threads": self.num_threads,
2020-11-18 04:11:19 +01:00
}
2020-11-03 15:15:58 +01:00
2021-02-17 14:23:32 +01:00
class LoggerConfig:
2020-12-04 13:59:03 +01:00
def __init__(self, config):
2021-02-17 14:23:32 +01:00
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):
2021-02-17 14:23:32 +01:00
return {"default": self.default, "logs": self.logs}
2020-11-03 15:15:58 +01:00
2021-02-17 14:23:32 +01:00
class MqttConfig:
2020-11-03 15:15:58 +01:00
def __init__(self, config):
2021-02-17 14:23:32 +01:00
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 {
2021-02-17 14:23:32 +01:00
"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
}
2021-02-17 14:23:32 +01:00
class CameraInput:
2021-02-05 14:22:33 +01:00
def __init__(self, camera_config, global_config, ffmpeg_input):
2021-02-17 14:23:32 +01:00
self._path = ffmpeg_input["path"]
self._roles = ffmpeg_input["roles"]
self._global_args = ffmpeg_input.get(
"global_args",
camera_config.get("global_args", global_config["global_args"]),
)
self._hwaccel_args = ffmpeg_input.get(
"hwaccel_args",
camera_config.get("hwaccel_args", global_config["hwaccel_args"]),
)
self._input_args = ffmpeg_input.get(
"input_args", camera_config.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):
2021-02-17 14:23:32 +01:00
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):
2021-02-17 14:23:32 +01:00
return (
self._hwaccel_args
if isinstance(self._hwaccel_args, list)
else self._hwaccel_args.split(" ")
)
2020-11-29 22:55:53 +01:00
@property
def input_args(self):
2021-02-17 14:23:32 +01:00
return (
self._input_args
if isinstance(self._input_args, list)
else self._input_args.split(" ")
)
2020-11-29 22:55:53 +01:00
2021-02-17 14:23:32 +01:00
class CameraFfmpegConfig:
2020-11-29 22:55:53 +01:00
def __init__(self, global_config, config):
2021-02-17 14:23:32 +01:00
self._inputs = [CameraInput(config, 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):
2021-02-17 14:23:32 +01:00
return {
k: v if isinstance(v, list) else v.split(" ")
for k, v in self._output_args.items()
}
2020-11-29 22:55:53 +01:00
2021-02-17 14:23:32 +01:00
class RetainConfig:
2020-11-23 15:25:46 +01:00
def __init__(self, global_config, config):
2021-02-17 14:23:32 +01:00
self._default = config.get("default", global_config.get("default"))
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):
2021-02-17 14:23:32 +01:00
return {"default": self.default, "objects": self.objects}
2020-11-23 15:25:46 +01:00
2021-02-17 14:23:32 +01:00
class ClipsConfig:
2020-11-03 15:15:58 +01:00
def __init__(self, config):
2021-02-17 14:23:32 +01:00
self._max_seconds = config["max_seconds"]
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-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 {
2021-02-17 14:23:32 +01:00
"max_seconds": self.max_seconds,
"retain": self.retain.to_dict(),
2020-11-18 04:11:19 +01:00
}
2020-11-03 15:15:58 +01:00
2021-02-17 14:23:32 +01:00
class SnapshotsConfig:
2021-01-14 13:38:13 +01:00
def __init__(self, config):
2021-02-17 14:23:32 +01:00
self._retain = RetainConfig(config["retain"], config["retain"])
2021-01-14 13:38:13 +01:00
@property
def retain(self):
return self._retain
def to_dict(self):
2021-02-17 14:23:32 +01:00
return {"retain": self.retain.to_dict()}
2021-01-14 13:38:13 +01:00
2021-02-17 14:23:32 +01:00
class RecordConfig:
2020-11-30 02:39:33 +01:00
def __init__(self, global_config, config):
2021-02-17 14:23:32 +01:00
self._enabled = config.get("enabled", global_config["enabled"])
self._retain_days = config.get("retain_days", global_config["retain_days"])
2020-11-30 02:39:33 +01:00
@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 {
2021-02-17 14:23:32 +01:00
"enabled": self.enabled,
"retain_days": self.retain_days,
2020-11-30 02:39:33 +01:00
}
2021-02-17 14:23:32 +01:00
class FilterConfig:
2021-02-06 13:30:26 +01:00
def __init__(self, global_config, config, global_mask=None, frame_shape=None):
2021-02-17 14:23:32 +01:00
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-02-06 13:30:26 +01:00
self._raw_mask = []
if global_mask:
if isinstance(global_mask, list):
self._raw_mask += global_mask
elif isinstance(global_mask, str):
self._raw_mask += [global_mask]
2021-02-17 14:23:32 +01:00
mask = config.get("mask")
2021-02-06 13:30:26 +01:00
if mask:
if isinstance(mask, list):
self._raw_mask += mask
elif isinstance(mask, str):
self._raw_mask += [mask]
2021-02-17 14:23:32 +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 {
2021-02-17 14:23:32 +01:00
"min_area": self.min_area,
"max_area": self.max_area,
"threshold": self.threshold,
"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
2021-02-17 14:23:32 +01:00
class ObjectConfig:
2021-01-14 14:19:12 +01:00
def __init__(self, global_config, config, frame_shape):
2021-02-17 14:23:32 +01:00
self._track = config.get(
"track", global_config.get("track", DEFAULT_TRACKED_OBJECTS)
)
self._raw_mask = config.get("mask")
self._filters = {
name: FilterConfig(
global_config["filters"].get(name, {}),
config["filters"].get(name, {}),
self._raw_mask,
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 {
2021-02-17 14:23:32 +01:00
"track": self.track,
"mask": self._raw_mask,
"filters": {k: f.to_dict() for k, f in self.filters.items()},
2020-11-18 04:11:19 +01:00
}
2021-02-17 14:23:32 +01:00
class CameraSnapshotsConfig:
2021-01-13 13:49:05 +01:00
def __init__(self, global_config, config):
2021-02-17 14:23:32 +01:00
self._enabled = config["enabled"]
self._timestamp = config["timestamp"]
self._bounding_box = config["bounding_box"]
self._crop = config["crop"]
self._height = config.get("height")
self._retain = RetainConfig(
global_config["snapshots"]["retain"], config["retain"]
)
self._required_zones = config["required_zones"]
@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-02-17 14:23:32 +01:00
2021-01-13 13:49:05 +01:00
@property
def retain(self):
return self._retain
@property
def required_zones(self):
return self._required_zones
2021-02-17 14:23:32 +01:00
def to_dict(self):
return {
2021-02-17 14:23:32 +01:00
"enabled": self.enabled,
"timestamp": self.timestamp,
"bounding_box": self.bounding_box,
"crop": self.crop,
"height": self.height,
"retain": self.retain.to_dict(),
"required_zones": self.required_zones,
}
2021-02-17 14:23:32 +01:00
class CameraMqttConfig:
def __init__(self, config):
2021-02-17 14:23:32 +01:00
self._enabled = config["enabled"]
self._timestamp = config["timestamp"]
self._bounding_box = config["bounding_box"]
self._crop = config["crop"]
self._height = config.get("height")
self._required_zones = config["required_zones"]
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
@property
def required_zones(self):
return self._required_zones
2020-11-18 04:11:19 +01:00
def to_dict(self):
return {
2021-02-17 14:23:32 +01:00
"enabled": self.enabled,
"timestamp": self.timestamp,
"bounding_box": self.bounding_box,
"crop": self.crop,
"height": self.height,
"required_zones": self.required_zones,
2020-11-18 04:11:19 +01:00
}
2021-02-17 14:23:32 +01:00
class CameraClipsConfig:
2020-11-23 15:25:46 +01:00
def __init__(self, global_config, config):
2021-02-17 14:23:32 +01:00
self._enabled = config["enabled"]
self._pre_capture = config["pre_capture"]
self._post_capture = config["post_capture"]
self._objects = config.get("objects")
self._retain = RetainConfig(global_config["clips"]["retain"], config["retain"])
self._required_zones = config["required_zones"]
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
@property
def required_zones(self):
return self._required_zones
2020-11-18 04:11:19 +01:00
def to_dict(self):
return {
2021-02-17 14:23:32 +01:00
"enabled": self.enabled,
"pre_capture": self.pre_capture,
"post_capture": self.post_capture,
"objects": self.objects,
"retain": self.retain.to_dict(),
"required_zones": self.required_zones,
2020-11-18 04:11:19 +01:00
}
2020-11-29 22:55:53 +01:00
2021-02-17 14:23:32 +01:00
class CameraRtmpConfig:
2020-11-29 22:55:53 +01:00
def __init__(self, global_config, config):
2021-02-17 14:23:32 +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 {
2021-02-17 14:23:32 +01:00
"enabled": self.enabled,
2020-11-28 14:58:27 +01:00
}
2020-11-03 15:15:58 +01:00
2021-02-17 14:23:32 +01:00
class MotionConfig:
2021-01-14 14:19:12 +01:00
def __init__(self, global_config, config, frame_shape):
2021-02-17 14:23:32 +01:00
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
2021-02-17 14:23:32 +01:00
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)
)
self._frame_height = config.get(
"frame_height", global_config.get("frame_height", frame_shape[0] // 6)
)
2021-01-14 14:19:12 +01:00
@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-02-17 14:23:32 +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,
}
2021-02-17 14:23:32 +01:00
class DetectConfig:
def __init__(self, global_config, config, camera_fps):
2021-02-17 14:23:32 +01:00
self._enabled = config["enabled"]
self._max_disappeared = config.get(
"max_disappeared", global_config.get("max_disappeared", camera_fps * 5)
)
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 {
2021-02-17 14:23:32 +01:00
"enabled": self.enabled,
"max_disappeared": self._max_disappeared,
}
2021-02-17 14:23:32 +01:00
class ZoneConfig:
2020-11-03 15:15:58 +01:00
def __init__(self, name, config):
2021-02-17 14:23:32 +01:00
self._coordinates = config["coordinates"]
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):
2021-02-17 14:23:32 +01:00
self._contour = np.array(
[
[int(p.split(",")[0]), int(p.split(",")[1])]
for p in self._coordinates
]
)
2020-11-03 15:15:58 +01:00
elif isinstance(self._coordinates, str):
2021-02-17 14:23:32 +01:00
points = self._coordinates.split(",")
self._contour = np.array(
[[int(points[i]), int(points[i + 1])] for i in range(0, len(points), 2)]
)
2020-11-03 15:15:58 +01:00
else:
print(f"Unable to parse zone coordinates for {name}")
self._contour = np.array([])
2021-01-09 18:26:46 +01:00
2021-02-17 14:23:32 +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-02-17 14:23:32 +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
2021-02-17 14:23:32 +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
2021-02-17 14:23:32 +01:00
self._ffmpeg = CameraFfmpegConfig(global_config["ffmpeg"], config["ffmpeg"])
self._height = config.get("height")
self._width = config.get("width")
2020-11-03 15:15:58 +01:00
self._frame_shape = (self._height, self._width)
2021-02-17 14:23:32 +01:00
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()}
self._clips = CameraClipsConfig(global_config, config["clips"])
self._record = RecordConfig(global_config["record"], config["record"])
self._rtmp = CameraRtmpConfig(global_config, config["rtmp"])
self._snapshots = CameraSnapshotsConfig(global_config, config["snapshots"])
self._mqtt = CameraMqttConfig(config["mqtt"])
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
2021-02-17 14:23:32 +01:00
self._ffmpeg_cmds.append({"roles": ffmpeg_input.roles, "cmd": ffmpeg_cmd})
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 = []
2021-02-17 14:23:32 +01:00
if "detect" in ffmpeg_input.roles:
ffmpeg_output_args = (
self.ffmpeg.output_args["detect"] + ffmpeg_output_args + ["pipe:"]
)
2020-11-29 22:55:53 +01:00
if self.fps:
ffmpeg_output_args = ["-r", str(self.fps)] + ffmpeg_output_args
2021-02-17 14:23:32 +01:00
if "rtmp" in ffmpeg_input.roles and self.rtmp.enabled:
ffmpeg_output_args = (
self.ffmpeg.output_args["rtmp"]
+ [f"rtmp://127.0.0.1/live/{self.name}"]
+ ffmpeg_output_args
)
if "clips" in ffmpeg_input.roles:
ffmpeg_output_args = (
self.ffmpeg.output_args["clips"]
+ [f"{os.path.join(CACHE_DIR, self.name)}-%Y%m%d%H%M%S.mp4"]
+ ffmpeg_output_args
)
if "record" in ffmpeg_input.roles and self.record.enabled:
ffmpeg_output_args = (
self.ffmpeg.output_args["record"]
+ [f"{os.path.join(RECORD_DIR, self.name)}-%Y%m%d%H%M%S.mp4"]
+ 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
2021-02-17 14:23:32 +01:00
cmd = (
["ffmpeg"]
+ 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
2021-02-17 14:23:32 +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 = {}
2021-02-17 14:23:32 +01:00
colors = plt.cm.get_cmap("tab10", len(all_zone_names))
2020-11-03 15:15:58 +01:00
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
2021-02-17 14:23:32 +01:00
def zones(self) -> Dict[str, ZoneConfig]:
2020-11-03 15:15:58 +01:00
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 {
2021-02-17 14:23:32 +01:00
"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()},
"clips": self.clips.to_dict(),
"record": self.record.to_dict(),
"rtmp": self.rtmp.to_dict(),
"snapshots": self.snapshots.to_dict(),
"mqtt": self.mqtt.to_dict(),
"objects": self.objects.to_dict(),
"motion": self.motion.to_dict(),
"detect": self.detect.to_dict(),
"frame_shape": self.frame_shape,
"ffmpeg_cmds": [
{"roles": c["roles"], "cmd": " ".join(c["cmd"])}
for c in self.ffmpeg_cmds
],
2020-11-18 04:11:19 +01:00
}
2021-02-17 14:23:32 +01:00
class FrigateConfig:
2020-11-03 15:15:58 +01:00
def __init__(self, config_file=None, config=None):
if config is None and config_file is None:
2021-02-17 14:23:32 +01:00
raise ValueError("config or config_file must be defined")
2020-11-03 15:15:58 +01:00
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)
2021-02-17 14:23:32 +01:00
self._database = DatabaseConfig(config["database"])
self._model = ModelConfig(config["model"])
self._detectors = {
name: DetectorConfig(d) for name, d in config["detectors"].items()
}
self._mqtt = MqttConfig(config["mqtt"])
self._clips = ClipsConfig(config["clips"])
self._snapshots = SnapshotsConfig(config["snapshots"])
self._cameras = {
name: CameraConfig(name, c, config) for name, c in config["cameras"].items()
}
self._logger = LoggerConfig(config["logger"])
self._environment_vars = config["environment_vars"]
2020-11-03 15:15:58 +01:00
def _sub_env_vars(self, config):
2021-02-17 14:23:32 +01:00
frigate_env_vars = {
k: v for k, v in os.environ.items() if k.startswith("FRIGATE_")
}
2020-11-03 15:15:58 +01:00
2021-02-17 14:23:32 +01:00
if "password" in config["mqtt"]:
config["mqtt"]["password"] = config["mqtt"]["password"].format(
**frigate_env_vars
)
2021-01-09 18:26:46 +01:00
2021-02-17 14:23:32 +01:00
for camera in config["cameras"].values():
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 {
2021-02-17 14:23:32 +01:00
"database": self.database.to_dict(),
"model": self.model.to_dict(),
"detectors": {k: d.to_dict() for k, d in self.detectors.items()},
"mqtt": self.mqtt.to_dict(),
"clips": self.clips.to_dict(),
"snapshots": self.snapshots.to_dict(),
"cameras": {k: c.to_dict() for k, c in self.cameras.items()},
"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