diff --git a/frigate/config.py b/frigate/config.py index 8c9052948..8d54a441d 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -9,7 +9,7 @@ from typing import Dict, List, Optional, Tuple, Union import matplotlib.pyplot as plt import numpy as np import yaml -from pydantic import BaseModel, Field, validator +from pydantic import BaseModel, Extra, Field, validator from pydantic.fields import PrivateAttr from frigate.const import BASE_DIR, CACHE_DIR, RECORD_DIR @@ -29,18 +29,23 @@ DEFAULT_TRACKED_OBJECTS = ["person"] DEFAULT_DETECTORS = {"cpu": {"type": "cpu"}} +class FrigateBaseModel(BaseModel): + class Config: + extra = Extra.forbid + + class DetectorTypeEnum(str, Enum): edgetpu = "edgetpu" cpu = "cpu" -class DetectorConfig(BaseModel): +class DetectorConfig(FrigateBaseModel): type: DetectorTypeEnum = Field(default=DetectorTypeEnum.cpu, title="Detector Type") device: str = Field(default="usb", title="Device Type") num_threads: int = Field(default=3, title="Number of detection threads") -class MqttConfig(BaseModel): +class MqttConfig(FrigateBaseModel): host: str = Field(title="MQTT Host") port: int = Field(default=1883, title="MQTT Port") topic_prefix: str = Field(default="frigate", title="MQTT Topic Prefix") @@ -60,7 +65,7 @@ class MqttConfig(BaseModel): return v -class RetainConfig(BaseModel): +class RetainConfig(FrigateBaseModel): default: int = Field(default=10, title="Default retention period.") objects: Dict[str, int] = Field( default_factory=dict, title="Object retention period." @@ -68,7 +73,7 @@ class RetainConfig(BaseModel): # DEPRECATED: Will eventually be removed -class ClipsConfig(BaseModel): +class ClipsConfig(FrigateBaseModel): enabled: bool = Field(default=False, title="Save clips.") max_seconds: int = Field(default=300, title="Maximum clip duration.") pre_capture: int = Field(default=5, title="Seconds to capture before event starts.") @@ -85,7 +90,7 @@ class ClipsConfig(BaseModel): ) -class RecordConfig(BaseModel): +class RecordConfig(FrigateBaseModel): enabled: bool = Field(default=False, title="Enable record on all cameras.") retain_days: int = Field(default=0, title="Recording retention period in days.") events: ClipsConfig = Field( @@ -93,7 +98,7 @@ class RecordConfig(BaseModel): ) -class MotionConfig(BaseModel): +class MotionConfig(FrigateBaseModel): threshold: int = Field( default=25, title="Motion detection threshold (1-255).", @@ -146,9 +151,10 @@ class RuntimeMotionConfig(MotionConfig): class Config: arbitrary_types_allowed = True + extra = Extra.ignore -class DetectConfig(BaseModel): +class DetectConfig(FrigateBaseModel): height: int = Field(default=720, title="Height of the stream for the detect role.") width: int = Field(default=1280, title="Width of the stream for the detect role.") fps: int = Field( @@ -160,7 +166,7 @@ class DetectConfig(BaseModel): ) -class FilterConfig(BaseModel): +class FilterConfig(FrigateBaseModel): min_area: int = Field( default=0, title="Minimum area of bounding box for object to be counted." ) @@ -201,8 +207,10 @@ class RuntimeFilterConfig(FilterConfig): class Config: arbitrary_types_allowed = True + extra = Extra.ignore +# this uses the base model because the color is an extra attribute class ZoneConfig(BaseModel): filters: Dict[str, FilterConfig] = Field( default_factory=dict, title="Zone filters." @@ -244,7 +252,7 @@ class ZoneConfig(BaseModel): self._contour = np.array([]) -class ObjectConfig(BaseModel): +class ObjectConfig(FrigateBaseModel): track: List[str] = Field(default=DEFAULT_TRACKED_OBJECTS, title="Objects to track.") filters: Optional[Dict[str, FilterConfig]] = Field(title="Object filters.") mask: Union[str, List[str]] = Field(default="", title="Object mask.") @@ -256,7 +264,7 @@ class BirdseyeModeEnum(str, Enum): continuous = "continuous" -class BirdseyeConfig(BaseModel): +class BirdseyeConfig(FrigateBaseModel): enabled: bool = Field(default=True, title="Enable birdseye view.") width: int = Field(default=1280, title="Birdseye width.") height: int = Field(default=720, title="Birdseye height.") @@ -303,7 +311,7 @@ RECORD_FFMPEG_OUTPUT_ARGS_DEFAULT = [ ] -class FfmpegOutputArgsConfig(BaseModel): +class FfmpegOutputArgsConfig(FrigateBaseModel): detect: Union[str, List[str]] = Field( default=DETECT_FFMPEG_OUTPUT_ARGS_DEFAULT, title="Detect role FFmpeg output arguments.", @@ -318,7 +326,7 @@ class FfmpegOutputArgsConfig(BaseModel): ) -class FfmpegConfig(BaseModel): +class FfmpegConfig(FrigateBaseModel): global_args: Union[str, List[str]] = Field( default=FFMPEG_GLOBAL_ARGS_DEFAULT, title="Global FFmpeg arguments." ) @@ -340,7 +348,7 @@ class CameraRoleEnum(str, Enum): detect = "detect" -class CameraInput(BaseModel): +class CameraInput(FrigateBaseModel): path: str = Field(title="Camera input path.") roles: List[CameraRoleEnum] = Field(title="Roles assigned to this input.") global_args: Union[str, List[str]] = Field( @@ -371,7 +379,7 @@ class CameraFfmpegConfig(FfmpegConfig): return v -class SnapshotsConfig(BaseModel): +class SnapshotsConfig(FrigateBaseModel): enabled: bool = Field(default=False, title="Snapshots enabled.") clean_copy: bool = Field( default=True, title="Create a clean copy of the snapshot image." @@ -399,7 +407,7 @@ class SnapshotsConfig(BaseModel): ) -class ColorConfig(BaseModel): +class ColorConfig(FrigateBaseModel): red: int = Field(default=255, ge=0, le=255, title="Red") green: int = Field(default=255, ge=0, le=255, title="Green") blue: int = Field(default=255, ge=0, le=255, title="Blue") @@ -417,7 +425,7 @@ class TimestampEffectEnum(str, Enum): shadow = "shadow" -class TimestampStyleConfig(BaseModel): +class TimestampStyleConfig(FrigateBaseModel): position: TimestampPositionEnum = Field( default=TimestampPositionEnum.tl, title="Timestamp position." ) @@ -427,7 +435,7 @@ class TimestampStyleConfig(BaseModel): effect: Optional[TimestampEffectEnum] = Field(title="Timestamp effect.") -class CameraMqttConfig(BaseModel): +class CameraMqttConfig(FrigateBaseModel): enabled: bool = Field(default=True, title="Send image over MQTT.") timestamp: bool = Field(default=True, title="Add timestamp to MQTT image.") bounding_box: bool = Field(default=True, title="Add bounding box to MQTT image.") @@ -445,16 +453,16 @@ class CameraMqttConfig(BaseModel): ) -class RtmpConfig(BaseModel): +class RtmpConfig(FrigateBaseModel): enabled: bool = Field(default=True, title="RTMP restreaming enabled.") -class CameraLiveConfig(BaseModel): +class CameraLiveConfig(FrigateBaseModel): height: int = Field(default=720, title="Live camera view height") quality: int = Field(default=8, ge=1, le=31, title="Live camera view quality") -class CameraConfig(BaseModel): +class CameraConfig(FrigateBaseModel): name: Optional[str] = Field(title="Camera name.") ffmpeg: CameraFfmpegConfig = Field(title="FFmpeg configuration for the camera.") best_image_timeout: int = Field( @@ -590,13 +598,13 @@ class CameraConfig(BaseModel): return [part for part in cmd if part != ""] -class DatabaseConfig(BaseModel): +class DatabaseConfig(FrigateBaseModel): path: str = Field( default=os.path.join(BASE_DIR, "frigate.db"), title="Database path." ) -class ModelConfig(BaseModel): +class ModelConfig(FrigateBaseModel): width: int = Field(default=320, title="Object detection model input width.") height: int = Field(default=320, title="Object detection model input height.") labelmap: Dict[int, str] = Field( @@ -636,7 +644,7 @@ class LogLevelEnum(str, Enum): critical = "critical" -class LoggerConfig(BaseModel): +class LoggerConfig(FrigateBaseModel): default: LogLevelEnum = Field( default=LogLevelEnum.info, title="Default logging level." ) @@ -645,7 +653,7 @@ class LoggerConfig(BaseModel): ) -class FrigateConfig(BaseModel): +class FrigateConfig(FrigateBaseModel): mqtt: MqttConfig = Field(title="MQTT Configuration.") database: DatabaseConfig = Field( default_factory=DatabaseConfig, title="Database configuration." diff --git a/frigate/test/test_config.py b/frigate/test/test_config.py index 22f27ca4d..8bf96736a 100644 --- a/frigate/test/test_config.py +++ b/frigate/test/test_config.py @@ -962,7 +962,7 @@ class TestConfig(unittest.TestCase): config = { "mqtt": {"host": "mqtt"}, - "timestamp_style": {"position": "bl", "scale": 1.5}, + "timestamp_style": {"position": "bl"}, "cameras": { "back": { "ffmpeg": { @@ -981,7 +981,6 @@ class TestConfig(unittest.TestCase): runtime_config = frigate_config.runtime_config assert runtime_config.cameras["back"].timestamp_style.position == "bl" - assert runtime_config.cameras["back"].timestamp_style.scale == 1.5 def test_default_timestamp_style(self): @@ -1005,14 +1004,13 @@ class TestConfig(unittest.TestCase): runtime_config = frigate_config.runtime_config assert runtime_config.cameras["back"].timestamp_style.position == "tl" - assert runtime_config.cameras["back"].timestamp_style.scale == 1.0 def test_global_timestamp_style_merge(self): config = { "mqtt": {"host": "mqtt"}, "rtmp": {"enabled": False}, - "timestamp_style": {"position": "br", "scale": 2.0}, + "timestamp_style": {"position": "br", "thickness": 2}, "cameras": { "back": { "ffmpeg": { @@ -1023,7 +1021,7 @@ class TestConfig(unittest.TestCase): }, ] }, - "timestamp_style": {"position": "bl", "scale": 1.5}, + "timestamp_style": {"position": "bl", "thickness": 4}, } }, } @@ -1032,7 +1030,7 @@ class TestConfig(unittest.TestCase): runtime_config = frigate_config.runtime_config assert runtime_config.cameras["back"].timestamp_style.position == "bl" - assert runtime_config.cameras["back"].timestamp_style.scale == 1.5 + assert runtime_config.cameras["back"].timestamp_style.thickness == 4 if __name__ == "__main__":