2024-09-28 22:21:42 +03:00
from __future__ import annotations
2026-05-07 08:53:07 -05:00
import io
2024-09-28 22:21:42 +03:00
import json
import logging
import os
2026-07-06 09:28:02 -08:00
from typing import Any , Self
2024-09-28 22:21:42 +03:00
import numpy as np
from pydantic import (
BaseModel ,
ConfigDict ,
Field ,
TypeAdapter ,
ValidationInfo ,
field_validator ,
model_validator ,
)
from ruamel.yaml import YAML
from frigate.const import REGEX_JSON
from frigate.detectors import DetectorConfig , ModelConfig
from frigate.detectors.detector_config import BaseDetectorConfig
from frigate.plus import PlusApi
from frigate.util.builtin import (
deep_merge ,
get_ffmpeg_arg_list ,
)
from frigate.util.config import (
2025-12-26 07:45:03 -07:00
CURRENT_CONFIG_VERSION ,
2024-09-28 22:21:42 +03:00
StreamInfoRetriever ,
2025-02-09 15:48:23 -06:00
convert_area_to_pixels ,
2024-12-11 18:46:42 -06:00
find_config_file ,
2024-09-28 22:21:42 +03:00
get_relative_coordinates ,
migrate_frigate_config ,
)
from frigate.util.image import create_mask
from frigate.util.services import auto_detect_hwaccel
from .auth import AuthConfig
from .base import FrigateBaseModel
from .camera import CameraConfig , CameraLiveConfig
2026-03-25 14:14:32 -05:00
from .camera.audio import AudioConfig , AudioFilterConfig
2024-09-28 22:21:42 +03:00
from .camera.birdseye import BirdseyeConfig
from .camera.detect import DetectConfig
from .camera.ffmpeg import FfmpegConfig
2026-02-27 08:35:33 -07:00
from .camera.genai import GenAIConfig , GenAIRoleEnum
2026-02-28 08:04:43 -06:00
from .camera.mask import ObjectMaskConfig
2024-09-28 22:21:42 +03:00
from .camera.motion import MotionConfig
2025-02-10 20:47:15 -06:00
from .camera.notification import NotificationConfig
2024-09-28 22:21:42 +03:00
from .camera.objects import FilterConfig , ObjectConfig
2025-05-30 17:01:39 -06:00
from .camera.record import RecordConfig
2024-09-28 22:21:42 +03:00
from .camera.review import ReviewConfig
from .camera.snapshots import SnapshotsConfig
from .camera.timestamp import TimestampStyleConfig
from .camera_group import CameraGroupConfig
2025-01-13 08:09:04 -07:00
from .classification import (
2025-05-27 10:26:00 -05:00
AudioTranscriptionConfig ,
2025-01-13 08:09:04 -07:00
ClassificationConfig ,
FaceRecognitionConfig ,
LicensePlateRecognitionConfig ,
SemanticSearchConfig ,
2026-03-08 09:55:00 -06:00
SemanticSearchModelEnum ,
2025-01-13 08:09:04 -07:00
)
2024-09-28 22:21:42 +03:00
from .database import DatabaseConfig
from .env import EnvVars
from .logger import LoggerConfig
from .mqtt import MqttConfig
2025-08-18 16:39:12 -07:00
from .network import NetworkingConfig
2026-03-19 09:47:57 -05:00
from .profile import ProfileDefinitionConfig
2024-09-28 22:21:42 +03:00
from .proxy import ProxyConfig
from .telemetry import TelemetryConfig
from .tls import TlsConfig
from .ui import UIConfig
__all__ = [ "FrigateConfig" ]
logger = logging . getLogger ( __name__ )
yaml = YAML ()
2026-05-17 12:54:21 -05:00
# Pydantic field default applied when an existing config omits `detectors:`.
# Kept as cpu tflite for backwards compatibility with 0.17 configs.
DEFAULT_DETECTORS = { "cpu" : { "type" : "cpu" }}
# Used by the openvino branch below and rendered into the new-config YAML
# template so first-time setups default to openvino on CPU.
2026-05-07 08:53:07 -05:00
DEFAULT_MODEL = {
"width" : 300 ,
"height" : 300 ,
"input_tensor" : "nhwc" ,
"input_pixel_format" : "bgr" ,
"path" : "/openvino-model/ssdlite_mobilenet_v2.xml" ,
"labelmap_path" : "/openvino-model/coco_91cl_bkgr.txt" ,
}
2026-05-17 12:54:21 -05:00
NEW_CONFIG_DETECTORS = { "ov" : { "type" : "openvino" , "device" : "CPU" }}
2026-05-07 08:53:07 -05:00
DEFAULT_DETECT_DIMENSIONS = { "width" : 1280 , "height" : 720 }
def _render_default_yaml ( data : dict ) -> str :
buf = io . StringIO ()
_yaml_writer = YAML ()
_yaml_writer . indent ( mapping = 2 , sequence = 4 , offset = 2 )
_yaml_writer . dump ( data , buf )
return buf . getvalue ()
2025-12-26 07:45:03 -07:00
DEFAULT_CONFIG = f """
2024-09-28 22:21:42 +03:00
mqtt:
enabled: False
2026-05-17 12:54:21 -05:00
{ _render_default_yaml ({ "detectors" : NEW_CONFIG_DETECTORS , "model" : DEFAULT_MODEL }) }
2025-12-26 07:45:03 -07:00
cameras: {{}} # No cameras defined, UI wizard should be used
version: { CURRENT_CONFIG_VERSION }
2024-09-28 22:21:42 +03:00
"""
# stream info handler
stream_info_retriever = StreamInfoRetriever ()
class RuntimeMotionConfig ( MotionConfig ):
2026-02-28 08:04:43 -06:00
"""Runtime version of MotionConfig with rasterized masks."""
2026-03-19 09:47:57 -05:00
rasterized_mask : np . ndarray = Field ( default = None , exclude = True )
2024-09-28 22:21:42 +03:00
def __init__ ( self , ** config ):
frame_shape = config . get ( "frame_shape" , ( 1 , 1 ))
2026-02-28 08:04:43 -06:00
# Store original mask dict for serialization
original_mask = config . get ( "mask" , {})
if isinstance ( original_mask , dict ):
# Process the new dict format - update raw_coordinates for each mask
processed_mask = {}
for mask_id , mask_config in original_mask . items ():
if isinstance ( mask_config , dict ):
coords = mask_config . get ( "coordinates" , "" )
relative_coords = get_relative_coordinates ( coords , frame_shape )
mask_config_copy = mask_config . copy ()
mask_config_copy [ "raw_coordinates" ] = (
relative_coords if relative_coords else coords
)
mask_config_copy [ "coordinates" ] = (
relative_coords if relative_coords else coords
)
processed_mask [ mask_id ] = mask_config_copy
else :
processed_mask [ mask_id ] = mask_config
config [ "mask" ] = processed_mask
config [ "raw_mask" ] = processed_mask
2024-09-28 22:21:42 +03:00
super () . __init__ ( ** config )
2026-02-28 08:04:43 -06:00
# Rasterize only enabled masks
enabled_coords = []
for mask_config in self . mask . values ():
if mask_config . enabled and mask_config . coordinates :
coords = mask_config . coordinates
if isinstance ( coords , list ):
enabled_coords . extend ( coords )
else :
enabled_coords . append ( coords )
if enabled_coords :
self . rasterized_mask = create_mask ( frame_shape , enabled_coords )
else :
empty_mask = np . zeros ( frame_shape , np . uint8 )
empty_mask [:] = 255
self . rasterized_mask = empty_mask
2024-09-28 22:21:42 +03:00
model_config = ConfigDict ( arbitrary_types_allowed = True , extra = "ignore" )
class RuntimeFilterConfig ( FilterConfig ):
2026-02-28 08:04:43 -06:00
"""Runtime version of FilterConfig with rasterized masks."""
2026-07-06 09:28:02 -08:00
rasterized_mask : np . ndarray | None = Field ( default = None , exclude = True )
2024-09-28 22:21:42 +03:00
def __init__ ( self , ** config ):
frame_shape = config . get ( "frame_shape" , ( 1 , 1 ))
2026-02-28 08:04:43 -06:00
# Store original mask dict for serialization
original_mask = config . get ( "mask" , {})
if isinstance ( original_mask , dict ):
# Process the new dict format - update raw_coordinates for each mask
processed_mask = {}
for mask_id , mask_config in original_mask . items ():
# Handle both dict and ObjectMaskConfig formats
if hasattr ( mask_config , "model_dump" ):
# It's an ObjectMaskConfig object
mask_dict = mask_config . model_dump ()
coords = mask_dict . get ( "coordinates" , "" )
relative_coords = get_relative_coordinates ( coords , frame_shape )
mask_dict [ "raw_coordinates" ] = (
relative_coords if relative_coords else coords
)
mask_dict [ "coordinates" ] = (
relative_coords if relative_coords else coords
)
processed_mask [ mask_id ] = mask_dict
elif isinstance ( mask_config , dict ):
coords = mask_config . get ( "coordinates" , "" )
relative_coords = get_relative_coordinates ( coords , frame_shape )
mask_config_copy = mask_config . copy ()
mask_config_copy [ "raw_coordinates" ] = (
relative_coords if relative_coords else coords
)
mask_config_copy [ "coordinates" ] = (
relative_coords if relative_coords else coords
)
processed_mask [ mask_id ] = mask_config_copy
else :
processed_mask [ mask_id ] = mask_config
config [ "mask" ] = processed_mask
config [ "raw_mask" ] = processed_mask
2024-09-28 22:21:42 +03:00
2025-02-09 15:48:23 -06:00
# Convert min_area and max_area to pixels if they're percentages
if "min_area" in config :
config [ "min_area" ] = convert_area_to_pixels ( config [ "min_area" ], frame_shape )
if "max_area" in config :
config [ "max_area" ] = convert_area_to_pixels ( config [ "max_area" ], frame_shape )
2024-09-28 22:21:42 +03:00
super () . __init__ ( ** config )
2026-02-28 08:04:43 -06:00
# Rasterize only enabled masks
enabled_coords = []
for mask_config in self . mask . values ():
if mask_config . enabled and mask_config . coordinates :
coords = mask_config . coordinates
if isinstance ( coords , list ):
enabled_coords . extend ( coords )
else :
enabled_coords . append ( coords )
if enabled_coords :
self . rasterized_mask = create_mask ( frame_shape , enabled_coords )
else :
self . rasterized_mask = None
2024-09-28 22:21:42 +03:00
model_config = ConfigDict ( arbitrary_types_allowed = True , extra = "ignore" )
class RestreamConfig ( BaseModel ):
model_config = ConfigDict ( extra = "allow" )
def verify_config_roles ( camera_config : CameraConfig ) -> None :
"""Verify that roles are setup in the config correctly."""
assigned_roles = list (
set ([ r for i in camera_config . ffmpeg . inputs for r in i . roles ])
)
if camera_config . record . enabled and "record" not in assigned_roles :
raise ValueError (
f "Camera { camera_config . name } has record enabled, but record is not assigned to an input."
)
if camera_config . audio . enabled and "audio" not in assigned_roles :
raise ValueError (
f "Camera { camera_config . name } has audio events enabled, but audio is not assigned to an input."
)
2025-02-10 10:42:35 -06:00
def verify_valid_live_stream_names (
2024-09-28 22:21:42 +03:00
frigate_config : FrigateConfig , camera_config : CameraConfig
) -> ValueError | None :
"""Verify that a restream exists to use for live view."""
2025-02-10 10:42:35 -06:00
for _ , stream_name in camera_config . live . streams . items ():
if (
stream_name
not in frigate_config . go2rtc . model_dump () . get ( "streams" , {}) . keys ()
):
return ValueError (
f "No restream with name { stream_name } exists for camera { camera_config . name } ."
)
2024-09-28 22:21:42 +03:00
def verify_recording_segments_setup_with_reasonable_time (
camera_config : CameraConfig ,
) -> None :
"""Verify that recording segments are setup and segment time is not greater than 60."""
record_args : list [ str ] = get_ffmpeg_arg_list (
camera_config . ffmpeg . output_args . record
)
if record_args [ 0 ] . startswith ( "preset" ):
return
try :
seg_arg_index = record_args . index ( "-segment_time" )
except ValueError :
2024-11-30 18:21:50 -06:00
raise ValueError (
f "Camera { camera_config . name } has no segment_time in \
recording output args, segment args are required for record."
2026-07-06 09:28:02 -08:00
) from None
2024-09-28 22:21:42 +03:00
if int ( record_args [ seg_arg_index + 1 ]) > 60 :
2024-11-30 18:21:50 -06:00
raise ValueError (
f "Camera { camera_config . name } has invalid segment_time output arg, \
segment_time must be 60 or less."
)
2024-09-28 22:21:42 +03:00
def verify_zone_objects_are_tracked ( camera_config : CameraConfig ) -> None :
"""Verify that user has not entered zone objects that are not in the tracking config."""
for zone_name , zone in camera_config . zones . items ():
for obj in zone . objects :
if obj not in camera_config . objects . track :
raise ValueError (
f "Zone { zone_name } is configured to track { obj } but that object type is not added to objects -> track."
)
def verify_required_zones_exist ( camera_config : CameraConfig ) -> None :
for det_zone in camera_config . review . detections . required_zones :
if det_zone not in camera_config . zones . keys ():
raise ValueError (
f "Camera { camera_config . name } has a required zone for detections { det_zone } that is not defined."
)
for det_zone in camera_config . review . alerts . required_zones :
if det_zone not in camera_config . zones . keys ():
raise ValueError (
f "Camera { camera_config . name } has a required zone for alerts { det_zone } that is not defined."
)
2026-05-25 08:04:00 -05:00
def verify_profile_overrides_match_base ( camera_config : CameraConfig ) -> None :
"""Verify that profile zone and mask IDs reference entries defined on the base camera."""
for profile_name , profile in camera_config . profiles . items ():
if profile . zones :
for zone_name in profile . zones :
if zone_name not in camera_config . zones :
raise ValueError (
f "Camera ' { camera_config . name } ' profile ' { profile_name } ' defines "
f "zone ' { zone_name } ' that does not exist on the base config"
)
if profile . motion and profile . motion . mask :
for mask_name in profile . motion . mask :
if mask_name not in camera_config . motion . mask :
raise ValueError (
f "Camera ' { camera_config . name } ' profile ' { profile_name } ' defines "
f "motion mask ' { mask_name } ' that does not exist on the base config"
)
if profile . objects :
for mask_name in profile . objects . mask or {}:
if mask_name not in ( camera_config . objects . mask or {}):
raise ValueError (
f "Camera ' { camera_config . name } ' profile ' { profile_name } ' defines "
f "object mask ' { mask_name } ' that does not exist on the base config"
)
for label , filter_config in ( profile . objects . filters or {}) . items ():
base_filter = ( camera_config . objects . filters or {}) . get ( label )
profile_filter_masks = (
filter_config . mask if filter_config else None
) or {}
base_filter_masks = ( base_filter . mask if base_filter else None ) or {}
for mask_name in profile_filter_masks :
if mask_name not in base_filter_masks :
raise ValueError (
f "Camera ' { camera_config . name } ' profile ' { profile_name } ' defines "
f "object mask ' { mask_name } ' for ' { label } ' that does not exist "
f "on the base config"
)
2024-09-28 22:21:42 +03:00
def verify_autotrack_zones ( camera_config : CameraConfig ) -> ValueError | None :
"""Verify that required_zones are specified when autotracking is enabled."""
if (
camera_config . onvif . autotracking . enabled
and not camera_config . onvif . autotracking . required_zones
):
raise ValueError (
f "Camera { camera_config . name } has autotracking enabled, required_zones must be set to at least one of the camera's zones."
)
def verify_motion_and_detect ( camera_config : CameraConfig ) -> ValueError | None :
2025-03-27 06:49:14 -05:00
"""Verify that motion detection is not disabled and object detection is enabled."""
2024-09-28 22:21:42 +03:00
if camera_config . detect . enabled and not camera_config . motion . enabled :
raise ValueError (
f "Camera { camera_config . name } has motion detection disabled and object detection enabled but object detection requires motion detection."
)
2025-05-22 10:38:14 -05:00
def verify_objects_track (
camera_config : CameraConfig , enabled_objects : list [ str ]
) -> None :
"""Verify that a user has not specified an object to track that is not in the labelmap."""
valid_objects = [
obj for obj in camera_config . objects . track if obj in enabled_objects
]
if len ( valid_objects ) != len ( camera_config . objects . track ):
invalid_objects = set ( camera_config . objects . track ) - set ( valid_objects )
logger . warning (
f " { camera_config . name } is configured to track { list ( invalid_objects ) } objects, which are not supported by the current model."
)
camera_config . objects . track = valid_objects
2026-07-13 19:55:00 -08:00
for label in invalid_objects :
camera_config . objects . filters . pop ( label , None )
2025-05-22 10:38:14 -05:00
2025-03-27 06:49:14 -05:00
def verify_lpr_and_face (
frigate_config : FrigateConfig , camera_config : CameraConfig
) -> ValueError | None :
"""Verify that lpr and face are enabled at the global level if enabled at the camera level."""
if camera_config . lpr . enabled and not frigate_config . lpr . enabled :
raise ValueError (
f "Camera { camera_config . name } has lpr enabled but lpr is disabled at the global level of the config. You must enable lpr at the global level."
)
if (
camera_config . face_recognition . enabled
and not frigate_config . face_recognition . enabled
):
raise ValueError (
f "Camera { camera_config . name } has face_recognition enabled but face_recognition is disabled at the global level of the config. You must enable face_recognition at the global level."
)
2024-09-28 22:21:42 +03:00
class FrigateConfig ( FrigateBaseModel ):
2026-07-06 09:28:02 -08:00
version : str | None = Field (
2026-02-27 09:55:36 -06:00
default = None ,
title = "Current config version" ,
description = "Numeric or string version of the active configuration to help detect migrations or format changes." ,
)
2025-05-24 10:47:15 -06:00
safe_mode : bool = Field (
2026-02-27 09:55:36 -06:00
default = False ,
title = "Safe mode" ,
description = "When enabled, start Frigate in safe mode with reduced features for troubleshooting." ,
2025-05-24 10:47:15 -06:00
)
2024-09-28 22:21:42 +03:00
# Fields that install global state should be defined first, so that their validators run first.
environment_vars : EnvVars = Field (
2026-02-27 09:55:36 -06:00
default_factory = dict ,
title = "Environment variables" ,
description = "Key/value pairs of environment variables to set for the Frigate process in Home Assistant OS. Non-HAOS users must use Docker environment variable configuration instead." ,
2024-09-28 22:21:42 +03:00
)
logger : LoggerConfig = Field (
2024-10-03 15:33:53 +03:00
default_factory = LoggerConfig ,
2026-02-27 09:55:36 -06:00
title = "Logging" ,
description = "Controls default log verbosity and per-component log level overrides." ,
2024-10-03 15:33:53 +03:00
validate_default = True ,
2024-09-28 22:21:42 +03:00
)
# Global config
2026-02-27 09:55:36 -06:00
auth : AuthConfig = Field (
default_factory = AuthConfig ,
title = "Authentication" ,
description = "Authentication and session-related settings including cookie and rate limit options." ,
)
2024-09-28 22:21:42 +03:00
database : DatabaseConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = DatabaseConfig ,
title = "Database" ,
description = "Settings for the SQLite database used by Frigate to store tracked object and recording metadata." ,
2024-09-28 22:21:42 +03:00
)
go2rtc : RestreamConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = RestreamConfig ,
title = "go2rtc" ,
description = "Settings for the integrated go2rtc restreaming service used for live stream relaying and translation." ,
)
mqtt : MqttConfig = Field (
title = "MQTT" ,
description = "Settings for connecting and publishing telemetry, snapshots, and event details to an MQTT broker." ,
2024-09-28 22:21:42 +03:00
)
notifications : NotificationConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = NotificationConfig ,
title = "Notifications" ,
description = "Settings to enable and control notifications for all cameras; can be overridden per-camera." ,
2024-09-28 22:21:42 +03:00
)
2025-08-18 16:39:12 -07:00
networking : NetworkingConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = NetworkingConfig ,
title = "Networking" ,
description = "Network-related settings such as IPv6 enablement for Frigate endpoints." ,
2025-08-18 16:39:12 -07:00
)
2024-09-28 22:21:42 +03:00
proxy : ProxyConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = ProxyConfig ,
title = "Proxy" ,
description = "Settings for integrating Frigate behind a reverse proxy that passes authenticated user headers." ,
2024-09-28 22:21:42 +03:00
)
telemetry : TelemetryConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = TelemetryConfig ,
title = "Telemetry" ,
description = "System telemetry and stats options including GPU and network bandwidth monitoring." ,
)
tls : TlsConfig = Field (
default_factory = TlsConfig ,
title = "TLS" ,
description = "TLS settings for Frigate's web endpoints (port 8971)." ,
)
ui : UIConfig = Field (
default_factory = UIConfig ,
title = "UI" ,
description = "User interface preferences such as timezone, time/date formatting, and units." ,
2024-09-28 22:21:42 +03:00
)
# Detector config
2026-07-06 09:28:02 -08:00
detectors : dict [ str , BaseDetectorConfig ] = Field (
2024-09-28 22:21:42 +03:00
default = DEFAULT_DETECTORS ,
2026-02-27 09:55:36 -06:00
title = "Detector hardware" ,
description = "Configuration for object detectors (CPU, GPU, ONNX backends) and any detector-specific model settings." ,
2024-09-28 22:21:42 +03:00
)
model : ModelConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = ModelConfig ,
title = "Detection model" ,
description = "Settings to configure a custom object detection model and its input shape." ,
2024-09-28 22:21:42 +03:00
)
2026-02-27 08:35:33 -07:00
# GenAI config (named provider configs: name -> GenAIConfig)
2026-07-06 09:28:02 -08:00
genai : dict [ str , GenAIConfig ] = Field (
2026-02-27 09:55:36 -06:00
default_factory = dict ,
2026-03-20 08:24:34 -05:00
title = "Generative AI configuration" ,
2026-02-27 09:55:36 -06:00
description = "Settings for integrated generative AI providers used to generate object descriptions and review summaries." ,
2025-08-08 16:33:11 -06:00
)
2024-09-28 22:21:42 +03:00
# Camera config
2026-07-06 09:28:02 -08:00
cameras : dict [ str , CameraConfig ] = Field ( title = "Cameras" , description = "Cameras" )
2024-09-28 22:21:42 +03:00
audio : AudioConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = AudioConfig ,
2026-05-07 13:23:02 -05:00
title = "Audio detection" ,
2026-02-27 09:55:36 -06:00
description = "Settings for audio-based event detection for all cameras; can be overridden per-camera." ,
2024-09-28 22:21:42 +03:00
)
birdseye : BirdseyeConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = BirdseyeConfig ,
title = "Birdseye" ,
description = "Settings for the Birdseye composite view that composes multiple camera feeds into a single layout." ,
2024-09-28 22:21:42 +03:00
)
detect : DetectConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = DetectConfig ,
title = "Object Detection" ,
description = "Settings for the detection/detect role used to run object detection and initialize trackers." ,
2024-09-28 22:21:42 +03:00
)
ffmpeg : FfmpegConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = FfmpegConfig ,
title = "FFmpeg" ,
description = "FFmpeg settings including binary path, args, hwaccel options, and per-role output args." ,
2024-09-28 22:21:42 +03:00
)
live : CameraLiveConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = CameraLiveConfig ,
title = "Live playback" ,
2026-03-25 14:14:32 -05:00
description = "Settings to control the jsmpeg live stream resolution and quality. This does not affect restreamed cameras that use go2rtc for live view." ,
2024-09-28 22:21:42 +03:00
)
2026-07-06 09:28:02 -08:00
motion : MotionConfig | None = Field (
2026-02-27 09:55:36 -06:00
default = None ,
title = "Motion detection" ,
description = "Default motion detection settings applied to cameras unless overridden per-camera." ,
2024-09-28 22:21:42 +03:00
)
objects : ObjectConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = ObjectConfig ,
title = "Objects" ,
description = "Object tracking defaults including which labels to track and per-object filters." ,
2024-09-28 22:21:42 +03:00
)
record : RecordConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = RecordConfig ,
title = "Recording" ,
description = "Recording and retention settings applied to cameras unless overridden per-camera." ,
2024-09-28 22:21:42 +03:00
)
review : ReviewConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = ReviewConfig ,
title = "Review" ,
description = "Settings that control alerts, detections, and GenAI review summaries used by the UI and storage." ,
2024-09-28 22:21:42 +03:00
)
snapshots : SnapshotsConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = SnapshotsConfig ,
title = "Snapshots" ,
2026-03-22 19:33:04 +00:00
description = "Settings for API-generated snapshots of tracked objects for all cameras; can be overridden per-camera." ,
2024-09-28 22:21:42 +03:00
)
timestamp_style : TimestampStyleConfig = Field (
default_factory = TimestampStyleConfig ,
2026-02-27 09:55:36 -06:00
title = "Timestamp style" ,
description = "Styling options for in-feed timestamps applied to debug view and snapshots." ,
2024-09-28 22:21:42 +03:00
)
2025-03-19 09:02:40 -06:00
# Classification Config
2025-05-27 10:26:00 -05:00
audio_transcription : AudioTranscriptionConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = AudioTranscriptionConfig ,
title = "Audio transcription" ,
description = "Settings for live and speech audio transcription used for events and live captions." ,
2025-05-27 10:26:00 -05:00
)
2025-03-19 09:02:40 -06:00
classification : ClassificationConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = ClassificationConfig ,
title = "Object classification" ,
description = "Settings for classification models used to refine object labels or state classification." ,
2025-03-19 09:02:40 -06:00
)
semantic_search : SemanticSearchConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = SemanticSearchConfig ,
title = "Semantic Search" ,
description = "Settings for Semantic Search which builds and queries object embeddings to find similar items." ,
2025-03-19 09:02:40 -06:00
)
face_recognition : FaceRecognitionConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = FaceRecognitionConfig ,
title = "Face recognition" ,
description = "Settings for face detection and recognition for all cameras; can be overridden per-camera." ,
2025-03-19 09:02:40 -06:00
)
lpr : LicensePlateRecognitionConfig = Field (
default_factory = LicensePlateRecognitionConfig ,
2026-02-27 09:55:36 -06:00
title = "License Plate Recognition" ,
description = "License plate recognition settings including detection thresholds, formatting, and known plates." ,
2025-03-19 09:02:40 -06:00
)
2026-07-06 09:28:02 -08:00
camera_groups : dict [ str , CameraGroupConfig ] = Field (
2026-02-27 09:55:36 -06:00
default_factory = dict ,
title = "Camera groups" ,
description = "Configuration for named camera groups used to organize cameras in the UI." ,
2024-09-28 22:21:42 +03:00
)
2026-07-06 09:28:02 -08:00
profiles : dict [ str , ProfileDefinitionConfig ] = Field (
2026-03-19 09:47:57 -05:00
default_factory = dict ,
title = "Profiles" ,
description = "Named profile definitions with friendly names. Camera profiles must reference names defined here." ,
)
2026-07-06 09:28:02 -08:00
active_profile : str | None = Field (
2026-03-19 09:47:57 -05:00
default = None ,
title = "Active profile" ,
description = "Currently active profile name. Runtime-only, not persisted in YAML." ,
exclude = True ,
)
2024-09-28 22:21:42 +03:00
_plus_api : PlusApi
@property
def plus_api ( self ) -> PlusApi :
return self . _plus_api
@model_validator ( mode = "after" )
def post_validation ( self , info : ValidationInfo ) -> Self :
# Load plus api from context, if possible.
self . _plus_api = None
if isinstance ( info . context , dict ):
self . _plus_api = info . context . get ( "plus_api" )
# Ensure self._plus_api is set, if no explicit value is provided.
if self . _plus_api is None :
self . _plus_api = PlusApi ()
# set notifications state
self . notifications . enabled_in_config = self . notifications . enabled
2026-02-27 08:35:33 -07:00
# validate genai: each role (tools, vision, embeddings) at most once
role_to_name : dict [ GenAIRoleEnum , str ] = {}
for name , genai_cfg in self . genai . items ():
for role in genai_cfg . roles :
if role in role_to_name :
raise ValueError (
f "GenAI role ' { role . value } ' is assigned to both "
f "' { role_to_name [ role ] } ' and ' { name } '; each role must have "
"exactly one provider."
)
role_to_name [ role ] = name
2026-03-08 09:55:00 -06:00
# validate semantic_search.model when it is a GenAI provider name
if (
self . semantic_search . enabled
and isinstance ( self . semantic_search . model , str )
and not isinstance ( self . semantic_search . model , SemanticSearchModelEnum )
):
if self . semantic_search . model not in self . genai :
raise ValueError (
f "semantic_search.model ' { self . semantic_search . model } ' is not a "
"valid GenAI config key. Must match a key in genai config."
)
genai_cfg = self . genai [ self . semantic_search . model ]
if GenAIRoleEnum . embeddings not in genai_cfg . roles :
raise ValueError (
f "GenAI provider ' { self . semantic_search . model } ' must have "
"'embeddings' in its roles for semantic search."
)
2024-09-28 22:21:42 +03:00
# set default min_score for object attributes
for attribute in self . model . all_attributes :
2026-05-19 09:31:50 -05:00
existing = self . objects . filters . get ( attribute )
if existing is None :
2024-09-28 22:21:42 +03:00
self . objects . filters [ attribute ] = FilterConfig ( min_score = 0.7 )
2026-05-19 09:31:50 -05:00
elif "min_score" not in existing . model_fields_set :
existing . min_score = 0.7
2024-09-28 22:21:42 +03:00
# auto detect hwaccel args
if self . ffmpeg . hwaccel_args == "auto" :
self . ffmpeg . hwaccel_args = auto_detect_hwaccel ()
2026-05-27 10:19:11 -05:00
# Resolve global export hwaccel_args so it matches the per-camera
# resolution below. Without this, every camera reads as overriding
# record.export.hwaccel_args because the global stays "auto" while
# the camera value gets resolved to the actual args list.
if self . record . export . hwaccel_args == "auto" :
self . record . export . hwaccel_args = self . ffmpeg . hwaccel_args
2026-05-15 10:06:38 -05:00
# Populate global audio filters from listen. Existing user-defined
# entries for labels not in listen are preserved but unused at runtime.
2026-03-26 13:47:24 -05:00
if self . audio . filters is None :
self . audio . filters = {}
2026-05-15 10:06:38 -05:00
for key in sorted ( set ( self . audio . listen ) - self . audio . filters . keys ()):
2026-03-26 13:47:24 -05:00
self . audio . filters [ key ] = AudioFilterConfig ()
self . audio . filters = dict ( sorted ( self . audio . filters . items ()))
2024-09-28 22:21:42 +03:00
# Global config to propagate down to camera level
global_config = self . model_dump (
include = {
"audio" : ... ,
2025-05-27 10:26:00 -05:00
"audio_transcription" : ... ,
2024-09-28 22:21:42 +03:00
"birdseye" : ... ,
2025-03-19 20:52:55 -06:00
"face_recognition" : ... ,
"lpr" : ... ,
2024-09-28 22:21:42 +03:00
"record" : ... ,
"snapshots" : ... ,
"live" : ... ,
"objects" : ... ,
"review" : ... ,
"motion" : ... ,
2025-02-10 20:47:15 -06:00
"notifications" : ... ,
2024-09-28 22:21:42 +03:00
"detect" : ... ,
"ffmpeg" : ... ,
"timestamp_style" : ... ,
},
exclude_unset = True ,
)
2025-05-22 10:38:14 -05:00
for key , detector in self . detectors . items ():
adapter = TypeAdapter ( DetectorConfig )
model_dict = (
detector
if isinstance ( detector , dict )
else detector . model_dump ( warnings = "none" )
)
detector_config : BaseDetectorConfig = adapter . validate_python ( model_dict )
# users should not set model themselves
if detector_config . model :
2026-02-27 09:55:36 -06:00
logger . warning (
"The model key should be specified at the root level of the config, not under detectors. The nested model key will be ignored."
)
2025-05-22 10:38:14 -05:00
detector_config . model = None
model_config = self . model . model_dump ( exclude_unset = True , warnings = "none" )
if detector_config . model_path :
model_config [ "path" ] = detector_config . model_path
if "path" not in model_config :
2025-06-06 14:41:04 -05:00
if detector_config . type == "cpu" or detector_config . type . endswith (
"_tfl"
):
2025-05-22 10:38:14 -05:00
model_config [ "path" ] = "/cpu_model.tflite"
elif detector_config . type == "edgetpu" :
model_config [ "path" ] = "/edgetpu_model.tflite"
2026-05-07 08:53:07 -05:00
elif detector_config . type == "openvino" :
for default_key , default_value in DEFAULT_MODEL . items ():
model_config . setdefault ( default_key , default_value )
2025-05-22 10:38:14 -05:00
model = ModelConfig . model_validate ( model_config )
model . check_and_load_plus_model ( self . plus_api , detector_config . type )
model . compute_model_hash ()
labelmap_objects = model . merged_labelmap . values ()
detector_config . model = model
self . detectors [ key ] = detector_config
2024-09-28 22:21:42 +03:00
for name , camera in self . cameras . items ():
2025-04-24 08:30:10 -05:00
modified_global_config = global_config . copy ()
# only populate some fields down to the camera level for specific keys
allowed_fields_map = {
"face_recognition" : [ "enabled" , "min_area" ],
"lpr" : [ "enabled" , "expire_time" , "min_area" , "enhancement" ],
2025-05-27 10:26:00 -05:00
"audio_transcription" : [ "enabled" , "live_enabled" ],
2025-04-24 08:30:10 -05:00
}
for section in allowed_fields_map :
if section in modified_global_config :
modified_global_config [ section ] = {
k : v
for k , v in modified_global_config [ section ] . items ()
if k in allowed_fields_map [ section ]
}
2024-09-28 22:21:42 +03:00
merged_config = deep_merge (
2025-04-24 08:30:10 -05:00
camera . model_dump ( exclude_unset = True ), modified_global_config
2024-09-28 22:21:42 +03:00
)
camera_config : CameraConfig = CameraConfig . model_validate (
{ "name" : name , ** merged_config }
)
if camera_config . ffmpeg . hwaccel_args == "auto" :
camera_config . ffmpeg . hwaccel_args = self . ffmpeg . hwaccel_args
2025-12-22 11:10:40 -05:00
# Resolve export hwaccel_args: camera export -> camera ffmpeg -> global ffmpeg
# This allows per-camera override for exports (e.g., when camera resolution
# exceeds hardware encoder limits)
if camera_config . record . export . hwaccel_args == "auto" :
camera_config . record . export . hwaccel_args = (
camera_config . ffmpeg . hwaccel_args
)
2024-09-28 22:21:42 +03:00
for input in camera_config . ffmpeg . inputs :
need_detect_dimensions = "detect" in input . roles and (
camera_config . detect . height is None
or camera_config . detect . width is None
)
2025-01-03 08:11:18 -06:00
if need_detect_dimensions :
2026-04-18 08:10:50 -05:00
logger . info (
f "detect.width and detect.height not set for { camera_config . name } , probing detect stream to determine resolution."
)
2024-09-28 22:21:42 +03:00
stream_info = { "width" : 0 , "height" : 0 , "fourcc" : None }
try :
stream_info = stream_info_retriever . get_stream_info (
self . ffmpeg , input . path
)
except Exception :
logger . warning (
f "Error detecting stream parameters automatically for { input . path } Applying default values."
)
stream_info = { "width" : 0 , "height" : 0 , "fourcc" : None }
if need_detect_dimensions :
camera_config . detect . width = (
stream_info [ "width" ]
if stream_info . get ( "width" )
else DEFAULT_DETECT_DIMENSIONS [ "width" ]
)
camera_config . detect . height = (
stream_info [ "height" ]
if stream_info . get ( "height" )
else DEFAULT_DETECT_DIMENSIONS [ "height" ]
)
# Warn if detect fps > 10
2025-04-18 08:45:37 -05:00
if camera_config . detect . fps > 10 and camera_config . type != "lpr" :
2024-09-28 22:21:42 +03:00
logger . warning (
f " { camera_config . name } detect fps is set to { camera_config . detect . fps } . This does NOT need to match your camera's frame rate. High values could lead to reduced performance. Recommended value is 5."
)
2025-04-18 08:45:37 -05:00
if camera_config . detect . fps > 15 and camera_config . type == "lpr" :
logger . warning (
f " { camera_config . name } detect fps is set to { camera_config . detect . fps } . This does NOT need to match your camera's frame rate. High values could lead to reduced performance. Recommended value for LPR cameras are between 5-15."
)
2024-09-28 22:21:42 +03:00
# Default min_initialized configuration
2026-03-26 13:47:24 -05:00
min_initialized = max ( int ( camera_config . detect . fps / 2 ), 2 )
2024-09-28 22:21:42 +03:00
if camera_config . detect . min_initialized is None :
camera_config . detect . min_initialized = min_initialized
# Default max_disappeared configuration
max_disappeared = camera_config . detect . fps * 5
if camera_config . detect . max_disappeared is None :
camera_config . detect . max_disappeared = max_disappeared
# Default stationary_threshold configuration
stationary_threshold = camera_config . detect . fps * 10
if camera_config . detect . stationary . threshold is None :
camera_config . detect . stationary . threshold = stationary_threshold
# default to the stationary_threshold if not defined
if camera_config . detect . stationary . interval is None :
camera_config . detect . stationary . interval = stationary_threshold
# set config pre-value
2025-03-03 09:30:52 -06:00
camera_config . enabled_in_config = camera_config . enabled
2024-09-28 22:21:42 +03:00
camera_config . audio . enabled_in_config = camera_config . audio . enabled
2025-05-27 10:26:00 -05:00
camera_config . audio_transcription . enabled_in_config = (
camera_config . audio_transcription . enabled
)
2024-09-28 22:21:42 +03:00
camera_config . record . enabled_in_config = camera_config . record . enabled
2025-02-10 20:47:15 -06:00
camera_config . notifications . enabled_in_config = (
camera_config . notifications . enabled
)
2024-09-28 22:21:42 +03:00
camera_config . onvif . autotracking . enabled_in_config = (
camera_config . onvif . autotracking . enabled
)
2025-02-11 08:46:25 -06:00
camera_config . review . alerts . enabled_in_config = (
camera_config . review . alerts . enabled
)
camera_config . review . detections . enabled_in_config = (
camera_config . review . detections . enabled
)
2025-08-08 16:33:11 -06:00
camera_config . objects . genai . enabled_in_config = (
camera_config . objects . genai . enabled
)
2025-08-10 05:57:54 -06:00
camera_config . review . genai . enabled_in_config = (
2025-08-10 07:38:04 -06:00
camera_config . review . genai . enabled
2025-08-10 05:57:54 -06:00
)
2024-09-28 22:21:42 +03:00
2026-03-25 14:14:32 -05:00
if camera_config . audio . filters is None :
camera_config . audio . filters = {}
2026-05-15 10:06:38 -05:00
for key in sorted (
set ( camera_config . audio . listen ) - camera_config . audio . filters . keys ()
):
2026-03-25 14:14:32 -05:00
camera_config . audio . filters [ key ] = AudioFilterConfig ()
2026-03-26 13:47:24 -05:00
camera_config . audio . filters = dict (
sorted ( camera_config . audio . filters . items ())
)
2024-09-28 22:21:42 +03:00
# Add default filters
object_keys = camera_config . objects . track
if camera_config . objects . filters is None :
camera_config . objects . filters = {}
object_keys = object_keys - camera_config . objects . filters . keys ()
for key in object_keys :
camera_config . objects . filters [ key ] = FilterConfig ()
2026-02-28 08:04:43 -06:00
# Process global object masks to set raw_coordinates
if camera_config . objects . mask :
processed_global_masks = {}
for mask_id , mask_config in camera_config . objects . mask . items ():
if mask_config :
coords = mask_config . coordinates
relative_coords = get_relative_coordinates (
2026-05-10 22:21:44 +02:00
coords ,
camera_config . frame_shape ,
camera_name = camera_config . name ,
2026-02-28 08:04:43 -06:00
)
# Create a new ObjectMaskConfig with raw_coordinates set
processed_global_masks [ mask_id ] = ObjectMaskConfig (
friendly_name = mask_config . friendly_name ,
enabled = mask_config . enabled ,
coordinates = relative_coords if relative_coords else coords ,
raw_coordinates = relative_coords
if relative_coords
else coords ,
enabled_in_config = mask_config . enabled ,
)
else :
processed_global_masks [ mask_id ] = mask_config
camera_config . objects . mask = processed_global_masks
camera_config . objects . raw_mask = processed_global_masks
2024-09-28 22:21:42 +03:00
# Apply global object masks and convert masks to numpy array
for object , filter in camera_config . objects . filters . items ():
2026-02-28 08:04:43 -06:00
# Set enabled_in_config for per-object masks before processing
for mask_config in filter . mask . values ():
if mask_config :
mask_config . enabled_in_config = mask_config . enabled
# Merge global object masks with per-object filter masks
merged_mask = dict ( filter . mask ) # Copy filter-specific masks
# Add global object masks if they exist
2024-09-28 22:21:42 +03:00
if camera_config . objects . mask :
2026-02-28 08:04:43 -06:00
for mask_id , mask_config in camera_config . objects . mask . items ():
# Use a global prefix to avoid key collisions
global_mask_id = f "global_ { mask_id } "
merged_mask [ global_mask_id ] = mask_config
2024-09-28 22:21:42 +03:00
# Set runtime filter to create masks
camera_config . objects . filters [ object ] = RuntimeFilterConfig (
frame_shape = camera_config . frame_shape ,
2026-02-28 08:04:43 -06:00
mask = merged_mask ,
** filter . model_dump (
exclude_unset = True , exclude = { "mask" , "raw_mask" }
),
2024-09-28 22:21:42 +03:00
)
2026-02-28 08:04:43 -06:00
# Set enabled_in_config for motion masks to match config file state BEFORE creating RuntimeMotionConfig
if camera_config . motion :
camera_config . motion . enabled_in_config = camera_config . motion . enabled
for mask_config in camera_config . motion . mask . values ():
if mask_config :
mask_config . enabled_in_config = mask_config . enabled
2024-09-28 22:21:42 +03:00
# Convert motion configuration
if camera_config . motion is None :
camera_config . motion = RuntimeMotionConfig (
frame_shape = camera_config . frame_shape
)
else :
camera_config . motion = RuntimeMotionConfig (
frame_shape = camera_config . frame_shape ,
** camera_config . motion . model_dump ( exclude_unset = True ),
)
# generate zone contours
if len ( camera_config . zones ) > 0 :
for zone in camera_config . zones . values ():
2026-01-18 07:36:27 -06:00
if zone . filters :
for object_name , filter_config in zone . filters . items ():
zone . filters [ object_name ] = RuntimeFilterConfig (
frame_shape = camera_config . frame_shape ,
** filter_config . model_dump ( exclude_unset = True ),
)
2024-09-28 22:21:42 +03:00
zone . generate_contour ( camera_config . frame_shape )
2026-02-28 08:04:43 -06:00
# Set enabled_in_config for zones to match config file state
for zone in camera_config . zones . values ():
zone . enabled_in_config = zone . enabled
2024-09-28 22:21:42 +03:00
# Set live view stream if none is set
2025-02-10 10:42:35 -06:00
if not camera_config . live . streams :
camera_config . live . streams = { name : name }
2024-09-28 22:21:42 +03:00
# generate the ffmpeg commands
camera_config . create_ffmpeg_cmds ()
self . cameras [ name ] = camera_config
verify_config_roles ( camera_config )
2025-02-10 10:42:35 -06:00
verify_valid_live_stream_names ( self , camera_config )
2024-09-28 22:21:42 +03:00
verify_recording_segments_setup_with_reasonable_time ( camera_config )
verify_zone_objects_are_tracked ( camera_config )
verify_required_zones_exist ( camera_config )
2026-05-25 08:04:00 -05:00
verify_profile_overrides_match_base ( camera_config )
2024-09-28 22:21:42 +03:00
verify_autotrack_zones ( camera_config )
verify_motion_and_detect ( camera_config )
2025-05-22 10:38:14 -05:00
verify_objects_track ( camera_config , labelmap_objects )
2025-03-27 06:49:14 -05:00
verify_lpr_and_face ( self , camera_config )
2024-09-28 22:21:42 +03:00
2026-03-19 09:47:57 -05:00
# Validate camera profiles reference top-level profile definitions
for cam_name , cam_config in self . cameras . items ():
for profile_name in cam_config . profiles :
if profile_name not in self . profiles :
raise ValueError (
f "Camera ' { cam_name } ' references profile ' { profile_name } ' "
f "which is not defined in the top-level 'profiles' section"
)
2025-05-29 17:51:32 -06:00
# set names on classification configs
for name , config in self . classification . custom . items ():
config . name = name
2024-10-26 12:14:21 -06:00
self . objects . parse_all_objects ( self . cameras )
self . model . create_colormap ( sorted ( self . objects . all_objects ))
2024-09-28 22:21:42 +03:00
self . model . check_and_load_plus_model ( self . plus_api )
2025-05-27 10:26:00 -05:00
# Check audio transcription and audio detection requirements
if self . audio_transcription . enabled :
# If audio transcription is enabled globally, at least one camera must have audio detection enabled
if not any ( camera . audio . enabled for camera in self . cameras . values ()):
raise ValueError (
"Audio transcription is enabled globally, but no cameras have audio detection enabled. At least one camera must have audio detection enabled."
)
else :
# If audio transcription is disabled globally, check each camera with audio_transcription enabled
for camera in self . cameras . values ():
if camera . audio_transcription . enabled and not camera . audio . enabled :
raise ValueError (
f "Camera { camera . name } has audio transcription enabled, but audio detection is not enabled for this camera. Audio detection must be enabled for cameras with audio transcription when it is disabled globally."
)
2025-09-12 06:19:29 -05:00
# Validate auth roles against cameras
camera_names = set ( self . cameras . keys ())
for role , allowed_cameras in self . auth . roles . items ():
invalid_cameras = [
cam for cam in allowed_cameras if cam not in camera_names
]
if invalid_cameras :
logger . warning (
f "Role ' { role } ' references non-existent cameras: { invalid_cameras } . "
)
2024-09-28 22:21:42 +03:00
return self
@field_validator ( "cameras" )
@classmethod
2026-07-06 09:28:02 -08:00
def ensure_zones_and_cameras_have_different_names ( cls , v : dict [ str , CameraConfig ]):
2024-09-28 22:21:42 +03:00
zones = [ zone for camera in v . values () for zone in camera . zones . keys ()]
for zone in zones :
if zone in v . keys ():
raise ValueError ( "Zones cannot share names with cameras" )
return v
@classmethod
def load ( cls , ** kwargs ):
2025-05-24 10:47:15 -06:00
"""Loads the Frigate config file, runs migrations, and creates the config object."""
2024-12-11 18:46:42 -06:00
config_path = find_config_file ()
2024-09-28 22:21:42 +03:00
# No configuration file found, create one.
new_config = False
2024-10-30 06:16:56 -06:00
if not os . path . isfile ( config_path ):
2024-09-28 22:21:42 +03:00
logger . info ( "No config file found, saving default config" )
2024-12-11 18:46:42 -06:00
config_path = config_path
2024-09-28 22:21:42 +03:00
new_config = True
else :
# Check if the config file needs to be migrated.
migrate_frigate_config ( config_path )
# Finally, load the resulting configuration file.
2024-10-30 08:22:20 -06:00
with open ( config_path , "a+" if new_config else "r" ) as f :
2024-09-28 22:21:42 +03:00
# Only write the default config if the opened file is non-empty. This can happen as
# a race condition. It's extremely unlikely, but eh. Might as well check it.
if new_config and f . tell () == 0 :
f . write ( DEFAULT_CONFIG )
logger . info (
2025-12-26 07:45:03 -07:00
"Created default config file, see the getting started docs for configuration: https://docs.frigate.video/guides/getting_started"
2024-09-28 22:21:42 +03:00
)
f . seek ( 0 )
return FrigateConfig . parse ( f , ** kwargs )
@classmethod
2025-05-24 10:47:15 -06:00
def parse ( cls , config , * , is_json = None , safe_load = False , ** context ):
2024-09-28 22:21:42 +03:00
# If config is a file, read its contents.
if hasattr ( config , "read" ):
fname = getattr ( config , "name" , None )
config = config . read ()
# Try to guess the value of is_json from the file extension.
if is_json is None and fname :
_ , ext = os . path . splitext ( fname )
if ext in ( ".yaml" , ".yml" ):
is_json = False
elif ext == ".json" :
is_json = True
# At this point, try to sniff the config string, to guess if it is json or not.
if is_json is None :
is_json = REGEX_JSON . match ( config ) is not None
# Parse the config into a dictionary.
if is_json :
config = json . load ( config )
else :
config = yaml . load ( config )
2025-05-24 10:47:15 -06:00
# load minimal Frigate config after the full config did not validate
if safe_load :
safe_config = { "safe_mode" : True , "cameras" : {}, "mqtt" : { "enabled" : False }}
# copy over auth and proxy config in case auth needs to be enforced
safe_config [ "auth" ] = config . get ( "auth" , {})
safe_config [ "proxy" ] = config . get ( "proxy" , {})
2025-11-21 14:40:58 -07:00
# copy over database config for auth and so a new db is not created
safe_config [ "database" ] = config . get ( "database" , {})
2025-05-24 10:47:15 -06:00
return cls . parse_object ( safe_config , ** context )
2024-09-28 22:21:42 +03:00
# Validate and return the config dict.
return cls . parse_object ( config , ** context )
@classmethod
def parse_yaml ( cls , config_yaml , ** context ):
return cls . parse ( config_yaml , is_json = False , ** context )
@classmethod
def parse_object (
2026-07-06 09:28:02 -08:00
cls , obj : Any , * , plus_api : PlusApi | None = None , install : bool = False
2024-09-28 22:21:42 +03:00
):
return cls . model_validate (
obj , context = { "plus_api" : plus_api , "install" : install }
)