mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +01:00
Migrate pydantic to V2 (#10142)
* Run pydantic migration tool * Finish removing deprecated functions * Formatting * Fix movement weights type * Fix movement weight test * Fix config checks * formatting * fix typing * formatting * Fix * Fix serialization issues * Formatting * fix model namespace warnings * Update formatting * Format go2rtc file * Cleanup migrations * Fix warnings * Don't include null values in config json * Formatting * Fix test --------- Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
This commit is contained in:
parent
a1424bad6c
commit
cb30450060
@ -12,7 +12,7 @@ pandas == 2.1.4
|
|||||||
peewee == 3.17.*
|
peewee == 3.17.*
|
||||||
peewee_migrate == 1.12.*
|
peewee_migrate == 1.12.*
|
||||||
psutil == 5.9.*
|
psutil == 5.9.*
|
||||||
pydantic == 1.10.*
|
pydantic == 2.6.*
|
||||||
git+https://github.com/fbcotter/py3nvml#egg=py3nvml
|
git+https://github.com/fbcotter/py3nvml#egg=py3nvml
|
||||||
PyYAML == 6.0.*
|
PyYAML == 6.0.*
|
||||||
pytz == 2023.3.post1
|
pytz == 2023.3.post1
|
||||||
|
@ -109,9 +109,9 @@ if int(os.environ["LIBAVFORMAT_VERSION_MAJOR"]) < 59:
|
|||||||
"rtsp": "-fflags nobuffer -flags low_delay -stimeout 5000000 -user_agent go2rtc/ffmpeg -rtsp_transport tcp -i {input}"
|
"rtsp": "-fflags nobuffer -flags low_delay -stimeout 5000000 -user_agent go2rtc/ffmpeg -rtsp_transport tcp -i {input}"
|
||||||
}
|
}
|
||||||
elif go2rtc_config["ffmpeg"].get("rtsp") is None:
|
elif go2rtc_config["ffmpeg"].get("rtsp") is None:
|
||||||
go2rtc_config["ffmpeg"][
|
go2rtc_config["ffmpeg"]["rtsp"] = (
|
||||||
"rtsp"
|
"-fflags nobuffer -flags low_delay -stimeout 5000000 -user_agent go2rtc/ffmpeg -rtsp_transport tcp -i {input}"
|
||||||
] = "-fflags nobuffer -flags low_delay -stimeout 5000000 -user_agent go2rtc/ffmpeg -rtsp_transport tcp -i {input}"
|
)
|
||||||
|
|
||||||
# add hardware acceleration presets for rockchip devices
|
# add hardware acceleration presets for rockchip devices
|
||||||
# may be removed if frigate uses a go2rtc version that includes these presets
|
# may be removed if frigate uses a go2rtc version that includes these presets
|
||||||
|
@ -6,11 +6,19 @@ import logging
|
|||||||
import os
|
import os
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, List, Optional, Tuple, Union
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||||
|
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from pydantic import BaseModel, Extra, Field, parse_obj_as, validator
|
from pydantic import (
|
||||||
|
BaseModel,
|
||||||
|
ConfigDict,
|
||||||
|
Field,
|
||||||
|
TypeAdapter,
|
||||||
|
ValidationInfo,
|
||||||
|
field_serializer,
|
||||||
|
field_validator,
|
||||||
|
)
|
||||||
from pydantic.fields import PrivateAttr
|
from pydantic.fields import PrivateAttr
|
||||||
|
|
||||||
from frigate.const import (
|
from frigate.const import (
|
||||||
@ -66,8 +74,7 @@ DEFAULT_TIME_LAPSE_FFMPEG_ARGS = "-vf setpts=0.04*PTS -r 30"
|
|||||||
|
|
||||||
|
|
||||||
class FrigateBaseModel(BaseModel):
|
class FrigateBaseModel(BaseModel):
|
||||||
class Config:
|
model_config = ConfigDict(extra="forbid", protected_namespaces=())
|
||||||
extra = Extra.forbid
|
|
||||||
|
|
||||||
|
|
||||||
class LiveModeEnum(str, Enum):
|
class LiveModeEnum(str, Enum):
|
||||||
@ -93,7 +100,7 @@ class UIConfig(FrigateBaseModel):
|
|||||||
live_mode: LiveModeEnum = Field(
|
live_mode: LiveModeEnum = Field(
|
||||||
default=LiveModeEnum.mse, title="Default Live Mode."
|
default=LiveModeEnum.mse, title="Default Live Mode."
|
||||||
)
|
)
|
||||||
timezone: Optional[str] = Field(title="Override UI timezone.")
|
timezone: Optional[str] = Field(default=None, title="Override UI timezone.")
|
||||||
use_experimental: bool = Field(default=False, title="Experimental UI")
|
use_experimental: bool = Field(default=False, title="Experimental UI")
|
||||||
time_format: TimeFormatEnum = Field(
|
time_format: TimeFormatEnum = Field(
|
||||||
default=TimeFormatEnum.browser, title="Override UI time format."
|
default=TimeFormatEnum.browser, title="Override UI time format."
|
||||||
@ -135,16 +142,17 @@ class MqttConfig(FrigateBaseModel):
|
|||||||
topic_prefix: str = Field(default="frigate", title="MQTT Topic Prefix")
|
topic_prefix: str = Field(default="frigate", title="MQTT Topic Prefix")
|
||||||
client_id: str = Field(default="frigate", title="MQTT Client ID")
|
client_id: str = Field(default="frigate", title="MQTT Client ID")
|
||||||
stats_interval: int = Field(default=60, title="MQTT Camera Stats Interval")
|
stats_interval: int = Field(default=60, title="MQTT Camera Stats Interval")
|
||||||
user: Optional[str] = Field(title="MQTT Username")
|
user: Optional[str] = Field(None, title="MQTT Username")
|
||||||
password: Optional[str] = Field(title="MQTT Password")
|
password: Optional[str] = Field(None, title="MQTT Password", validate_default=True)
|
||||||
tls_ca_certs: Optional[str] = Field(title="MQTT TLS CA Certificates")
|
tls_ca_certs: Optional[str] = Field(None, title="MQTT TLS CA Certificates")
|
||||||
tls_client_cert: Optional[str] = Field(title="MQTT TLS Client Certificate")
|
tls_client_cert: Optional[str] = Field(None, title="MQTT TLS Client Certificate")
|
||||||
tls_client_key: Optional[str] = Field(title="MQTT TLS Client Key")
|
tls_client_key: Optional[str] = Field(None, title="MQTT TLS Client Key")
|
||||||
tls_insecure: Optional[bool] = Field(title="MQTT TLS Insecure")
|
tls_insecure: Optional[bool] = Field(None, title="MQTT TLS Insecure")
|
||||||
|
|
||||||
@validator("password", pre=True, always=True)
|
@field_validator("password")
|
||||||
def validate_password(cls, v, values):
|
def user_requires_pass(cls, v, info: ValidationInfo):
|
||||||
if (v is None) != (values["user"] is None):
|
print(f"doing a check where {v} is None and {info.data['user']} is None")
|
||||||
|
if (v is None) != (info.data["user"] is None):
|
||||||
raise ValueError("Password must be provided with username.")
|
raise ValueError("Password must be provided with username.")
|
||||||
return v
|
return v
|
||||||
|
|
||||||
@ -186,18 +194,19 @@ class PtzAutotrackConfig(FrigateBaseModel):
|
|||||||
title="Internal value used for PTZ movements based on the speed of your camera's motor.",
|
title="Internal value used for PTZ movements based on the speed of your camera's motor.",
|
||||||
)
|
)
|
||||||
enabled_in_config: Optional[bool] = Field(
|
enabled_in_config: Optional[bool] = Field(
|
||||||
title="Keep track of original state of autotracking."
|
None, title="Keep track of original state of autotracking."
|
||||||
)
|
)
|
||||||
|
|
||||||
@validator("movement_weights", pre=True)
|
@field_validator("movement_weights", mode="before")
|
||||||
|
@classmethod
|
||||||
def validate_weights(cls, v):
|
def validate_weights(cls, v):
|
||||||
if v is None:
|
if v is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if isinstance(v, str):
|
if isinstance(v, str):
|
||||||
weights = list(map(float, v.split(",")))
|
weights = list(map(str, map(float, v.split(","))))
|
||||||
elif isinstance(v, list):
|
elif isinstance(v, list):
|
||||||
weights = [float(val) for val in v]
|
weights = [str(float(val)) for val in v]
|
||||||
else:
|
else:
|
||||||
raise ValueError("Invalid type for movement_weights")
|
raise ValueError("Invalid type for movement_weights")
|
||||||
|
|
||||||
@ -210,8 +219,8 @@ class PtzAutotrackConfig(FrigateBaseModel):
|
|||||||
class OnvifConfig(FrigateBaseModel):
|
class OnvifConfig(FrigateBaseModel):
|
||||||
host: str = Field(default="", title="Onvif Host")
|
host: str = Field(default="", title="Onvif Host")
|
||||||
port: int = Field(default=8000, title="Onvif Port")
|
port: int = Field(default=8000, title="Onvif Port")
|
||||||
user: Optional[str] = Field(title="Onvif Username")
|
user: Optional[str] = Field(None, title="Onvif Username")
|
||||||
password: Optional[str] = Field(title="Onvif Password")
|
password: Optional[str] = Field(None, title="Onvif Password")
|
||||||
autotracking: PtzAutotrackConfig = Field(
|
autotracking: PtzAutotrackConfig = Field(
|
||||||
default_factory=PtzAutotrackConfig,
|
default_factory=PtzAutotrackConfig,
|
||||||
title="PTZ auto tracking config.",
|
title="PTZ auto tracking config.",
|
||||||
@ -242,6 +251,7 @@ class EventsConfig(FrigateBaseModel):
|
|||||||
title="List of required zones to be entered in order to save the event.",
|
title="List of required zones to be entered in order to save the event.",
|
||||||
)
|
)
|
||||||
objects: Optional[List[str]] = Field(
|
objects: Optional[List[str]] = Field(
|
||||||
|
None,
|
||||||
title="List of objects to be detected in order to save the event.",
|
title="List of objects to be detected in order to save the event.",
|
||||||
)
|
)
|
||||||
retain: RetainConfig = Field(
|
retain: RetainConfig = Field(
|
||||||
@ -296,7 +306,7 @@ class RecordConfig(FrigateBaseModel):
|
|||||||
default_factory=RecordPreviewConfig, title="Recording Preview Config"
|
default_factory=RecordPreviewConfig, title="Recording Preview Config"
|
||||||
)
|
)
|
||||||
enabled_in_config: Optional[bool] = Field(
|
enabled_in_config: Optional[bool] = Field(
|
||||||
title="Keep track of original state of recording."
|
None, title="Keep track of original state of recording."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -324,8 +334,17 @@ class MotionConfig(FrigateBaseModel):
|
|||||||
title="Delay for updating MQTT with no motion detected.",
|
title="Delay for updating MQTT with no motion detected.",
|
||||||
)
|
)
|
||||||
enabled_in_config: Optional[bool] = Field(
|
enabled_in_config: Optional[bool] = Field(
|
||||||
title="Keep track of original state of motion detection."
|
None, title="Keep track of original state of motion detection."
|
||||||
)
|
)
|
||||||
|
raw_mask: Union[str, List[str]] = ""
|
||||||
|
|
||||||
|
@field_serializer("mask", when_used="json")
|
||||||
|
def serialize_mask(self, value: Any, info):
|
||||||
|
return self.raw_mask
|
||||||
|
|
||||||
|
@field_serializer("raw_mask", when_used="json")
|
||||||
|
def serialize_raw_mask(self, value: Any, info):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class RuntimeMotionConfig(MotionConfig):
|
class RuntimeMotionConfig(MotionConfig):
|
||||||
@ -348,19 +367,25 @@ class RuntimeMotionConfig(MotionConfig):
|
|||||||
super().__init__(**config)
|
super().__init__(**config)
|
||||||
|
|
||||||
def dict(self, **kwargs):
|
def dict(self, **kwargs):
|
||||||
ret = super().dict(**kwargs)
|
ret = super().model_dump(**kwargs)
|
||||||
if "mask" in ret:
|
if "mask" in ret:
|
||||||
ret["mask"] = ret["raw_mask"]
|
ret["mask"] = ret["raw_mask"]
|
||||||
ret.pop("raw_mask")
|
ret.pop("raw_mask")
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
class Config:
|
@field_serializer("mask", when_used="json")
|
||||||
arbitrary_types_allowed = True
|
def serialize_mask(self, value: Any, info):
|
||||||
extra = Extra.ignore
|
return self.raw_mask
|
||||||
|
|
||||||
|
@field_serializer("raw_mask", when_used="json")
|
||||||
|
def serialize_raw_mask(self, value: Any, info):
|
||||||
|
return None
|
||||||
|
|
||||||
|
model_config = ConfigDict(arbitrary_types_allowed=True, extra="ignore")
|
||||||
|
|
||||||
|
|
||||||
class StationaryMaxFramesConfig(FrigateBaseModel):
|
class StationaryMaxFramesConfig(FrigateBaseModel):
|
||||||
default: Optional[int] = Field(title="Default max frames.", ge=1)
|
default: Optional[int] = Field(None, title="Default max frames.", ge=1)
|
||||||
objects: Dict[str, int] = Field(
|
objects: Dict[str, int] = Field(
|
||||||
default_factory=dict, title="Object specific max frames."
|
default_factory=dict, title="Object specific max frames."
|
||||||
)
|
)
|
||||||
@ -368,10 +393,12 @@ class StationaryMaxFramesConfig(FrigateBaseModel):
|
|||||||
|
|
||||||
class StationaryConfig(FrigateBaseModel):
|
class StationaryConfig(FrigateBaseModel):
|
||||||
interval: Optional[int] = Field(
|
interval: Optional[int] = Field(
|
||||||
|
None,
|
||||||
title="Frame interval for checking stationary objects.",
|
title="Frame interval for checking stationary objects.",
|
||||||
gt=0,
|
gt=0,
|
||||||
)
|
)
|
||||||
threshold: Optional[int] = Field(
|
threshold: Optional[int] = Field(
|
||||||
|
None,
|
||||||
title="Number of frames without a position change for an object to be considered stationary",
|
title="Number of frames without a position change for an object to be considered stationary",
|
||||||
ge=1,
|
ge=1,
|
||||||
)
|
)
|
||||||
@ -382,17 +409,21 @@ class StationaryConfig(FrigateBaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class DetectConfig(FrigateBaseModel):
|
class DetectConfig(FrigateBaseModel):
|
||||||
height: Optional[int] = Field(title="Height of the stream for the detect role.")
|
height: Optional[int] = Field(
|
||||||
width: Optional[int] = Field(title="Width of the stream for the detect role.")
|
None, title="Height of the stream for the detect role."
|
||||||
|
)
|
||||||
|
width: Optional[int] = Field(None, title="Width of the stream for the detect role.")
|
||||||
fps: int = Field(
|
fps: int = Field(
|
||||||
default=5, title="Number of frames per second to process through detection."
|
default=5, title="Number of frames per second to process through detection."
|
||||||
)
|
)
|
||||||
enabled: bool = Field(default=True, title="Detection Enabled.")
|
enabled: bool = Field(default=True, title="Detection Enabled.")
|
||||||
min_initialized: Optional[int] = Field(
|
min_initialized: Optional[int] = Field(
|
||||||
title="Minimum number of consecutive hits for an object to be initialized by the tracker."
|
None,
|
||||||
|
title="Minimum number of consecutive hits for an object to be initialized by the tracker.",
|
||||||
)
|
)
|
||||||
max_disappeared: Optional[int] = Field(
|
max_disappeared: Optional[int] = Field(
|
||||||
title="Maximum number of frames the object can dissapear before detection ends."
|
None,
|
||||||
|
title="Maximum number of frames the object can dissapear before detection ends.",
|
||||||
)
|
)
|
||||||
stationary: StationaryConfig = Field(
|
stationary: StationaryConfig = Field(
|
||||||
default_factory=StationaryConfig,
|
default_factory=StationaryConfig,
|
||||||
@ -426,8 +457,18 @@ class FilterConfig(FrigateBaseModel):
|
|||||||
default=0.5, title="Minimum detection confidence for object to be counted."
|
default=0.5, title="Minimum detection confidence for object to be counted."
|
||||||
)
|
)
|
||||||
mask: Optional[Union[str, List[str]]] = Field(
|
mask: Optional[Union[str, List[str]]] = Field(
|
||||||
|
None,
|
||||||
title="Detection area polygon mask for this filter configuration.",
|
title="Detection area polygon mask for this filter configuration.",
|
||||||
)
|
)
|
||||||
|
raw_mask: Union[str, List[str]] = ""
|
||||||
|
|
||||||
|
@field_serializer("mask", when_used="json")
|
||||||
|
def serialize_mask(self, value: Any, info):
|
||||||
|
return self.raw_mask
|
||||||
|
|
||||||
|
@field_serializer("raw_mask", when_used="json")
|
||||||
|
def serialize_raw_mask(self, value: Any, info):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class AudioFilterConfig(FrigateBaseModel):
|
class AudioFilterConfig(FrigateBaseModel):
|
||||||
@ -440,8 +481,8 @@ class AudioFilterConfig(FrigateBaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class RuntimeFilterConfig(FilterConfig):
|
class RuntimeFilterConfig(FilterConfig):
|
||||||
mask: Optional[np.ndarray]
|
mask: Optional[np.ndarray] = None
|
||||||
raw_mask: Optional[Union[str, List[str]]]
|
raw_mask: Optional[Union[str, List[str]]] = None
|
||||||
|
|
||||||
def __init__(self, **config):
|
def __init__(self, **config):
|
||||||
mask = config.get("mask")
|
mask = config.get("mask")
|
||||||
@ -453,15 +494,13 @@ class RuntimeFilterConfig(FilterConfig):
|
|||||||
super().__init__(**config)
|
super().__init__(**config)
|
||||||
|
|
||||||
def dict(self, **kwargs):
|
def dict(self, **kwargs):
|
||||||
ret = super().dict(**kwargs)
|
ret = super().model_dump(**kwargs)
|
||||||
if "mask" in ret:
|
if "mask" in ret:
|
||||||
ret["mask"] = ret["raw_mask"]
|
ret["mask"] = ret["raw_mask"]
|
||||||
ret.pop("raw_mask")
|
ret.pop("raw_mask")
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
class Config:
|
model_config = ConfigDict(arbitrary_types_allowed=True, extra="ignore")
|
||||||
arbitrary_types_allowed = True
|
|
||||||
extra = Extra.ignore
|
|
||||||
|
|
||||||
|
|
||||||
# this uses the base model because the color is an extra attribute
|
# this uses the base model because the color is an extra attribute
|
||||||
@ -531,9 +570,11 @@ class AudioConfig(FrigateBaseModel):
|
|||||||
listen: List[str] = Field(
|
listen: List[str] = Field(
|
||||||
default=DEFAULT_LISTEN_AUDIO, title="Audio to listen for."
|
default=DEFAULT_LISTEN_AUDIO, title="Audio to listen for."
|
||||||
)
|
)
|
||||||
filters: Optional[Dict[str, AudioFilterConfig]] = Field(title="Audio filters.")
|
filters: Optional[Dict[str, AudioFilterConfig]] = Field(
|
||||||
|
None, title="Audio filters."
|
||||||
|
)
|
||||||
enabled_in_config: Optional[bool] = Field(
|
enabled_in_config: Optional[bool] = Field(
|
||||||
title="Keep track of original state of audio detection."
|
None, title="Keep track of original state of audio detection."
|
||||||
)
|
)
|
||||||
num_threads: int = Field(default=2, title="Number of detection threads", ge=1)
|
num_threads: int = Field(default=2, title="Number of detection threads", ge=1)
|
||||||
|
|
||||||
@ -660,7 +701,8 @@ class CameraInput(FrigateBaseModel):
|
|||||||
class CameraFfmpegConfig(FfmpegConfig):
|
class CameraFfmpegConfig(FfmpegConfig):
|
||||||
inputs: List[CameraInput] = Field(title="Camera inputs.")
|
inputs: List[CameraInput] = Field(title="Camera inputs.")
|
||||||
|
|
||||||
@validator("inputs")
|
@field_validator("inputs")
|
||||||
|
@classmethod
|
||||||
def validate_roles(cls, v):
|
def validate_roles(cls, v):
|
||||||
roles = [role for i in v for role in i.roles]
|
roles = [role for i in v for role in i.roles]
|
||||||
roles_set = set(roles)
|
roles_set = set(roles)
|
||||||
@ -690,7 +732,7 @@ class SnapshotsConfig(FrigateBaseModel):
|
|||||||
default_factory=list,
|
default_factory=list,
|
||||||
title="List of required zones to be entered in order to save a snapshot.",
|
title="List of required zones to be entered in order to save a snapshot.",
|
||||||
)
|
)
|
||||||
height: Optional[int] = Field(title="Snapshot image height.")
|
height: Optional[int] = Field(None, title="Snapshot image height.")
|
||||||
retain: RetainConfig = Field(
|
retain: RetainConfig = Field(
|
||||||
default_factory=RetainConfig, title="Snapshot retention."
|
default_factory=RetainConfig, title="Snapshot retention."
|
||||||
)
|
)
|
||||||
@ -727,7 +769,7 @@ class TimestampStyleConfig(FrigateBaseModel):
|
|||||||
format: str = Field(default=DEFAULT_TIME_FORMAT, title="Timestamp format.")
|
format: str = Field(default=DEFAULT_TIME_FORMAT, title="Timestamp format.")
|
||||||
color: ColorConfig = Field(default_factory=ColorConfig, title="Timestamp color.")
|
color: ColorConfig = Field(default_factory=ColorConfig, title="Timestamp color.")
|
||||||
thickness: int = Field(default=2, title="Timestamp thickness.")
|
thickness: int = Field(default=2, title="Timestamp thickness.")
|
||||||
effect: Optional[TimestampEffectEnum] = Field(title="Timestamp effect.")
|
effect: Optional[TimestampEffectEnum] = Field(None, title="Timestamp effect.")
|
||||||
|
|
||||||
|
|
||||||
class CameraMqttConfig(FrigateBaseModel):
|
class CameraMqttConfig(FrigateBaseModel):
|
||||||
@ -755,8 +797,7 @@ class CameraLiveConfig(FrigateBaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class RestreamConfig(BaseModel):
|
class RestreamConfig(BaseModel):
|
||||||
class Config:
|
model_config = ConfigDict(extra="allow")
|
||||||
extra = Extra.allow
|
|
||||||
|
|
||||||
|
|
||||||
class CameraUiConfig(FrigateBaseModel):
|
class CameraUiConfig(FrigateBaseModel):
|
||||||
@ -767,7 +808,7 @@ class CameraUiConfig(FrigateBaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class CameraConfig(FrigateBaseModel):
|
class CameraConfig(FrigateBaseModel):
|
||||||
name: Optional[str] = Field(title="Camera name.", regex=REGEX_CAMERA_NAME)
|
name: Optional[str] = Field(None, title="Camera name.", pattern=REGEX_CAMERA_NAME)
|
||||||
enabled: bool = Field(default=True, title="Enable camera.")
|
enabled: bool = Field(default=True, title="Enable camera.")
|
||||||
ffmpeg: CameraFfmpegConfig = Field(title="FFmpeg configuration for the camera.")
|
ffmpeg: CameraFfmpegConfig = Field(title="FFmpeg configuration for the camera.")
|
||||||
best_image_timeout: int = Field(
|
best_image_timeout: int = Field(
|
||||||
@ -775,6 +816,7 @@ class CameraConfig(FrigateBaseModel):
|
|||||||
title="How long to wait for the image with the highest confidence score.",
|
title="How long to wait for the image with the highest confidence score.",
|
||||||
)
|
)
|
||||||
webui_url: Optional[str] = Field(
|
webui_url: Optional[str] = Field(
|
||||||
|
None,
|
||||||
title="URL to visit the camera directly from system page",
|
title="URL to visit the camera directly from system page",
|
||||||
)
|
)
|
||||||
zones: Dict[str, ZoneConfig] = Field(
|
zones: Dict[str, ZoneConfig] = Field(
|
||||||
@ -798,7 +840,9 @@ class CameraConfig(FrigateBaseModel):
|
|||||||
audio: AudioConfig = Field(
|
audio: AudioConfig = Field(
|
||||||
default_factory=AudioConfig, title="Audio events configuration."
|
default_factory=AudioConfig, title="Audio events configuration."
|
||||||
)
|
)
|
||||||
motion: Optional[MotionConfig] = Field(title="Motion detection configuration.")
|
motion: Optional[MotionConfig] = Field(
|
||||||
|
None, title="Motion detection configuration."
|
||||||
|
)
|
||||||
detect: DetectConfig = Field(
|
detect: DetectConfig = Field(
|
||||||
default_factory=DetectConfig, title="Object detection configuration."
|
default_factory=DetectConfig, title="Object detection configuration."
|
||||||
)
|
)
|
||||||
@ -983,7 +1027,7 @@ def verify_valid_live_stream_name(
|
|||||||
"""Verify that a restream exists to use for live view."""
|
"""Verify that a restream exists to use for live view."""
|
||||||
if (
|
if (
|
||||||
camera_config.live.stream_name
|
camera_config.live.stream_name
|
||||||
not in frigate_config.go2rtc.dict().get("streams", {}).keys()
|
not in frigate_config.go2rtc.model_dump().get("streams", {}).keys()
|
||||||
):
|
):
|
||||||
return ValueError(
|
return ValueError(
|
||||||
f"No restream with name {camera_config.live.stream_name} exists for camera {camera_config.name}."
|
f"No restream with name {camera_config.live.stream_name} exists for camera {camera_config.name}."
|
||||||
@ -1108,7 +1152,7 @@ class FrigateConfig(FrigateBaseModel):
|
|||||||
default_factory=AudioConfig, title="Global Audio events configuration."
|
default_factory=AudioConfig, title="Global Audio events configuration."
|
||||||
)
|
)
|
||||||
motion: Optional[MotionConfig] = Field(
|
motion: Optional[MotionConfig] = Field(
|
||||||
title="Global motion detection configuration."
|
None, title="Global motion detection configuration."
|
||||||
)
|
)
|
||||||
detect: DetectConfig = Field(
|
detect: DetectConfig = Field(
|
||||||
default_factory=DetectConfig, title="Global object tracking configuration."
|
default_factory=DetectConfig, title="Global object tracking configuration."
|
||||||
@ -1121,7 +1165,7 @@ class FrigateConfig(FrigateBaseModel):
|
|||||||
|
|
||||||
def runtime_config(self, plus_api: PlusApi = None) -> FrigateConfig:
|
def runtime_config(self, plus_api: PlusApi = None) -> FrigateConfig:
|
||||||
"""Merge camera config with globals."""
|
"""Merge camera config with globals."""
|
||||||
config = self.copy(deep=True)
|
config = self.model_copy(deep=True)
|
||||||
|
|
||||||
# MQTT user/password substitutions
|
# MQTT user/password substitutions
|
||||||
if config.mqtt.user or config.mqtt.password:
|
if config.mqtt.user or config.mqtt.password:
|
||||||
@ -1140,7 +1184,7 @@ class FrigateConfig(FrigateBaseModel):
|
|||||||
config.ffmpeg.hwaccel_args = auto_detect_hwaccel()
|
config.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.dict(
|
global_config = config.model_dump(
|
||||||
include={
|
include={
|
||||||
"audio": ...,
|
"audio": ...,
|
||||||
"birdseye": ...,
|
"birdseye": ...,
|
||||||
@ -1157,8 +1201,10 @@ class FrigateConfig(FrigateBaseModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
for name, camera in config.cameras.items():
|
for name, camera in config.cameras.items():
|
||||||
merged_config = deep_merge(camera.dict(exclude_unset=True), global_config)
|
merged_config = deep_merge(
|
||||||
camera_config: CameraConfig = CameraConfig.parse_obj(
|
camera.model_dump(exclude_unset=True), global_config
|
||||||
|
)
|
||||||
|
camera_config: CameraConfig = CameraConfig.model_validate(
|
||||||
{"name": name, **merged_config}
|
{"name": name, **merged_config}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1203,7 +1249,7 @@ class FrigateConfig(FrigateBaseModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Default min_initialized configuration
|
# Default min_initialized configuration
|
||||||
min_initialized = camera_config.detect.fps / 2
|
min_initialized = int(camera_config.detect.fps / 2)
|
||||||
if camera_config.detect.min_initialized is None:
|
if camera_config.detect.min_initialized is None:
|
||||||
camera_config.detect.min_initialized = min_initialized
|
camera_config.detect.min_initialized = min_initialized
|
||||||
|
|
||||||
@ -1267,7 +1313,7 @@ class FrigateConfig(FrigateBaseModel):
|
|||||||
# Set runtime filter to create masks
|
# Set runtime filter to create masks
|
||||||
camera_config.objects.filters[object] = RuntimeFilterConfig(
|
camera_config.objects.filters[object] = RuntimeFilterConfig(
|
||||||
frame_shape=camera_config.frame_shape,
|
frame_shape=camera_config.frame_shape,
|
||||||
**filter.dict(exclude_unset=True),
|
**filter.model_dump(exclude_unset=True),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Convert motion configuration
|
# Convert motion configuration
|
||||||
@ -1279,7 +1325,7 @@ class FrigateConfig(FrigateBaseModel):
|
|||||||
camera_config.motion = RuntimeMotionConfig(
|
camera_config.motion = RuntimeMotionConfig(
|
||||||
frame_shape=camera_config.frame_shape,
|
frame_shape=camera_config.frame_shape,
|
||||||
raw_mask=camera_config.motion.mask,
|
raw_mask=camera_config.motion.mask,
|
||||||
**camera_config.motion.dict(exclude_unset=True),
|
**camera_config.motion.model_dump(exclude_unset=True),
|
||||||
)
|
)
|
||||||
camera_config.motion.enabled_in_config = camera_config.motion.enabled
|
camera_config.motion.enabled_in_config = camera_config.motion.enabled
|
||||||
|
|
||||||
@ -1309,12 +1355,16 @@ class FrigateConfig(FrigateBaseModel):
|
|||||||
config.model.check_and_load_plus_model(plus_api)
|
config.model.check_and_load_plus_model(plus_api)
|
||||||
|
|
||||||
for key, detector in config.detectors.items():
|
for key, detector in config.detectors.items():
|
||||||
detector_config: DetectorConfig = parse_obj_as(DetectorConfig, detector)
|
adapter = TypeAdapter(DetectorConfig)
|
||||||
|
model_dict = (
|
||||||
|
detector if isinstance(detector, dict) else detector.model_dump()
|
||||||
|
)
|
||||||
|
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
|
detector_config.model = config.model
|
||||||
else:
|
else:
|
||||||
model = detector_config.model
|
model = detector_config.model
|
||||||
schema = ModelConfig.schema()["properties"]
|
schema = ModelConfig.model_json_schema()["properties"]
|
||||||
if (
|
if (
|
||||||
model.width != schema["width"]["default"]
|
model.width != schema["width"]["default"]
|
||||||
or model.height != schema["height"]["default"]
|
or model.height != schema["height"]["default"]
|
||||||
@ -1328,8 +1378,8 @@ class FrigateConfig(FrigateBaseModel):
|
|||||||
"Customizing more than a detector model path is unsupported."
|
"Customizing more than a detector model path is unsupported."
|
||||||
)
|
)
|
||||||
merged_model = deep_merge(
|
merged_model = deep_merge(
|
||||||
detector_config.model.dict(exclude_unset=True),
|
detector_config.model.model_dump(exclude_unset=True),
|
||||||
config.model.dict(exclude_unset=True),
|
config.model.model_dump(exclude_unset=True),
|
||||||
)
|
)
|
||||||
|
|
||||||
if "path" not in merged_model:
|
if "path" not in merged_model:
|
||||||
@ -1338,7 +1388,7 @@ class FrigateConfig(FrigateBaseModel):
|
|||||||
elif detector_config.type == "edgetpu":
|
elif detector_config.type == "edgetpu":
|
||||||
merged_model["path"] = "/edgetpu_model.tflite"
|
merged_model["path"] = "/edgetpu_model.tflite"
|
||||||
|
|
||||||
detector_config.model = ModelConfig.parse_obj(merged_model)
|
detector_config.model = ModelConfig.model_validate(merged_model)
|
||||||
detector_config.model.check_and_load_plus_model(
|
detector_config.model.check_and_load_plus_model(
|
||||||
plus_api, detector_config.type
|
plus_api, detector_config.type
|
||||||
)
|
)
|
||||||
@ -1347,7 +1397,8 @@ class FrigateConfig(FrigateBaseModel):
|
|||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
@validator("cameras")
|
@field_validator("cameras")
|
||||||
|
@classmethod
|
||||||
def ensure_zones_and_cameras_have_different_names(cls, v: Dict[str, CameraConfig]):
|
def ensure_zones_and_cameras_have_different_names(cls, v: Dict[str, CameraConfig]):
|
||||||
zones = [zone for camera in v.values() for zone in camera.zones.keys()]
|
zones = [zone for camera in v.values() for zone in camera.zones.keys()]
|
||||||
for zone in zones:
|
for zone in zones:
|
||||||
@ -1365,9 +1416,9 @@ class FrigateConfig(FrigateBaseModel):
|
|||||||
elif config_file.endswith(".json"):
|
elif config_file.endswith(".json"):
|
||||||
config = json.loads(raw_config)
|
config = json.loads(raw_config)
|
||||||
|
|
||||||
return cls.parse_obj(config)
|
return cls.model_validate(config)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_raw(cls, raw_config):
|
def parse_raw(cls, raw_config):
|
||||||
config = load_config_with_no_duplicates(raw_config)
|
config = load_config_with_no_duplicates(raw_config)
|
||||||
return cls.parse_obj(config)
|
return cls.model_validate(config)
|
||||||
|
@ -7,7 +7,7 @@ from typing import Dict, Optional, Tuple
|
|||||||
|
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import requests
|
import requests
|
||||||
from pydantic import BaseModel, Extra, Field
|
from pydantic import BaseModel, ConfigDict, Field
|
||||||
from pydantic.fields import PrivateAttr
|
from pydantic.fields import PrivateAttr
|
||||||
|
|
||||||
from frigate.plus import PlusApi
|
from frigate.plus import PlusApi
|
||||||
@ -35,8 +35,10 @@ class ModelTypeEnum(str, Enum):
|
|||||||
|
|
||||||
|
|
||||||
class ModelConfig(BaseModel):
|
class ModelConfig(BaseModel):
|
||||||
path: Optional[str] = Field(title="Custom Object detection model path.")
|
path: Optional[str] = Field(None, title="Custom Object detection model path.")
|
||||||
labelmap_path: Optional[str] = Field(title="Label map for custom object detector.")
|
labelmap_path: Optional[str] = Field(
|
||||||
|
None, title="Label map for custom object detector."
|
||||||
|
)
|
||||||
width: int = Field(default=320, title="Object detection model input width.")
|
width: int = Field(default=320, title="Object detection model input width.")
|
||||||
height: int = Field(default=320, title="Object detection model input height.")
|
height: int = Field(default=320, title="Object detection model input height.")
|
||||||
labelmap: Dict[int, str] = Field(
|
labelmap: Dict[int, str] = Field(
|
||||||
@ -132,17 +134,15 @@ class ModelConfig(BaseModel):
|
|||||||
for key, val in enumerate(enabled_labels):
|
for key, val in enumerate(enabled_labels):
|
||||||
self._colormap[val] = tuple(int(round(255 * c)) for c in cmap(key)[:3])
|
self._colormap[val] = tuple(int(round(255 * c)) for c in cmap(key)[:3])
|
||||||
|
|
||||||
class Config:
|
model_config = ConfigDict(extra="forbid", protected_namespaces=())
|
||||||
extra = Extra.forbid
|
|
||||||
|
|
||||||
|
|
||||||
class BaseDetectorConfig(BaseModel):
|
class BaseDetectorConfig(BaseModel):
|
||||||
# the type field must be defined in all subclasses
|
# the type field must be defined in all subclasses
|
||||||
type: str = Field(default="cpu", title="Detector Type")
|
type: str = Field(default="cpu", title="Detector Type")
|
||||||
model: ModelConfig = Field(
|
model: Optional[ModelConfig] = Field(
|
||||||
default=None, title="Detector specific model configuration."
|
default=None, title="Detector specific model configuration."
|
||||||
)
|
)
|
||||||
|
model_config = ConfigDict(
|
||||||
class Config:
|
extra="allow", arbitrary_types_allowed=True, protected_namespaces=()
|
||||||
extra = Extra.allow
|
)
|
||||||
arbitrary_types_allowed = True
|
|
||||||
|
@ -247,9 +247,9 @@ class AudioEventMaintainer(threading.Thread):
|
|||||||
|
|
||||||
def handle_detection(self, label: str, score: float) -> None:
|
def handle_detection(self, label: str, score: float) -> None:
|
||||||
if self.detections.get(label):
|
if self.detections.get(label):
|
||||||
self.detections[label][
|
self.detections[label]["last_detection"] = (
|
||||||
"last_detection"
|
datetime.datetime.now().timestamp()
|
||||||
] = datetime.datetime.now().timestamp()
|
)
|
||||||
else:
|
else:
|
||||||
self.requestor.send_data(f"{self.config.name}/audio/{label}", "ON")
|
self.requestor.send_data(f"{self.config.name}/audio/{label}", "ON")
|
||||||
|
|
||||||
|
@ -115,12 +115,12 @@ PRESETS_HW_ACCEL_ENCODE_BIRDSEYE = {
|
|||||||
"preset-rk-h265": "ffmpeg -hide_banner {0} -c:v hevc_rkmpp -profile:v high {1}",
|
"preset-rk-h265": "ffmpeg -hide_banner {0} -c:v hevc_rkmpp -profile:v high {1}",
|
||||||
"default": "ffmpeg -hide_banner {0} -c:v libx264 -g 50 -profile:v high -level:v 4.1 -preset:v superfast -tune:v zerolatency {1}",
|
"default": "ffmpeg -hide_banner {0} -c:v libx264 -g 50 -profile:v high -level:v 4.1 -preset:v superfast -tune:v zerolatency {1}",
|
||||||
}
|
}
|
||||||
PRESETS_HW_ACCEL_ENCODE_BIRDSEYE[
|
PRESETS_HW_ACCEL_ENCODE_BIRDSEYE["preset-nvidia-h264"] = (
|
||||||
"preset-nvidia-h264"
|
PRESETS_HW_ACCEL_ENCODE_BIRDSEYE[FFMPEG_HWACCEL_NVIDIA]
|
||||||
] = PRESETS_HW_ACCEL_ENCODE_BIRDSEYE[FFMPEG_HWACCEL_NVIDIA]
|
)
|
||||||
PRESETS_HW_ACCEL_ENCODE_BIRDSEYE[
|
PRESETS_HW_ACCEL_ENCODE_BIRDSEYE["preset-nvidia-h265"] = (
|
||||||
"preset-nvidia-h265"
|
PRESETS_HW_ACCEL_ENCODE_BIRDSEYE[FFMPEG_HWACCEL_NVIDIA]
|
||||||
] = PRESETS_HW_ACCEL_ENCODE_BIRDSEYE[FFMPEG_HWACCEL_NVIDIA]
|
)
|
||||||
|
|
||||||
PRESETS_HW_ACCEL_ENCODE_TIMELAPSE = {
|
PRESETS_HW_ACCEL_ENCODE_TIMELAPSE = {
|
||||||
"preset-rpi-64-h264": "ffmpeg -hide_banner {0} -c:v h264_v4l2m2m -pix_fmt yuv420p {1}",
|
"preset-rpi-64-h264": "ffmpeg -hide_banner {0} -c:v h264_v4l2m2m -pix_fmt yuv420p {1}",
|
||||||
@ -136,9 +136,9 @@ PRESETS_HW_ACCEL_ENCODE_TIMELAPSE = {
|
|||||||
"preset-rk-h265": "ffmpeg -hide_banner {0} -c:v hevc_rkmpp -profile:v high {1}",
|
"preset-rk-h265": "ffmpeg -hide_banner {0} -c:v hevc_rkmpp -profile:v high {1}",
|
||||||
"default": "ffmpeg -hide_banner {0} -c:v libx264 -preset:v ultrafast -tune:v zerolatency {1}",
|
"default": "ffmpeg -hide_banner {0} -c:v libx264 -preset:v ultrafast -tune:v zerolatency {1}",
|
||||||
}
|
}
|
||||||
PRESETS_HW_ACCEL_ENCODE_TIMELAPSE[
|
PRESETS_HW_ACCEL_ENCODE_TIMELAPSE["preset-nvidia-h264"] = (
|
||||||
"preset-nvidia-h264"
|
PRESETS_HW_ACCEL_ENCODE_TIMELAPSE[FFMPEG_HWACCEL_NVIDIA]
|
||||||
] = PRESETS_HW_ACCEL_ENCODE_TIMELAPSE[FFMPEG_HWACCEL_NVIDIA]
|
)
|
||||||
|
|
||||||
# encoding of previews is only done on CPU due to comparable encode times and better quality from libx264
|
# encoding of previews is only done on CPU due to comparable encode times and better quality from libx264
|
||||||
PRESETS_HW_ACCEL_ENCODE_PREVIEW = {
|
PRESETS_HW_ACCEL_ENCODE_PREVIEW = {
|
||||||
|
@ -917,9 +917,9 @@ def event_snapshot(id):
|
|||||||
else:
|
else:
|
||||||
response.headers["Cache-Control"] = "no-store"
|
response.headers["Cache-Control"] = "no-store"
|
||||||
if download:
|
if download:
|
||||||
response.headers[
|
response.headers["Content-Disposition"] = (
|
||||||
"Content-Disposition"
|
f"attachment; filename=snapshot-{id}.jpg"
|
||||||
] = f"attachment; filename=snapshot-{id}.jpg"
|
)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@ -1106,9 +1106,9 @@ def event_clip(id):
|
|||||||
if download:
|
if download:
|
||||||
response.headers["Content-Disposition"] = "attachment; filename=%s" % file_name
|
response.headers["Content-Disposition"] = "attachment; filename=%s" % file_name
|
||||||
response.headers["Content-Length"] = os.path.getsize(clip_path)
|
response.headers["Content-Length"] = os.path.getsize(clip_path)
|
||||||
response.headers[
|
response.headers["X-Accel-Redirect"] = (
|
||||||
"X-Accel-Redirect"
|
f"/clips/{file_name}" # nginx: https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_ignore_headers
|
||||||
] = f"/clips/{file_name}" # nginx: https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_ignore_headers
|
)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@ -1384,7 +1384,7 @@ def end_event(event_id):
|
|||||||
|
|
||||||
@bp.route("/config")
|
@bp.route("/config")
|
||||||
def config():
|
def config():
|
||||||
config = current_app.frigate_config.dict()
|
config = current_app.frigate_config.model_dump(mode="json", exclude_none=True)
|
||||||
|
|
||||||
# remove the mqtt password
|
# remove the mqtt password
|
||||||
config["mqtt"].pop("password", None)
|
config["mqtt"].pop("password", None)
|
||||||
@ -1404,9 +1404,9 @@ def config():
|
|||||||
config["plus"] = {"enabled": current_app.plus_api.is_active()}
|
config["plus"] = {"enabled": current_app.plus_api.is_active()}
|
||||||
|
|
||||||
for detector, detector_config in config["detectors"].items():
|
for detector, detector_config in config["detectors"].items():
|
||||||
detector_config["model"][
|
detector_config["model"]["labelmap"] = (
|
||||||
"labelmap"
|
current_app.frigate_config.model.merged_labelmap
|
||||||
] = current_app.frigate_config.model.merged_labelmap
|
)
|
||||||
|
|
||||||
return jsonify(config)
|
return jsonify(config)
|
||||||
|
|
||||||
@ -1811,9 +1811,9 @@ def get_recordings_storage_usage():
|
|||||||
|
|
||||||
total_mb = recording_stats["total"]
|
total_mb = recording_stats["total"]
|
||||||
|
|
||||||
camera_usages: dict[
|
camera_usages: dict[str, dict] = (
|
||||||
str, dict
|
current_app.storage_maintainer.calculate_camera_usages()
|
||||||
] = current_app.storage_maintainer.calculate_camera_usages()
|
)
|
||||||
|
|
||||||
for camera_name in camera_usages.keys():
|
for camera_name in camera_usages.keys():
|
||||||
if camera_usages.get(camera_name, {}).get("usage"):
|
if camera_usages.get(camera_name, {}).get("usage"):
|
||||||
@ -2001,9 +2001,9 @@ def recording_clip(camera_name, start_ts, end_ts):
|
|||||||
if download:
|
if download:
|
||||||
response.headers["Content-Disposition"] = "attachment; filename=%s" % file_name
|
response.headers["Content-Disposition"] = "attachment; filename=%s" % file_name
|
||||||
response.headers["Content-Length"] = os.path.getsize(path)
|
response.headers["Content-Length"] = os.path.getsize(path)
|
||||||
response.headers[
|
response.headers["X-Accel-Redirect"] = (
|
||||||
"X-Accel-Redirect"
|
f"/cache/{file_name}" # nginx: https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_ignore_headers
|
||||||
] = f"/cache/{file_name}" # nginx: https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_ignore_headers
|
)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
@ -297,12 +297,12 @@ class PtzAutoTracker:
|
|||||||
self.ptz_metrics[camera][
|
self.ptz_metrics[camera][
|
||||||
"ptz_max_zoom"
|
"ptz_max_zoom"
|
||||||
].value = camera_config.onvif.autotracking.movement_weights[1]
|
].value = camera_config.onvif.autotracking.movement_weights[1]
|
||||||
self.intercept[
|
self.intercept[camera] = (
|
||||||
camera
|
camera_config.onvif.autotracking.movement_weights[2]
|
||||||
] = camera_config.onvif.autotracking.movement_weights[2]
|
)
|
||||||
self.move_coefficients[
|
self.move_coefficients[camera] = (
|
||||||
camera
|
camera_config.onvif.autotracking.movement_weights[3:]
|
||||||
] = camera_config.onvif.autotracking.movement_weights[3:]
|
)
|
||||||
else:
|
else:
|
||||||
camera_config.onvif.autotracking.enabled = False
|
camera_config.onvif.autotracking.enabled = False
|
||||||
self.ptz_metrics[camera]["ptz_autotracker_enabled"].value = False
|
self.ptz_metrics[camera]["ptz_autotracker_enabled"].value = False
|
||||||
@ -603,9 +603,9 @@ class PtzAutoTracker:
|
|||||||
) ** self.zoom_factor[camera]
|
) ** self.zoom_factor[camera]
|
||||||
|
|
||||||
if "original_target_box" not in self.tracked_object_metrics[camera]:
|
if "original_target_box" not in self.tracked_object_metrics[camera]:
|
||||||
self.tracked_object_metrics[camera][
|
self.tracked_object_metrics[camera]["original_target_box"] = (
|
||||||
"original_target_box"
|
self.tracked_object_metrics[camera]["target_box"]
|
||||||
] = self.tracked_object_metrics[camera]["target_box"]
|
)
|
||||||
|
|
||||||
(
|
(
|
||||||
self.tracked_object_metrics[camera]["valid_velocity"],
|
self.tracked_object_metrics[camera]["valid_velocity"],
|
||||||
|
@ -126,9 +126,9 @@ class OnvifController:
|
|||||||
logger.debug(f"Onvif config for {camera_name}: {ptz_config}")
|
logger.debug(f"Onvif config for {camera_name}: {ptz_config}")
|
||||||
|
|
||||||
service_capabilities_request = ptz.create_type("GetServiceCapabilities")
|
service_capabilities_request = ptz.create_type("GetServiceCapabilities")
|
||||||
self.cams[camera_name][
|
self.cams[camera_name]["service_capabilities_request"] = (
|
||||||
"service_capabilities_request"
|
service_capabilities_request
|
||||||
] = service_capabilities_request
|
)
|
||||||
|
|
||||||
fov_space_id = next(
|
fov_space_id = next(
|
||||||
(
|
(
|
||||||
@ -244,9 +244,9 @@ class OnvifController:
|
|||||||
supported_features.append("zoom-r")
|
supported_features.append("zoom-r")
|
||||||
try:
|
try:
|
||||||
# get camera's zoom limits from onvif config
|
# get camera's zoom limits from onvif config
|
||||||
self.cams[camera_name][
|
self.cams[camera_name]["relative_zoom_range"] = (
|
||||||
"relative_zoom_range"
|
ptz_config.Spaces.RelativeZoomTranslationSpace[0]
|
||||||
] = ptz_config.Spaces.RelativeZoomTranslationSpace[0]
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
if (
|
if (
|
||||||
self.config.cameras[camera_name].onvif.autotracking.zooming
|
self.config.cameras[camera_name].onvif.autotracking.zooming
|
||||||
@ -263,9 +263,9 @@ class OnvifController:
|
|||||||
supported_features.append("zoom-a")
|
supported_features.append("zoom-a")
|
||||||
try:
|
try:
|
||||||
# get camera's zoom limits from onvif config
|
# get camera's zoom limits from onvif config
|
||||||
self.cams[camera_name][
|
self.cams[camera_name]["absolute_zoom_range"] = (
|
||||||
"absolute_zoom_range"
|
ptz_config.Spaces.AbsoluteZoomPositionSpace[0]
|
||||||
] = ptz_config.Spaces.AbsoluteZoomPositionSpace[0]
|
)
|
||||||
self.cams[camera_name]["zoom_limits"] = configs.ZoomLimits
|
self.cams[camera_name]["zoom_limits"] = configs.ZoomLimits
|
||||||
except Exception:
|
except Exception:
|
||||||
if self.config.cameras[camera_name].onvif.autotracking.zooming:
|
if self.config.cameras[camera_name].onvif.autotracking.zooming:
|
||||||
@ -282,9 +282,9 @@ class OnvifController:
|
|||||||
and configs.DefaultRelativePanTiltTranslationSpace is not None
|
and configs.DefaultRelativePanTiltTranslationSpace is not None
|
||||||
):
|
):
|
||||||
supported_features.append("pt-r-fov")
|
supported_features.append("pt-r-fov")
|
||||||
self.cams[camera_name][
|
self.cams[camera_name]["relative_fov_range"] = (
|
||||||
"relative_fov_range"
|
ptz_config.Spaces.RelativePanTiltTranslationSpace[fov_space_id]
|
||||||
] = ptz_config.Spaces.RelativePanTiltTranslationSpace[fov_space_id]
|
)
|
||||||
|
|
||||||
self.cams[camera_name]["features"] = supported_features
|
self.cams[camera_name]["features"] = supported_features
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import unittest
|
import unittest
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
@ -70,7 +71,9 @@ class TestConfig(unittest.TestCase):
|
|||||||
assert runtime_config.detectors["cpu"].type == DetectorTypeEnum.cpu
|
assert runtime_config.detectors["cpu"].type == DetectorTypeEnum.cpu
|
||||||
assert runtime_config.detectors["cpu"].model.width == 320
|
assert runtime_config.detectors["cpu"].model.width == 320
|
||||||
|
|
||||||
def test_detector_custom_model_path(self):
|
@patch("frigate.detectors.detector_config.load_labels")
|
||||||
|
def test_detector_custom_model_path(self, mock_labels):
|
||||||
|
mock_labels.return_value = {}
|
||||||
config = {
|
config = {
|
||||||
"detectors": {
|
"detectors": {
|
||||||
"cpu": {
|
"cpu": {
|
||||||
@ -110,7 +113,7 @@ class TestConfig(unittest.TestCase):
|
|||||||
assert runtime_config.detectors["openvino"].model.path == "/etc/hosts"
|
assert runtime_config.detectors["openvino"].model.path == "/etc/hosts"
|
||||||
|
|
||||||
assert runtime_config.model.width == 512
|
assert runtime_config.model.width == 512
|
||||||
assert runtime_config.detectors["cpu"].model.width == 512
|
assert runtime_config.detectors["cpu"].model.width == 320
|
||||||
assert runtime_config.detectors["edgetpu"].model.width == 160
|
assert runtime_config.detectors["edgetpu"].model.width == 160
|
||||||
assert runtime_config.detectors["openvino"].model.width == 512
|
assert runtime_config.detectors["openvino"].model.width == 512
|
||||||
|
|
||||||
|
@ -41,9 +41,9 @@ class TestFfmpegPresets(unittest.TestCase):
|
|||||||
assert self.default_ffmpeg == frigate_config.dict(exclude_unset=True)
|
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"][
|
self.default_ffmpeg["cameras"]["back"]["ffmpeg"]["hwaccel_args"] = (
|
||||||
"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()
|
frigate_config.cameras["back"].create_ffmpeg_cmds()
|
||||||
assert "preset-rpi-64-h264" not in (
|
assert "preset-rpi-64-h264" not in (
|
||||||
@ -54,9 +54,9 @@ class TestFfmpegPresets(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_ffmpeg_hwaccel_not_preset(self):
|
def test_ffmpeg_hwaccel_not_preset(self):
|
||||||
self.default_ffmpeg["cameras"]["back"]["ffmpeg"][
|
self.default_ffmpeg["cameras"]["back"]["ffmpeg"]["hwaccel_args"] = (
|
||||||
"hwaccel_args"
|
"-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()
|
frigate_config.cameras["back"].create_ffmpeg_cmds()
|
||||||
assert "-other-hwaccel args" in (
|
assert "-other-hwaccel args" in (
|
||||||
@ -64,9 +64,9 @@ class TestFfmpegPresets(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_ffmpeg_hwaccel_scale_preset(self):
|
def test_ffmpeg_hwaccel_scale_preset(self):
|
||||||
self.default_ffmpeg["cameras"]["back"]["ffmpeg"][
|
self.default_ffmpeg["cameras"]["back"]["ffmpeg"]["hwaccel_args"] = (
|
||||||
"hwaccel_args"
|
"preset-nvidia-h264"
|
||||||
] = "preset-nvidia-h264"
|
)
|
||||||
self.default_ffmpeg["cameras"]["back"]["detect"] = {
|
self.default_ffmpeg["cameras"]["back"]["detect"] = {
|
||||||
"height": 1920,
|
"height": 1920,
|
||||||
"width": 2560,
|
"width": 2560,
|
||||||
@ -85,9 +85,9 @@ class TestFfmpegPresets(unittest.TestCase):
|
|||||||
def test_default_ffmpeg_input_arg_preset(self):
|
def test_default_ffmpeg_input_arg_preset(self):
|
||||||
frigate_config = FrigateConfig(**self.default_ffmpeg)
|
frigate_config = FrigateConfig(**self.default_ffmpeg)
|
||||||
|
|
||||||
self.default_ffmpeg["cameras"]["back"]["ffmpeg"][
|
self.default_ffmpeg["cameras"]["back"]["ffmpeg"]["input_args"] = (
|
||||||
"input_args"
|
"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_config.cameras["back"].create_ffmpeg_cmds()
|
||||||
frigate_preset_config.cameras["back"].create_ffmpeg_cmds()
|
frigate_preset_config.cameras["back"].create_ffmpeg_cmds()
|
||||||
@ -98,9 +98,9 @@ class TestFfmpegPresets(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_ffmpeg_input_preset(self):
|
def test_ffmpeg_input_preset(self):
|
||||||
self.default_ffmpeg["cameras"]["back"]["ffmpeg"][
|
self.default_ffmpeg["cameras"]["back"]["ffmpeg"]["input_args"] = (
|
||||||
"input_args"
|
"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()
|
frigate_config.cameras["back"].create_ffmpeg_cmds()
|
||||||
assert "preset-rtmp-generic" not in (
|
assert "preset-rtmp-generic" not in (
|
||||||
@ -131,9 +131,9 @@ class TestFfmpegPresets(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_ffmpeg_output_record_preset(self):
|
def test_ffmpeg_output_record_preset(self):
|
||||||
self.default_ffmpeg["cameras"]["back"]["ffmpeg"]["output_args"][
|
self.default_ffmpeg["cameras"]["back"]["ffmpeg"]["output_args"]["record"] = (
|
||||||
"record"
|
"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()
|
frigate_config.cameras["back"].create_ffmpeg_cmds()
|
||||||
assert "preset-record-generic-audio-aac" not in (
|
assert "preset-record-generic-audio-aac" not in (
|
||||||
@ -144,9 +144,9 @@ class TestFfmpegPresets(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_ffmpeg_output_record_not_preset(self):
|
def test_ffmpeg_output_record_not_preset(self):
|
||||||
self.default_ffmpeg["cameras"]["back"]["ffmpeg"]["output_args"][
|
self.default_ffmpeg["cameras"]["back"]["ffmpeg"]["output_args"]["record"] = (
|
||||||
"record"
|
"-some output"
|
||||||
] = "-some output"
|
)
|
||||||
frigate_config = FrigateConfig(**self.default_ffmpeg)
|
frigate_config = FrigateConfig(**self.default_ffmpeg)
|
||||||
frigate_config.cameras["back"].create_ffmpeg_cmds()
|
frigate_config.cameras["back"].create_ffmpeg_cmds()
|
||||||
assert "-some output" in (
|
assert "-some output" in (
|
||||||
|
@ -20,6 +20,7 @@ Some examples (model - class or model name)::
|
|||||||
> migrator.add_default(model, field_name, default)
|
> migrator.add_default(model, field_name, default)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import peewee as pw
|
import peewee as pw
|
||||||
|
|
||||||
SQL = pw.SQL
|
SQL = pw.SQL
|
||||||
|
@ -20,6 +20,7 @@ Some examples (model - class or model name)::
|
|||||||
> migrator.add_default(model, field_name, default)
|
> migrator.add_default(model, field_name, default)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import peewee as pw
|
import peewee as pw
|
||||||
|
|
||||||
SQL = pw.SQL
|
SQL = pw.SQL
|
||||||
|
@ -20,6 +20,7 @@ Some examples (model - class or model name)::
|
|||||||
> migrator.add_default(model, field_name, default)
|
> migrator.add_default(model, field_name, default)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import peewee as pw
|
import peewee as pw
|
||||||
|
|
||||||
SQL = pw.SQL
|
SQL = pw.SQL
|
||||||
|
@ -20,6 +20,7 @@ Some examples (model - class or model name)::
|
|||||||
> migrator.add_default(model, field_name, default)
|
> migrator.add_default(model, field_name, default)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import peewee as pw
|
import peewee as pw
|
||||||
|
|
||||||
SQL = pw.SQL
|
SQL = pw.SQL
|
||||||
|
@ -20,6 +20,7 @@ Some examples (model - class or model name)::
|
|||||||
> migrator.add_default(model, field_name, default)
|
> migrator.add_default(model, field_name, default)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import peewee as pw
|
import peewee as pw
|
||||||
|
|
||||||
SQL = pw.SQL
|
SQL = pw.SQL
|
||||||
|
@ -20,6 +20,7 @@ Some examples (model - class or model name)::
|
|||||||
> migrator.add_default(model, field_name, default)
|
> migrator.add_default(model, field_name, default)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import peewee as pw
|
import peewee as pw
|
||||||
|
|
||||||
SQL = pw.SQL
|
SQL = pw.SQL
|
||||||
|
Loading…
Reference in New Issue
Block a user