Automatically call FrigateConfig.runtime_config()

runtime_config needed to be called manually before. Now, it's been
removed, but the same code is run by a pydantic validator.
This commit is contained in:
George Tsiamasiotis 2024-09-21 13:24:02 +03:00
parent 2e933d3b34
commit 1959d24908
7 changed files with 163 additions and 270 deletions

View File

@ -361,8 +361,8 @@ def config_set():
json = request.get_json(silent=True) or {} json = request.get_json(silent=True) or {}
if json.get("requires_restart", 1) == 0: if json.get("requires_restart", 1) == 0:
current_app.frigate_config = FrigateConfig.runtime_config( current_app.frigate_config = FrigateConfig.parse_object(
config_obj, current_app.plus_api config_obj, plus_api=current_app.plus_api
) )
return make_response( return make_response(

View File

@ -129,8 +129,7 @@ class FrigateApp:
# check if the config file needs to be migrated # check if the config file needs to be migrated
migrate_frigate_config(config_file) migrate_frigate_config(config_file)
user_config = FrigateConfig.parse_file(config_file) self.config = FrigateConfig.parse_file(config_file, plus_api=self.plus_api)
self.config = user_config.runtime_config(self.plus_api)
for camera_name in self.config.cameras.keys(): for camera_name in self.config.cameras.keys():
# create camera_metrics # create camera_metrics

View File

@ -18,6 +18,7 @@ from pydantic import (
ValidationInfo, ValidationInfo,
field_serializer, field_serializer,
field_validator, field_validator,
model_validator,
) )
from pydantic.fields import PrivateAttr from pydantic.fields import PrivateAttr
from ruamel.yaml import YAML from ruamel.yaml import YAML
@ -44,7 +45,6 @@ from frigate.ffmpeg_presets import (
parse_preset_input, parse_preset_input,
parse_preset_output_record, parse_preset_output_record,
) )
from frigate.plus import PlusApi
from frigate.util.builtin import ( from frigate.util.builtin import (
deep_merge, deep_merge,
escape_special_characters, escape_special_characters,
@ -1494,26 +1494,28 @@ class FrigateConfig(FrigateBaseModel):
) )
version: Optional[str] = Field(default=None, title="Current config version.") version: Optional[str] = Field(default=None, title="Current config version.")
def runtime_config(self, plus_api: PlusApi = None) -> FrigateConfig: @model_validator(mode="after")
"""Merge camera config with globals.""" def post_validation(self, info: ValidationInfo) -> Self:
config = self.model_copy(deep=True) plus_api = None
if isinstance(info.context, dict):
plus_api = info.context.get("plus_api")
# set notifications state # set notifications state
config.notifications.enabled_in_config = config.notifications.enabled self.notifications.enabled_in_config = self.notifications.enabled
# set default min_score for object attributes # set default min_score for object attributes
for attribute in ALL_ATTRIBUTE_LABELS: for attribute in ALL_ATTRIBUTE_LABELS:
if not config.objects.filters.get(attribute): if not self.objects.filters.get(attribute):
config.objects.filters[attribute] = FilterConfig(min_score=0.7) self.objects.filters[attribute] = FilterConfig(min_score=0.7)
elif config.objects.filters[attribute].min_score == 0.5: elif self.objects.filters[attribute].min_score == 0.5:
config.objects.filters[attribute].min_score = 0.7 self.objects.filters[attribute].min_score = 0.7
# auto detect hwaccel args # auto detect hwaccel args
if config.ffmpeg.hwaccel_args == "auto": if self.ffmpeg.hwaccel_args == "auto":
config.ffmpeg.hwaccel_args = auto_detect_hwaccel() self.ffmpeg.hwaccel_args = auto_detect_hwaccel()
# Global config to propagate down to camera level # Global config to propagate down to camera level
global_config = config.model_dump( global_config = self.model_dump(
include={ include={
"audio": ..., "audio": ...,
"birdseye": ..., "birdseye": ...,
@ -1531,7 +1533,7 @@ class FrigateConfig(FrigateBaseModel):
exclude_unset=True, exclude_unset=True,
) )
for name, camera in config.cameras.items(): for name, camera in self.cameras.items():
merged_config = deep_merge( merged_config = deep_merge(
camera.model_dump(exclude_unset=True), global_config camera.model_dump(exclude_unset=True), global_config
) )
@ -1540,7 +1542,7 @@ class FrigateConfig(FrigateBaseModel):
) )
if camera_config.ffmpeg.hwaccel_args == "auto": if camera_config.ffmpeg.hwaccel_args == "auto":
camera_config.ffmpeg.hwaccel_args = config.ffmpeg.hwaccel_args camera_config.ffmpeg.hwaccel_args = self.ffmpeg.hwaccel_args
for input in camera_config.ffmpeg.inputs: for input in camera_config.ffmpeg.inputs:
need_record_fourcc = False and "record" in input.roles need_record_fourcc = False and "record" in input.roles
@ -1553,7 +1555,7 @@ class FrigateConfig(FrigateBaseModel):
stream_info = {"width": 0, "height": 0, "fourcc": None} stream_info = {"width": 0, "height": 0, "fourcc": None}
try: try:
stream_info = stream_info_retriever.get_stream_info( stream_info = stream_info_retriever.get_stream_info(
config.ffmpeg, input.path self.ffmpeg, input.path
) )
except Exception: except Exception:
logger.warn( logger.warn(
@ -1671,8 +1673,12 @@ class FrigateConfig(FrigateBaseModel):
if not camera_config.live.stream_name: if not camera_config.live.stream_name:
camera_config.live.stream_name = name camera_config.live.stream_name = name
# generate the ffmpeg commands
camera_config.create_ffmpeg_cmds()
self.cameras[name] = camera_config
verify_config_roles(camera_config) verify_config_roles(camera_config)
verify_valid_live_stream_name(config, camera_config) verify_valid_live_stream_name(self, camera_config)
verify_recording_retention(camera_config) verify_recording_retention(camera_config)
verify_recording_segments_setup_with_reasonable_time(camera_config) verify_recording_segments_setup_with_reasonable_time(camera_config)
verify_zone_objects_are_tracked(camera_config) verify_zone_objects_are_tracked(camera_config)
@ -1680,20 +1686,16 @@ class FrigateConfig(FrigateBaseModel):
verify_autotrack_zones(camera_config) verify_autotrack_zones(camera_config)
verify_motion_and_detect(camera_config) verify_motion_and_detect(camera_config)
# generate the ffmpeg commands
camera_config.create_ffmpeg_cmds()
config.cameras[name] = camera_config
# get list of unique enabled labels for tracking # get list of unique enabled labels for tracking
enabled_labels = set(config.objects.track) enabled_labels = set(self.objects.track)
for _, camera in config.cameras.items(): for camera in self.cameras.values():
enabled_labels.update(camera.objects.track) enabled_labels.update(camera.objects.track)
config.model.create_colormap(sorted(enabled_labels)) self.model.create_colormap(sorted(enabled_labels))
config.model.check_and_load_plus_model(plus_api) self.model.check_and_load_plus_model(plus_api)
for key, detector in config.detectors.items(): for key, detector in self.detectors.items():
adapter = TypeAdapter(DetectorConfig) adapter = TypeAdapter(DetectorConfig)
model_dict = ( model_dict = (
detector detector
@ -1702,10 +1704,10 @@ class FrigateConfig(FrigateBaseModel):
) )
detector_config: DetectorConfig = adapter.validate_python(model_dict) detector_config: DetectorConfig = adapter.validate_python(model_dict)
if detector_config.model is None: if detector_config.model is None:
detector_config.model = config.model.model_copy() detector_config.model = self.model.model_copy()
else: else:
path = detector_config.model.path path = detector_config.model.path
detector_config.model = config.model.model_copy() detector_config.model = self.model.model_copy()
detector_config.model.path = path detector_config.model.path = path
if "path" not in model_dict or len(model_dict.keys()) > 1: if "path" not in model_dict or len(model_dict.keys()) > 1:
@ -1715,7 +1717,7 @@ class FrigateConfig(FrigateBaseModel):
merged_model = deep_merge( merged_model = deep_merge(
detector_config.model.model_dump(exclude_unset=True, warnings="none"), detector_config.model.model_dump(exclude_unset=True, warnings="none"),
config.model.model_dump(exclude_unset=True, warnings="none"), self.model.model_dump(exclude_unset=True, warnings="none"),
) )
if "path" not in merged_model: if "path" not in merged_model:
@ -1729,9 +1731,9 @@ class FrigateConfig(FrigateBaseModel):
plus_api, detector_config.type plus_api, detector_config.type
) )
detector_config.model.compute_model_hash() detector_config.model.compute_model_hash()
config.detectors[key] = detector_config self.detectors[key] = detector_config
return config return self
@field_validator("cameras") @field_validator("cameras")
@classmethod @classmethod
@ -1743,12 +1745,12 @@ class FrigateConfig(FrigateBaseModel):
return v return v
@classmethod @classmethod
def parse_file(cls, config_path, *, is_json=None) -> Self: def parse_file(cls, config_path, **kwargs) -> Self:
with open(config_path) as f: with open(config_path) as f:
return FrigateConfig.parse(f, is_json=is_json) return FrigateConfig.parse(f, **kwargs)
@classmethod @classmethod
def parse(cls, config, *, is_json=None) -> Self: def parse(cls, config, *, is_json=None, **context) -> Self:
# If config is a file, read its contents. # If config is a file, read its contents.
if hasattr(config, "read"): if hasattr(config, "read"):
fname = getattr(config, "name", None) fname = getattr(config, "name", None)
@ -1773,8 +1775,12 @@ class FrigateConfig(FrigateBaseModel):
config = yaml.load(config) config = yaml.load(config)
# Validate and return the config dict. # Validate and return the config dict.
return cls.model_validate(config) return cls.parse_object(config, **context)
@classmethod @classmethod
def parse_yaml(cls, config_yaml) -> Self: def parse_object(cls, obj: Any, **context):
return cls.parse(config_yaml, is_json=False) return cls.model_validate(obj, context=context)
@classmethod
def parse_yaml(cls, config_yaml, **context) -> Self:
return cls.parse(config_yaml, is_json=False, **context)

