12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259 |
- import base64
- import json
- import logging
- import os
- from typing import Dict
- import cv2
- import matplotlib.pyplot as plt
- import numpy as np
- import voluptuous as vol
- import yaml
- from frigate.const import RECORD_DIR, CLIPS_DIR, CACHE_DIR
- from frigate.util import create_mask
- logger = logging.getLogger(__name__)
- DEFAULT_TRACKED_OBJECTS = ["person"]
- DETECTORS_SCHEMA = vol.Schema(
- {
- vol.Required(str): {
- vol.Required("type", default="edgetpu"): vol.In(["cpu", "edgetpu"]),
- vol.Optional("device", default="usb"): str,
- vol.Optional("num_threads", default=3): int,
- }
- }
- )
- 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,
- "user": str,
- "password": str,
- }
- )
- RETAIN_SCHEMA = vol.Schema(
- {vol.Required("default", default=10): int, "objects": {str: int}}
- )
- CLIPS_SCHEMA = vol.Schema(
- {
- vol.Optional("max_seconds", default=300): int,
- "tmpfs_cache_size": str,
- vol.Optional("retain", default={}): RETAIN_SCHEMA,
- }
- )
- 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"]
- 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",
- ]
- GLOBAL_FFMPEG_SCHEMA = vol.Schema(
- {
- 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]
- ),
- },
- }
- )
- MOTION_SCHEMA = vol.Schema(
- {
- "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})
- FILTER_SCHEMA = vol.Schema(
- {
- str: {
- "min_area": int,
- "max_area": int,
- "threshold": float,
- }
- }
- )
- def filters_for_all_tracked_objects(object_config):
- 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] = {}
- return object_config
- 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]),
- }
- }
- ),
- },
- )
- )
- 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
- CAMERA_FFMPEG_SCHEMA = vol.Schema(
- {
- 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]
- ),
- },
- }
- )
- 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(
- {
- 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",
- ),
- )
- )
- FRIGATE_CONFIG_SCHEMA = vol.Schema(
- {
- vol.Optional("database", default={}): {
- vol.Optional("path", default=os.path.join(CLIPS_DIR, "frigate.db")): str
- },
- vol.Optional("model", default={"width": 320, "height": 320}): {
- vol.Required("width"): int,
- vol.Required("height"): int,
- },
- 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"])
- },
- },
- vol.Optional("snapshots", default={}): {
- vol.Optional("retain", default={}): RETAIN_SCHEMA
- },
- vol.Optional("clips", default={}): CLIPS_SCHEMA,
- vol.Optional("record", default={}): {
- vol.Optional("enabled", default=False): bool,
- vol.Optional("retain_days", default=30): int,
- },
- 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},
- }
- )
- class DatabaseConfig:
- def __init__(self, config):
- self._path = config["path"]
- @property
- def path(self):
- return self._path
- def to_dict(self):
- return {"path": self.path}
- class ModelConfig:
- def __init__(self, config):
- self._width = config["width"]
- self._height = config["height"]
- @property
- def width(self):
- return self._width
- @property
- def height(self):
- return self._height
- def to_dict(self):
- return {"width": self.width, "height": self.height}
- class DetectorConfig:
- def __init__(self, config):
- self._type = config["type"]
- self._device = config["device"]
- self._num_threads = config["num_threads"]
- @property
- def type(self):
- return self._type
- @property
- def device(self):
- return self._device
- @property
- def num_threads(self):
- return self._num_threads
- def to_dict(self):
- return {
- "type": self.type,
- "device": self.device,
- "num_threads": self.num_threads,
- }
- class LoggerConfig:
- def __init__(self, config):
- self._default = config["default"].upper()
- self._logs = {k: v.upper() for k, v in config["logs"].items()}
- @property
- def default(self):
- return self._default
- @property
- def logs(self):
- return self._logs
- def to_dict(self):
- return {"default": self.default, "logs": self.logs}
- 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")
- @property
- def host(self):
- return self._host
- @property
- def port(self):
- return self._port
- @property
- def topic_prefix(self):
- return self._topic_prefix
- @property
- def client_id(self):
- return self._client_id
- @property
- def user(self):
- return self._user
- @property
- def password(self):
- return self._password
- @property
- def stats_interval(self):
- return self._stats_interval
- 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,
- }
- class CameraInput:
- def __init__(self, camera_config, global_config, ffmpeg_input):
- 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"])
- )
- @property
- def path(self):
- return self._path
- @property
- def roles(self):
- return self._roles
- @property
- def global_args(self):
- return (
- self._global_args
- if isinstance(self._global_args, list)
- else self._global_args.split(" ")
- )
- @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(config, global_config, i) for i in config["inputs"]]
- self._output_args = config.get("output_args", global_config["output_args"])
- @property
- def inputs(self):
- return self._inputs
- @property
- def output_args(self):
- return {
- k: v if isinstance(v, list) else v.split(" ")
- for k, v in self._output_args.items()
- }
- class RetainConfig:
- def __init__(self, global_config, config):
- self._default = config.get("default", global_config.get("default"))
- self._objects = config.get("objects", global_config.get("objects", {}))
- @property
- def default(self):
- return self._default
- @property
- def objects(self):
- return self._objects
- def to_dict(self):
- return {"default": self.default, "objects": self.objects}
- class ClipsConfig:
- def __init__(self, config):
- self._max_seconds = config["max_seconds"]
- self._tmpfs_cache_size = config.get("tmpfs_cache_size", "").strip()
- self._retain = RetainConfig(config["retain"], config["retain"])
- @property
- def max_seconds(self):
- return self._max_seconds
- @property
- def tmpfs_cache_size(self):
- return self._tmpfs_cache_size
- @property
- def retain(self):
- return self._retain
- def to_dict(self):
- return {
- "max_seconds": self.max_seconds,
- "tmpfs_cache_size": self.tmpfs_cache_size,
- "retain": self.retain.to_dict(),
- }
- 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()}
- 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
- @property
- def retain_days(self):
- return self._retain_days
- def to_dict(self):
- return {
- "enabled": self.enabled,
- "retain_days": self.retain_days,
- }
- class FilterConfig:
- def __init__(self, global_config, config, global_mask=None, 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))
- 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]
- mask = config.get("mask")
- if mask:
- if isinstance(mask, list):
- self._raw_mask += mask
- elif isinstance(mask, str):
- self._raw_mask += [mask]
- self._mask = (
- create_mask(frame_shape, self._raw_mask) if self._raw_mask else None
- )
- @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
- @property
- def min_score(self):
- return self._min_score
- @property
- def mask(self):
- return self._mask
- def to_dict(self):
- return {
- "min_area": self.min_area,
- "max_area": self.max_area,
- "threshold": self.threshold,
- "min_score": self.min_score,
- "mask": self._raw_mask,
- }
- class ObjectConfig:
- def __init__(self, global_config, config, frame_shape):
- 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
- }
- @property
- def track(self):
- return self._track
- @property
- def filters(self) -> Dict[str, FilterConfig]:
- return self._filters
- def to_dict(self):
- return {
- "track": self.track,
- "mask": self._raw_mask,
- "filters": {k: f.to_dict() for k, f in self.filters.items()},
- }
- class CameraSnapshotsConfig:
- 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")
- self._retain = RetainConfig(
- global_config["snapshots"]["retain"], config["retain"]
- )
- self._required_zones = config["required_zones"]
- @property
- def enabled(self):
- return self._enabled
- @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
- @property
- def retain(self):
- return self._retain
- @property
- def required_zones(self):
- return self._required_zones
- def to_dict(self):
- return {
- "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,
- }
- 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")
- self._required_zones = config["required_zones"]
- @property
- def enabled(self):
- return self._enabled
- @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
- @property
- def required_zones(self):
- return self._required_zones
- def to_dict(self):
- return {
- "enabled": self.enabled,
- "timestamp": self.timestamp,
- "bounding_box": self.bounding_box,
- "crop": self.crop,
- "height": self.height,
- "required_zones": self.required_zones,
- }
- class CameraClipsConfig:
- def __init__(self, global_config, config):
- 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"]
- @property
- def enabled(self):
- return self._enabled
- @property
- def pre_capture(self):
- return self._pre_capture
- @property
- def post_capture(self):
- return self._post_capture
- @property
- def objects(self):
- return self._objects
- @property
- def retain(self):
- return self._retain
- @property
- def required_zones(self):
- return self._required_zones
- def to_dict(self):
- return {
- "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,
- }
- class CameraRtmpConfig:
- def __init__(self, global_config, config):
- self._enabled = config["enabled"]
- @property
- def enabled(self):
- return self._enabled
- def to_dict(self):
- return {
- "enabled": self.enabled,
- }
- class MotionConfig:
- def __init__(self, global_config, config, frame_shape):
- self._raw_mask = config.get("mask")
- 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)
- )
- self._frame_height = config.get(
- "frame_height", global_config.get("frame_height", frame_shape[0] // 6)
- )
- @property
- def mask(self):
- return self._mask
- @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
- def to_dict(self):
- return {
- "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 * 5)
- )
- @property
- def enabled(self):
- return self._enabled
- @property
- def max_disappeared(self):
- return self._max_disappeared
- def to_dict(self):
- return {
- "enabled": self.enabled,
- "max_disappeared": self._max_disappeared,
- }
- class ZoneConfig:
- def __init__(self, name, config):
- self._coordinates = config["coordinates"]
- self._filters = {
- name: FilterConfig(c, c) for name, c in config["filters"].items()
- }
- 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([])
- self._color = (0, 0, 0)
- @property
- def coordinates(self):
- return self._coordinates
- @property
- def contour(self):
- return self._contour
- @contour.setter
- def contour(self, val):
- self._contour = val
- @property
- def color(self):
- return self._color
- @color.setter
- def color(self, val):
- self._color = val
- @property
- def filters(self):
- return self._filters
- def to_dict(self):
- return {
- "filters": {k: f.to_dict() for k, f in self.filters.items()},
- "coordinates": self._coordinates,
- }
- class CameraConfig:
- def __init__(self, name, config, global_config):
- self._name = name
- self._ffmpeg = CameraFfmpegConfig(global_config["ffmpeg"], config["ffmpeg"])
- 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()}
- 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)
- )
- self._ffmpeg_cmds = []
- for ffmpeg_input in self._ffmpeg.inputs:
- ffmpeg_cmd = self._get_ffmpeg_cmd(ffmpeg_input)
- if ffmpeg_cmd is None:
- continue
- self._ffmpeg_cmds.append({"roles": ffmpeg_input.roles, "cmd": ffmpeg_cmd})
- self._set_zone_colors(self._zones)
- def _get_ffmpeg_cmd(self, ffmpeg_input):
- 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"]
- + [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
- )
- # if there arent any outputs enabled for this input
- if len(ffmpeg_output_args) == 0:
- return None
- cmd = (
- ["ffmpeg"]
- + ffmpeg_input.global_args
- + ffmpeg_input.hwaccel_args
- + ffmpeg_input.input_args
- + ["-i", ffmpeg_input.path]
- + ffmpeg_output_args
- )
- return [part for part in cmd if part != ""]
- 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])
- for name, zone in zones.items():
- zone.color = zone_colors[name]
- @property
- def name(self):
- return self._name
- @property
- def ffmpeg(self):
- return self._ffmpeg
- @property
- def height(self):
- return self._height
- @property
- def width(self):
- return self._width
- @property
- def fps(self):
- return self._fps
- @property
- def best_image_timeout(self):
- return self._best_image_timeout
- @property
- def zones(self) -> Dict[str, ZoneConfig]:
- return self._zones
- @property
- def clips(self):
- return self._clips
- @property
- def record(self):
- return self._record
- @property
- def rtmp(self):
- return self._rtmp
- @property
- def snapshots(self):
- return self._snapshots
- @property
- def mqtt(self):
- return self._mqtt
- @property
- def objects(self):
- return self._objects
- @property
- def motion(self):
- return self._motion
- @property
- def detect(self):
- return self._detect
- @property
- def frame_shape(self):
- return self._frame_shape
- @property
- def frame_shape_yuv(self):
- return self._frame_shape_yuv
- @property
- def ffmpeg_cmds(self):
- return self._ffmpeg_cmds
- 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()},
- "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
- ],
- }
- 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)
- config = FRIGATE_CONFIG_SCHEMA(config)
- config = self._sub_env_vars(config)
- 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"]
- 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"]:
- config["mqtt"]["password"] = config["mqtt"]["password"].format(
- **frigate_env_vars
- )
- for camera in config["cameras"].values():
- for i in camera["ffmpeg"]["inputs"]:
- i["path"] = i["path"].format(**frigate_env_vars)
- return config
- def _load_file(self, config_file):
- with open(config_file) as f:
- raw_config = f.read()
- if config_file.endswith(".yml"):
- config = yaml.safe_load(raw_config)
- elif config_file.endswith(".json"):
- config = json.loads(raw_config)
- return config
- def to_dict(self):
- return {
- "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,
- }
- @property
- def database(self):
- return self._database
- @property
- def model(self):
- return self._model
- @property
- def detectors(self) -> Dict[str, DetectorConfig]:
- return self._detectors
- @property
- def logger(self):
- return self._logger
- @property
- def mqtt(self):
- return self._mqtt
- @property
- def clips(self):
- return self._clips
- @property
- def snapshots(self):
- return self._snapshots
- @property
- def cameras(self) -> Dict[str, CameraConfig]:
- return self._cameras
- @property
- def environment_vars(self):
- return self._environment_vars
|