View File

@ -10,7 +10,6 @@ from ruamel.yaml.constructor import DuplicateKeyError
from frigate.config import BirdseyeModeEnum, FrigateConfig from frigate.config import BirdseyeModeEnum, FrigateConfig
from frigate.const import MODEL_CACHE_DIR from frigate.const import MODEL_CACHE_DIR
from frigate.detectors import DetectorTypeEnum from frigate.detectors import DetectorTypeEnum
from frigate.plus import PlusApi
from frigate.util.builtin import deep_merge from frigate.util.builtin import deep_merge
@ -65,12 +64,9 @@ class TestConfig(unittest.TestCase):
def test_config_class(self): def test_config_class(self):
frigate_config = FrigateConfig(**self.minimal) frigate_config = FrigateConfig(**self.minimal)
assert self.minimal == frigate_config.model_dump(exclude_unset=True) assert "cpu" in frigate_config.detectors.keys()
assert frigate_config.detectors["cpu"].type == DetectorTypeEnum.cpu
runtime_config = frigate_config.runtime_config() assert frigate_config.detectors["cpu"].model.width == 320
assert "cpu" in runtime_config.detectors.keys()
assert runtime_config.detectors["cpu"].type == DetectorTypeEnum.cpu
assert runtime_config.detectors["cpu"].model.width == 320
@patch("frigate.detectors.detector_config.load_labels") @patch("frigate.detectors.detector_config.load_labels")
def test_detector_custom_model_path(self, mock_labels): def test_detector_custom_model_path(self, mock_labels):
@ -94,24 +90,23 @@ class TestConfig(unittest.TestCase):
} }
frigate_config = FrigateConfig(**(deep_merge(config, self.minimal))) frigate_config = FrigateConfig(**(deep_merge(config, self.minimal)))
runtime_config = frigate_config.runtime_config()
assert "cpu" in runtime_config.detectors.keys() assert "cpu" in frigate_config.detectors.keys()
assert "edgetpu" in runtime_config.detectors.keys() assert "edgetpu" in frigate_config.detectors.keys()
assert "openvino" in runtime_config.detectors.keys() assert "openvino" in frigate_config.detectors.keys()
assert runtime_config.detectors["cpu"].type == DetectorTypeEnum.cpu assert frigate_config.detectors["cpu"].type == DetectorTypeEnum.cpu
assert runtime_config.detectors["edgetpu"].type == DetectorTypeEnum.edgetpu assert frigate_config.detectors["edgetpu"].type == DetectorTypeEnum.edgetpu
assert runtime_config.detectors["openvino"].type == DetectorTypeEnum.openvino assert frigate_config.detectors["openvino"].type == DetectorTypeEnum.openvino
assert runtime_config.detectors["cpu"].num_threads == 3 assert frigate_config.detectors["cpu"].num_threads == 3
assert runtime_config.detectors["edgetpu"].device is None assert frigate_config.detectors["edgetpu"].device is None
assert runtime_config.detectors["openvino"].device is None assert frigate_config.detectors["openvino"].device is None
assert runtime_config.model.path == "/etc/hosts" assert frigate_config.model.path == "/etc/hosts"
assert runtime_config.detectors["cpu"].model.path == "/cpu_model.tflite" assert frigate_config.detectors["cpu"].model.path == "/cpu_model.tflite"
assert runtime_config.detectors["edgetpu"].model.path == "/edgetpu_model.tflite" assert frigate_config.detectors["edgetpu"].model.path == "/edgetpu_model.tflite"
assert runtime_config.detectors["openvino"].model.path == "/etc/hosts" assert frigate_config.detectors["openvino"].model.path == "/etc/hosts"
def test_invalid_mqtt_config(self): def test_invalid_mqtt_config(self):
config = { config = {
@ -152,11 +147,9 @@ class TestConfig(unittest.TestCase):
} }
}, },
} }
frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True)
runtime_config = frigate_config.runtime_config() frigate_config = FrigateConfig(**config)
assert "dog" in runtime_config.cameras["back"].objects.track assert "dog" in frigate_config.cameras["back"].objects.track
def test_override_birdseye(self): def test_override_birdseye(self):
config = { config = {
@ -178,12 +171,10 @@ class TestConfig(unittest.TestCase):
} }
}, },
} }
frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True)
runtime_config = frigate_config.runtime_config() frigate_config = FrigateConfig(**config)
assert not runtime_config.cameras["back"].birdseye.enabled assert not frigate_config.cameras["back"].birdseye.enabled
assert runtime_config.cameras["back"].birdseye.mode is BirdseyeModeEnum.motion assert frigate_config.cameras["back"].birdseye.mode is BirdseyeModeEnum.motion
def test_override_birdseye_non_inheritable(self): def test_override_birdseye_non_inheritable(self):
config = { config = {
@ -204,11 +195,9 @@ class TestConfig(unittest.TestCase):
} }
}, },
} }
frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True)
runtime_config = frigate_config.runtime_config() frigate_config = FrigateConfig(**config)
assert runtime_config.cameras["back"].birdseye.enabled assert frigate_config.cameras["back"].birdseye.enabled
def test_inherit_birdseye(self): def test_inherit_birdseye(self):
config = { config = {
@ -229,13 +218,11 @@ class TestConfig(unittest.TestCase):
} }
}, },
} }
frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True)
runtime_config = frigate_config.runtime_config() frigate_config = FrigateConfig(**config)
assert runtime_config.cameras["back"].birdseye.enabled assert frigate_config.cameras["back"].birdseye.enabled
assert ( assert (
runtime_config.cameras["back"].birdseye.mode is BirdseyeModeEnum.continuous frigate_config.cameras["back"].birdseye.mode is BirdseyeModeEnum.continuous
) )
def test_override_tracked_objects(self): def test_override_tracked_objects(self):
@ -258,11 +245,9 @@ class TestConfig(unittest.TestCase):
} }
}, },
} }
frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True)
runtime_config = frigate_config.runtime_config() frigate_config = FrigateConfig(**config)
assert "cat" in runtime_config.cameras["back"].objects.track assert "cat" in frigate_config.cameras["back"].objects.track
def test_default_object_filters(self): def test_default_object_filters(self):
config = { config = {
@ -283,11 +268,9 @@ class TestConfig(unittest.TestCase):
} }
}, },
} }
frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True)
runtime_config = frigate_config.runtime_config() frigate_config = FrigateConfig(**config)
assert "dog" in runtime_config.cameras["back"].objects.filters assert "dog" in frigate_config.cameras["back"].objects.filters
def test_inherit_object_filters(self): def test_inherit_object_filters(self):
config = { config = {
@ -311,12 +294,10 @@ class TestConfig(unittest.TestCase):
} }
}, },
} }
frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True)
runtime_config = frigate_config.runtime_config() frigate_config = FrigateConfig(**config)
assert "dog" in runtime_config.cameras["back"].objects.filters assert "dog" in frigate_config.cameras["back"].objects.filters
assert runtime_config.cameras["back"].objects.filters["dog"].threshold == 0.7 assert frigate_config.cameras["back"].objects.filters["dog"].threshold == 0.7
def test_override_object_filters(self): def test_override_object_filters(self):
config = { config = {
@ -340,12 +321,10 @@ class TestConfig(unittest.TestCase):
} }
}, },
} }
frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True)
runtime_config = frigate_config.runtime_config() frigate_config = FrigateConfig(**config)
assert "dog" in runtime_config.cameras["back"].objects.filters assert "dog" in frigate_config.cameras["back"].objects.filters
assert runtime_config.cameras["back"].objects.filters["dog"].threshold == 0.7 assert frigate_config.cameras["back"].objects.filters["dog"].threshold == 0.7
def test_global_object_mask(self): def test_global_object_mask(self):
config = { config = {
@ -370,11 +349,9 @@ class TestConfig(unittest.TestCase):
} }
}, },
} }
frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True)
runtime_config = frigate_config.runtime_config() frigate_config = FrigateConfig(**config)
back_camera = runtime_config.cameras["back"] back_camera = frigate_config.cameras["back"]
assert "dog" in back_camera.objects.filters assert "dog" in back_camera.objects.filters
assert len(back_camera.objects.filters["dog"].raw_mask) == 2 assert len(back_camera.objects.filters["dog"].raw_mask) == 2
assert len(back_camera.objects.filters["person"].raw_mask) == 1 assert len(back_camera.objects.filters["person"].raw_mask) == 1
@ -420,7 +397,8 @@ class TestConfig(unittest.TestCase):
}, },
}, },
} }
frigate_config = FrigateConfig(**config).runtime_config()
frigate_config = FrigateConfig(**config)
assert np.array_equal( assert np.array_equal(
frigate_config.cameras["explicit"].motion.mask, frigate_config.cameras["explicit"].motion.mask,
frigate_config.cameras["relative"].motion.mask, frigate_config.cameras["relative"].motion.mask,
@ -449,10 +427,7 @@ class TestConfig(unittest.TestCase):
} }
frigate_config = FrigateConfig(**config) frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True) assert "-rtsp_transport" in frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]
runtime_config = frigate_config.runtime_config()
assert "-rtsp_transport" in runtime_config.cameras["back"].ffmpeg_cmds[0]["cmd"]
def test_ffmpeg_params_global(self): def test_ffmpeg_params_global(self):
config = { config = {
@ -477,11 +452,9 @@ class TestConfig(unittest.TestCase):
} }
}, },
} }
frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True)
runtime_config = frigate_config.runtime_config() frigate_config = FrigateConfig(**config)
assert "-re" in runtime_config.cameras["back"].ffmpeg_cmds[0]["cmd"] assert "-re" in frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]
def test_ffmpeg_params_camera(self): def test_ffmpeg_params_camera(self):
config = { config = {
@ -507,12 +480,10 @@ class TestConfig(unittest.TestCase):
} }
}, },
} }
frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True)
runtime_config = frigate_config.runtime_config() frigate_config = FrigateConfig(**config)
assert "-re" in runtime_config.cameras["back"].ffmpeg_cmds[0]["cmd"] assert "-re" in frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]
assert "test" not in runtime_config.cameras["back"].ffmpeg_cmds[0]["cmd"] assert "test" not in frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]
def test_ffmpeg_params_input(self): def test_ffmpeg_params_input(self):
config = { config = {
@ -542,14 +513,12 @@ class TestConfig(unittest.TestCase):
} }
}, },
} }
frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True)
runtime_config = frigate_config.runtime_config() frigate_config = FrigateConfig(**config)
assert "-re" in runtime_config.cameras["back"].ffmpeg_cmds[0]["cmd"] assert "-re" in frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]
assert "test" in runtime_config.cameras["back"].ffmpeg_cmds[0]["cmd"] assert "test" in frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]
assert "test2" not in runtime_config.cameras["back"].ffmpeg_cmds[0]["cmd"] assert "test2" not in frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]
assert "test3" not in runtime_config.cameras["back"].ffmpeg_cmds[0]["cmd"] assert "test3" not in frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]
def test_inherit_clips_retention(self): def test_inherit_clips_retention(self):
config = { config = {
@ -570,11 +539,9 @@ class TestConfig(unittest.TestCase):
} }
}, },
} }
frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True)
runtime_config = frigate_config.runtime_config() frigate_config = FrigateConfig(**config)
assert runtime_config.cameras["back"].record.alerts.retain.days == 20 assert frigate_config.cameras["back"].record.alerts.retain.days == 20
def test_roles_listed_twice_throws_error(self): def test_roles_listed_twice_throws_error(self):
config = { config = {
@ -658,14 +625,12 @@ class TestConfig(unittest.TestCase):
} }
}, },
} }
frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True)
runtime_config = frigate_config.runtime_config() frigate_config = FrigateConfig(**config)
assert isinstance( assert isinstance(
runtime_config.cameras["back"].zones["test"].contour, np.ndarray frigate_config.cameras["back"].zones["test"].contour, np.ndarray
) )
assert runtime_config.cameras["back"].zones["test"].color != (0, 0, 0) assert frigate_config.cameras["back"].zones["test"].color != (0, 0, 0)
def test_zone_relative_matches_explicit(self): def test_zone_relative_matches_explicit(self):
config = { config = {
@ -700,7 +665,8 @@ class TestConfig(unittest.TestCase):
} }
}, },
} }
frigate_config = FrigateConfig(**config).runtime_config()
frigate_config = FrigateConfig(**config)
assert np.array_equal( assert np.array_equal(
frigate_config.cameras["back"].zones["explicit"].contour, frigate_config.cameras["back"].zones["explicit"].contour,
frigate_config.cameras["back"].zones["relative"].contour, frigate_config.cameras["back"].zones["relative"].contour,
@ -730,10 +696,7 @@ class TestConfig(unittest.TestCase):
} }
frigate_config = FrigateConfig(**config) frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True) ffmpeg_cmds = frigate_config.cameras["back"].ffmpeg_cmds
runtime_config = frigate_config.runtime_config()
ffmpeg_cmds = runtime_config.cameras["back"].ffmpeg_cmds
assert len(ffmpeg_cmds) == 1 assert len(ffmpeg_cmds) == 1
assert "clips" not in ffmpeg_cmds[0]["roles"] assert "clips" not in ffmpeg_cmds[0]["roles"]
@ -761,10 +724,7 @@ class TestConfig(unittest.TestCase):
} }
frigate_config = FrigateConfig(**config) frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True) assert frigate_config.cameras["back"].detect.max_disappeared == 5 * 5
runtime_config = frigate_config.runtime_config()
assert runtime_config.cameras["back"].detect.max_disappeared == 5 * 5
def test_motion_frame_height_wont_go_below_120(self): def test_motion_frame_height_wont_go_below_120(self):
config = { config = {
@ -789,10 +749,7 @@ class TestConfig(unittest.TestCase):
} }
frigate_config = FrigateConfig(**config) frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True) assert frigate_config.cameras["back"].motion.frame_height == 100
runtime_config = frigate_config.runtime_config()
assert runtime_config.cameras["back"].motion.frame_height == 100
def test_motion_contour_area_dynamic(self): def test_motion_contour_area_dynamic(self):
config = { config = {
@ -817,10 +774,7 @@ class TestConfig(unittest.TestCase):
} }
frigate_config = FrigateConfig(**config) frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True) assert round(frigate_config.cameras["back"].motion.contour_area) == 10
runtime_config = frigate_config.runtime_config()
assert round(runtime_config.cameras["back"].motion.contour_area) == 10
def test_merge_labelmap(self): def test_merge_labelmap(self):
config = { config = {
@ -846,10 +800,7 @@ class TestConfig(unittest.TestCase):
} }
frigate_config = FrigateConfig(**config) frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True) assert frigate_config.model.merged_labelmap[7] == "truck"
runtime_config = frigate_config.runtime_config()
assert runtime_config.model.merged_labelmap[7] == "truck"
def test_default_labelmap_empty(self): def test_default_labelmap_empty(self):
config = { config = {
@ -874,10 +825,7 @@ class TestConfig(unittest.TestCase):
} }
frigate_config = FrigateConfig(**config) frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True) assert frigate_config.model.merged_labelmap[0] == "person"
runtime_config = frigate_config.runtime_config()
assert runtime_config.model.merged_labelmap[0] == "person"
def test_default_labelmap(self): def test_default_labelmap(self):
config = { config = {
@ -903,10 +851,7 @@ class TestConfig(unittest.TestCase):
} }
frigate_config = FrigateConfig(**config) frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True) assert frigate_config.model.merged_labelmap[0] == "person"
runtime_config = frigate_config.runtime_config()
assert runtime_config.model.merged_labelmap[0] == "person"
def test_plus_labelmap(self): def test_plus_labelmap(self):
with open("/config/model_cache/test", "w") as f: with open("/config/model_cache/test", "w") as f:
@ -937,10 +882,7 @@ class TestConfig(unittest.TestCase):
} }
frigate_config = FrigateConfig(**config) frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True) assert frigate_config.model.merged_labelmap[0] == "amazon"
runtime_config = frigate_config.runtime_config(PlusApi())
assert runtime_config.model.merged_labelmap[0] == "amazon"
def test_fails_on_invalid_role(self): def test_fails_on_invalid_role(self):
config = { config = {
@ -997,8 +939,7 @@ class TestConfig(unittest.TestCase):
}, },
} }
frigate_config = FrigateConfig(**config) self.assertRaises(ValueError, lambda: FrigateConfig(**config))
self.assertRaises(ValueError, lambda: frigate_config.runtime_config())
def test_works_on_missing_role_multiple_cams(self): def test_works_on_missing_role_multiple_cams(self):
config = { config = {
@ -1045,8 +986,7 @@ class TestConfig(unittest.TestCase):
}, },
} }
frigate_config = FrigateConfig(**config) FrigateConfig(**config)
frigate_config.runtime_config()
def test_global_detect(self): def test_global_detect(self):
config = { config = {
@ -1070,12 +1010,10 @@ class TestConfig(unittest.TestCase):
} }
}, },
} }
frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True)
runtime_config = frigate_config.runtime_config() frigate_config = FrigateConfig(**config)
assert runtime_config.cameras["back"].detect.max_disappeared == 1 assert frigate_config.cameras["back"].detect.max_disappeared == 1
assert runtime_config.cameras["back"].detect.height == 1080 assert frigate_config.cameras["back"].detect.height == 1080
def test_default_detect(self): def test_default_detect(self):
config = { config = {
@ -1098,12 +1036,10 @@ class TestConfig(unittest.TestCase):
} }
}, },
} }
frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True)
runtime_config = frigate_config.runtime_config() frigate_config = FrigateConfig(**config)
assert runtime_config.cameras["back"].detect.max_disappeared == 25 assert frigate_config.cameras["back"].detect.max_disappeared == 25
assert runtime_config.cameras["back"].detect.height == 720 assert frigate_config.cameras["back"].detect.height == 720
def test_global_detect_merge(self): def test_global_detect_merge(self):
config = { config = {
@ -1127,13 +1063,11 @@ class TestConfig(unittest.TestCase):
} }
}, },
} }
frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True)
runtime_config = frigate_config.runtime_config() frigate_config = FrigateConfig(**config)
assert runtime_config.cameras["back"].detect.max_disappeared == 1 assert frigate_config.cameras["back"].detect.max_disappeared == 1
assert runtime_config.cameras["back"].detect.height == 1080 assert frigate_config.cameras["back"].detect.height == 1080
assert runtime_config.cameras["back"].detect.width == 1920 assert frigate_config.cameras["back"].detect.width == 1920
def test_global_snapshots(self): def test_global_snapshots(self):
config = { config = {
@ -1160,12 +1094,10 @@ class TestConfig(unittest.TestCase):
} }
}, },
} }
frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True)
runtime_config = frigate_config.runtime_config() frigate_config = FrigateConfig(**config)
assert runtime_config.cameras["back"].snapshots.enabled assert frigate_config.cameras["back"].snapshots.enabled
assert runtime_config.cameras["back"].snapshots.height == 100 assert frigate_config.cameras["back"].snapshots.height == 100
def test_default_snapshots(self): def test_default_snapshots(self):
config = { config = {
@ -1188,12 +1120,10 @@ class TestConfig(unittest.TestCase):
} }
}, },
} }
frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True)
runtime_config = frigate_config.runtime_config() frigate_config = FrigateConfig(**config)
assert runtime_config.cameras["back"].snapshots.bounding_box assert frigate_config.cameras["back"].snapshots.bounding_box
assert runtime_config.cameras["back"].snapshots.quality == 70 assert frigate_config.cameras["back"].snapshots.quality == 70
def test_global_snapshots_merge(self): def test_global_snapshots_merge(self):
config = { config = {
@ -1221,13 +1151,11 @@ class TestConfig(unittest.TestCase):
} }
}, },
} }
frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True)
runtime_config = frigate_config.runtime_config() frigate_config = FrigateConfig(**config)
assert runtime_config.cameras["back"].snapshots.bounding_box is False assert frigate_config.cameras["back"].snapshots.bounding_box is False
assert runtime_config.cameras["back"].snapshots.height == 150 assert frigate_config.cameras["back"].snapshots.height == 150
assert runtime_config.cameras["back"].snapshots.enabled assert frigate_config.cameras["back"].snapshots.enabled
def test_global_jsmpeg(self): def test_global_jsmpeg(self):
config = { config = {
@ -1251,11 +1179,9 @@ class TestConfig(unittest.TestCase):
} }
}, },
} }
frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True)
runtime_config = frigate_config.runtime_config() frigate_config = FrigateConfig(**config)
assert runtime_config.cameras["back"].live.quality == 4 assert frigate_config.cameras["back"].live.quality == 4
def test_default_live(self): def test_default_live(self):
config = { config = {
@ -1278,11 +1204,9 @@ class TestConfig(unittest.TestCase):
} }
}, },
} }
frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True)
runtime_config = frigate_config.runtime_config() frigate_config = FrigateConfig(**config)
assert runtime_config.cameras["back"].live.quality == 8 assert frigate_config.cameras["back"].live.quality == 8
def test_global_live_merge(self): def test_global_live_merge(self):
config = { config = {
@ -1309,12 +1233,10 @@ class TestConfig(unittest.TestCase):
} }
}, },
} }
frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True)
runtime_config = frigate_config.runtime_config() frigate_config = FrigateConfig(**config)
assert runtime_config.cameras["back"].live.quality == 7 assert frigate_config.cameras["back"].live.quality == 7
assert runtime_config.cameras["back"].live.height == 480 assert frigate_config.cameras["back"].live.height == 480
def test_global_timestamp_style(self): def test_global_timestamp_style(self):
config = { config = {
@ -1338,11 +1260,9 @@ class TestConfig(unittest.TestCase):
} }
}, },
} }
frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True)
runtime_config = frigate_config.runtime_config() frigate_config = FrigateConfig(**config)
assert runtime_config.cameras["back"].timestamp_style.position == "bl" assert frigate_config.cameras["back"].timestamp_style.position == "bl"
def test_default_timestamp_style(self): def test_default_timestamp_style(self):
config = { config = {
@ -1365,11 +1285,9 @@ class TestConfig(unittest.TestCase):
} }
}, },
} }
frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True)
runtime_config = frigate_config.runtime_config() frigate_config = FrigateConfig(**config)
assert runtime_config.cameras["back"].timestamp_style.position == "tl" assert frigate_config.cameras["back"].timestamp_style.position == "tl"
def test_global_timestamp_style_merge(self): def test_global_timestamp_style_merge(self):
config = { config = {
@ -1394,12 +1312,10 @@ class TestConfig(unittest.TestCase):
} }
}, },
} }
frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True)
runtime_config = frigate_config.runtime_config() frigate_config = FrigateConfig(**config)
assert runtime_config.cameras["back"].timestamp_style.position == "bl" assert frigate_config.cameras["back"].timestamp_style.position == "bl"
assert runtime_config.cameras["back"].timestamp_style.thickness == 4 assert frigate_config.cameras["back"].timestamp_style.thickness == 4
def test_allow_retain_to_be_a_decimal(self): def test_allow_retain_to_be_a_decimal(self):
config = { config = {
@ -1423,11 +1339,9 @@ class TestConfig(unittest.TestCase):
} }
}, },
} }
frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True)
runtime_config = frigate_config.runtime_config() frigate_config = FrigateConfig(**config)
assert runtime_config.cameras["back"].snapshots.retain.default == 1.5 assert frigate_config.cameras["back"].snapshots.retain.default == 1.5
def test_fails_on_bad_camera_name(self): def test_fails_on_bad_camera_name(self):
config = { config = {
@ -1452,11 +1366,7 @@ class TestConfig(unittest.TestCase):
}, },
} }
frigate_config = FrigateConfig(**config) self.assertRaises(ValidationError, lambda: FrigateConfig(**config).cameras)
self.assertRaises(
ValidationError, lambda: frigate_config.runtime_config().cameras
)
def test_fails_on_bad_segment_time(self): def test_fails_on_bad_segment_time(self):
config = { config = {
@ -1484,11 +1394,9 @@ class TestConfig(unittest.TestCase):
}, },
} }
frigate_config = FrigateConfig(**config)
self.assertRaises( self.assertRaises(
ValueError, ValueError,
lambda: frigate_config.runtime_config().ffmpeg.output_args.record, lambda: FrigateConfig(**config).ffmpeg.output_args.record,
) )
def test_fails_zone_defines_untracked_object(self): def test_fails_zone_defines_untracked_object(self):
@ -1520,9 +1428,7 @@ class TestConfig(unittest.TestCase):
}, },
} }
frigate_config = FrigateConfig(**config) self.assertRaises(ValueError, lambda: FrigateConfig(**config).cameras)
self.assertRaises(ValueError, lambda: frigate_config.runtime_config().cameras)
def test_fails_duplicate_keys(self): def test_fails_duplicate_keys(self):
raw_config = """ raw_config = """
@ -1563,13 +1469,11 @@ class TestConfig(unittest.TestCase):
} }
}, },
} }
frigate_config = FrigateConfig(**config)
assert config == frigate_config.model_dump(exclude_unset=True)
runtime_config = frigate_config.runtime_config() frigate_config = FrigateConfig(**config)
assert "dog" in runtime_config.cameras["back"].objects.filters assert "dog" in frigate_config.cameras["back"].objects.filters
assert runtime_config.cameras["back"].objects.filters["dog"].min_ratio == 0.2 assert frigate_config.cameras["back"].objects.filters["dog"].min_ratio == 0.2
assert runtime_config.cameras["back"].objects.filters["dog"].max_ratio == 10.1 assert frigate_config.cameras["back"].objects.filters["dog"].max_ratio == 10.1
def test_valid_movement_weights(self): def test_valid_movement_weights(self):
config = { config = {
@ -1592,10 +1496,9 @@ class TestConfig(unittest.TestCase):
} }
}, },
} }
frigate_config = FrigateConfig(**config)
runtime_config = frigate_config.runtime_config() frigate_config = FrigateConfig(**config)
assert runtime_config.cameras["back"].onvif.autotracking.movement_weights == [ assert frigate_config.cameras["back"].onvif.autotracking.movement_weights == [
"0.0", "0.0",
"1.0", "1.0",
"1.23", "1.23",

View File

@ -36,16 +36,13 @@ class TestFfmpegPresets(unittest.TestCase):
} }
def test_default_ffmpeg(self): def test_default_ffmpeg(self):
frigate_config = FrigateConfig(**self.default_ffmpeg) FrigateConfig(**self.default_ffmpeg)
frigate_config.cameras["back"].create_ffmpeg_cmds()
assert self.default_ffmpeg == frigate_config.dict(exclude_unset=True)
def test_ffmpeg_hwaccel_preset(self): def test_ffmpeg_hwaccel_preset(self):
self.default_ffmpeg["cameras"]["back"]["ffmpeg"]["hwaccel_args"] = ( self.default_ffmpeg["cameras"]["back"]["ffmpeg"]["hwaccel_args"] = (
"preset-rpi-64-h264" "preset-rpi-64-h264"
) )
frigate_config = FrigateConfig(**self.default_ffmpeg) frigate_config = FrigateConfig(**self.default_ffmpeg)
frigate_config.cameras["back"].create_ffmpeg_cmds()
assert "preset-rpi-64-h264" not in ( assert "preset-rpi-64-h264" not in (
" ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]) " ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"])
) )
@ -58,7 +55,6 @@ class TestFfmpegPresets(unittest.TestCase):
"-other-hwaccel args" "-other-hwaccel args"
) )
frigate_config = FrigateConfig(**self.default_ffmpeg) frigate_config = FrigateConfig(**self.default_ffmpeg)
frigate_config.cameras["back"].create_ffmpeg_cmds()
assert "-other-hwaccel args" in ( assert "-other-hwaccel args" in (
" ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]) " ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"])
) )
@ -73,7 +69,6 @@ class TestFfmpegPresets(unittest.TestCase):
"fps": 10, "fps": 10,
} }
frigate_config = FrigateConfig(**self.default_ffmpeg) frigate_config = FrigateConfig(**self.default_ffmpeg)
frigate_config.cameras["back"].create_ffmpeg_cmds()
assert "preset-nvidia-h264" not in ( assert "preset-nvidia-h264" not in (
" ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]) " ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"])
) )
@ -89,8 +84,6 @@ class TestFfmpegPresets(unittest.TestCase):
"preset-rtsp-generic" "preset-rtsp-generic"
) )
frigate_preset_config = FrigateConfig(**self.default_ffmpeg) frigate_preset_config = FrigateConfig(**self.default_ffmpeg)
frigate_config.cameras["back"].create_ffmpeg_cmds()
frigate_preset_config.cameras["back"].create_ffmpeg_cmds()
assert ( assert (
# Ignore global and user_agent args in comparison # Ignore global and user_agent args in comparison
frigate_preset_config.cameras["back"].ffmpeg_cmds[0]["cmd"] frigate_preset_config.cameras["back"].ffmpeg_cmds[0]["cmd"]
@ -102,7 +95,6 @@ class TestFfmpegPresets(unittest.TestCase):
"preset-rtmp-generic" "preset-rtmp-generic"
) )
frigate_config = FrigateConfig(**self.default_ffmpeg) frigate_config = FrigateConfig(**self.default_ffmpeg)
frigate_config.cameras["back"].create_ffmpeg_cmds()
assert "preset-rtmp-generic" not in ( assert "preset-rtmp-generic" not in (
" ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]) " ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"])
) )
@ -117,7 +109,6 @@ class TestFfmpegPresets(unittest.TestCase):
argsList = defaultArgsList + ["-some", "arg with space"] argsList = defaultArgsList + ["-some", "arg with space"]
self.default_ffmpeg["cameras"]["back"]["ffmpeg"]["input_args"] = argsString self.default_ffmpeg["cameras"]["back"]["ffmpeg"]["input_args"] = argsString
frigate_config = FrigateConfig(**self.default_ffmpeg) frigate_config = FrigateConfig(**self.default_ffmpeg)
frigate_config.cameras["back"].create_ffmpeg_cmds()
assert set(argsList).issubset( assert set(argsList).issubset(
frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"] frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]
) )
@ -125,7 +116,6 @@ class TestFfmpegPresets(unittest.TestCase):
def test_ffmpeg_input_not_preset(self): def test_ffmpeg_input_not_preset(self):
self.default_ffmpeg["cameras"]["back"]["ffmpeg"]["input_args"] = "-some inputs" self.default_ffmpeg["cameras"]["back"]["ffmpeg"]["input_args"] = "-some inputs"
frigate_config = FrigateConfig(**self.default_ffmpeg) frigate_config = FrigateConfig(**self.default_ffmpeg)
frigate_config.cameras["back"].create_ffmpeg_cmds()
assert "-some inputs" in ( assert "-some inputs" in (
" ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]) " ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"])
) )
@ -135,7 +125,6 @@ class TestFfmpegPresets(unittest.TestCase):
"preset-record-generic-audio-aac" "preset-record-generic-audio-aac"
) )
frigate_config = FrigateConfig(**self.default_ffmpeg) frigate_config = FrigateConfig(**self.default_ffmpeg)
frigate_config.cameras["back"].create_ffmpeg_cmds()
assert "preset-record-generic-audio-aac" not in ( assert "preset-record-generic-audio-aac" not in (
" ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]) " ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"])
) )
@ -148,7 +137,6 @@ class TestFfmpegPresets(unittest.TestCase):
"-some output" "-some output"
) )
frigate_config = FrigateConfig(**self.default_ffmpeg) frigate_config = FrigateConfig(**self.default_ffmpeg)
frigate_config.cameras["back"].create_ffmpeg_cmds()
assert "-some output" in ( assert "-some output" in (
" ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]) " ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"])
) )

View File

@ -345,7 +345,7 @@ class TestHttp(unittest.TestCase):
def test_config(self): def test_config(self):
app = create_app( app = create_app(
FrigateConfig(**self.minimal_config).runtime_config(), FrigateConfig(**self.minimal_config),
self.db, self.db,
None, None,
None, None,
@ -363,7 +363,7 @@ class TestHttp(unittest.TestCase):
def test_recordings(self): def test_recordings(self):
app = create_app( app = create_app(
FrigateConfig(**self.minimal_config).runtime_config(), FrigateConfig(**self.minimal_config),
self.db, self.db,
None, None,
None, None,
@ -385,7 +385,7 @@ class TestHttp(unittest.TestCase):
stats = Mock(spec=StatsEmitter) stats = Mock(spec=StatsEmitter)
stats.get_latest_stats.return_value = self.test_stats stats.get_latest_stats.return_value = self.test_stats
app = create_app( app = create_app(
FrigateConfig(**self.minimal_config).runtime_config(), FrigateConfig(**self.minimal_config),
self.db, self.db,
None, None,
None, None,

View File

@ -280,10 +280,7 @@ def process(path, label, output, debug_path):
json_config["cameras"]["camera"]["ffmpeg"]["inputs"][0]["path"] = c json_config["cameras"]["camera"]["ffmpeg"]["inputs"][0]["path"] = c
frigate_config = FrigateConfig(**json_config) frigate_config = FrigateConfig(**json_config)
runtime_config = frigate_config.runtime_config() process_clip = ProcessClip(c, frame_shape, frigate_config)
runtime_config.cameras["camera"].create_ffmpeg_cmds()
process_clip = ProcessClip(c, frame_shape, runtime_config)
process_clip.load_frames() process_clip.load_frames()
process_clip.process_frames(object_detector, objects_to_track=[label]) process_clip.process_frames(object_detector, objects_to_track=[label])