2024-09-28 22:21:42 +03:00
from __future__ import annotations
import json
import logging
import os
2026-02-28 17:04:43 +03:00
from typing import Any , Dict , Optional
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 typing_extensions import Self
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 ,
2026-03-25 22:14:32 +03:00
load_labels ,
2024-09-28 22:21:42 +03:00
)
from frigate . util . config import (
2025-12-26 17:45:03 +03:00
CURRENT_CONFIG_VERSION ,
2024-09-28 22:21:42 +03:00
StreamInfoRetriever ,
2025-02-10 00:48:23 +03:00
convert_area_to_pixels ,
2024-12-12 03:46:42 +03: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 22:14:32 +03: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 18:35:33 +03:00
from . camera . genai import GenAIConfig , GenAIRoleEnum
2026-02-28 17:04:43 +03:00
from . camera . mask import ObjectMaskConfig
2024-09-28 22:21:42 +03:00
from . camera . motion import MotionConfig
2025-02-11 05:47:15 +03:00
from . camera . notification import NotificationConfig
2024-09-28 22:21:42 +03:00
from . camera . objects import FilterConfig , ObjectConfig
2025-05-31 02:01:39 +03: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 18:09:04 +03:00
from . classification import (
2025-05-27 18:26:00 +03:00
AudioTranscriptionConfig ,
2025-01-13 18:09:04 +03:00
ClassificationConfig ,
FaceRecognitionConfig ,
LicensePlateRecognitionConfig ,
SemanticSearchConfig ,
2026-03-08 18:55:00 +03:00
SemanticSearchModelEnum ,
2025-01-13 18:09:04 +03: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-19 02:39:12 +03:00
from . network import NetworkingConfig
Camera profile support (#22482)
* add CameraProfileConfig model for named config overrides
* add profiles field to CameraConfig
* add active_profile field to FrigateConfig
Runtime-only field excluded from YAML serialization, tracks which
profile is currently active.
* add ProfileManager for profile activation and persistence
Handles snapshotting base configs, applying profile overrides via
deep_merge + apply_section_update, publishing ZMQ updates, and
persisting active profile to /config/.active_profile.
* add profile API endpoints (GET /profiles, GET/PUT /profile)
* add MQTT and dispatcher integration for profiles
- Subscribe to frigate/profile/set MQTT topic
- Publish profile/state and profiles/available on connect
- Add _on_profile_command handler to dispatcher
- Broadcast active profile state on WebSocket connect
* wire ProfileManager into app startup and FastAPI
- Create ProfileManager after dispatcher init
- Restore persisted profile on startup
- Pass dispatcher and profile_manager to FastAPI app
* add tests for invalid profile values and keys
Tests that Pydantic rejects: invalid field values (fps: "not_a_number"),
unknown section keys (ffmpeg in profile), invalid nested values, and
invalid profiles in full config parsing.
* formatting
* fix CameraLiveConfig JSON serialization error on profile activation
refactor _publish_updates to only publish ZMQ updates for
sections that actually changed, not all sections on affected cameras.
* consolidate
* add enabled field to camera profiles for enabling/disabling cameras
* add zones support to camera profiles
* add frontend profile types, color utility, and config save support
* add profile state management and save preview support
* add profileName prop to BaseSection for profile-aware config editing
* add profile section dropdown and wire into camera settings pages
* add per-profile camera enable/disable to Camera Management view
* add profiles summary page with card-based layout and fix backend zone comparison bug
* add active profile badge to settings toolbar
* i18n
* add red dot for any pending changes including profiles
* profile support for mask and zone editor
* fix hidden field validation errors caused by lodash wildcard and schema gaps
lodash unset does not support wildcard (*) segments, so hidden fields like
filters.*.mask were never stripped from form data, leaving null raw_coordinates
that fail RJSF anyOf validation. Add unsetWithWildcard helper and also strip
hidden fields from the JSON schema itself as defense-in-depth.
* add face_recognition and lpr to profile-eligible sections
* move profile dropdown from section panes to settings header
* add profiles enable toggle and improve empty state
* formatting
* tweaks
* tweak colors and switch
* fix profile save diff, masksAndZones delete, and config sync
* ui tweaks
* ensure profile manager gets updated config
* rename profile settings to ui settings
* refactor profilesview and add dots/border colors when overridden
* implement an update_config method for profile manager
* fix mask deletion
* more unique colors
* add top-level profiles config section with friendly names
* implement profile friendly names and improve profile UI
- Add ProfileDefinitionConfig type and profiles field to FrigateConfig
- Use ProfilesApiResponse type with friendly_name support throughout
- Replace Record<string, unknown> with proper JsonObject/JsonValue types
- Add profile creation form matching zone pattern (Zod + NameAndIdFields)
- Add pencil icon for renaming profile friendly names in ProfilesView
- Move Profiles menu item to first under Camera Configuration
- Add activity indicators on save/rename/delete buttons
- Display friendly names in CameraManagementView profile selector
- Fix duplicate colored dots in management profile dropdown
- Fix i18n namespace for overridden base config tooltips
- Move profile override deletion from dropdown trash icon to footer
button with confirmation dialog, matching Reset to Global pattern
- Remove Add Profile from section header dropdown to prevent saving
camera overrides before top-level profile definition exists
- Clean up newProfiles state after API profile deletion
- Refresh profiles SWR cache after saving profile definitions
* remove profile badge in settings and add profiles to main menu
* use icon only on mobile
* change color order
* docs
* show activity indicator on trash icon while deleting a profile
* tweak language
* immediately create profiles on backend instead of deferring to Save All
* hide restart-required fields when editing a profile section
fields that require a restart cannot take effect via profile switching,
so they are merged into hiddenFields when profileName is set
* show active profile indicator in desktop status bar
* fix profile config inheritance bug where Pydantic defaults override base values
The /config API was dumping profile overrides with model_dump() which included
all Pydantic defaults. When the frontend merged these over
the camera's base config, explicitly-set base values were
lost. Now profile overrides are re-dumped with exclude_unset=True so only
user-specified fields are returned.
Also fixes the Save All path generating spurious deletion markers for
restart-required fields that are hidden during profile
editing but not excluded from the raw data sanitization in
prepareSectionSavePayload.
* docs tweaks
* docs tweak
* formatting
* formatting
* fix typing
* fix test pollution
test_maintainer was injecting MagicMock() into sys.modules["frigate.config.camera.updater"] at module load time and never restoring it. When the profile tests later imported CameraConfigUpdateEnum and CameraConfigUpdateTopic from that module, they got mock objects instead of the real dataclass/enum, so equality comparisons always failed
* remove
* fix settings showing profile-merged values when editing base config
When a profile is active, the in-memory config contains effective
(profile-merged) values. The settings UI was displaying these merged
values even when the "Base Config" view was selected.
Backend: snapshot pre-profile base configs in ProfileManager and expose
them via a `base_config` key in the /api/config camera response when a
profile is active. The top-level sections continue to reflect the
effective running config.
Frontend: read from `base_config` when available in BaseSection,
useConfigOverride, useAllCameraOverrides, and prepareSectionSavePayload.
Include formData labels in Object/Audio switches widgets so that labels
added only by a profile override remain visible when editing that profile.
* use rasterized_mask as field
makes it easier to exclude from the schema with exclude=True
prevents leaking of the field when using model_dump for profiles
* fix zones
- Fix zone colors not matching across profiles by falling back to base zone color when profile zone data lacks a color field
- Use base_config for base-layer values in masks/zones view so profile-merged values don't pollute the base config editing view
- Handle zones separately in profile manager snapshot/restore since ZoneConfig requires special serialization (color as private attr, contour generation)
- Inherit base zone color and generate contours for profile zone overrides in profile manager
* formatting
* don't require restart for camera enabled change for profiles
* publish camera state when changing profiles
* formatting
* remove available profiles from mqtt
* improve typing
2026-03-19 17:47:57 +03: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 ( )
2025-12-26 17:45:03 +03:00
DEFAULT_CONFIG = f """
2024-09-28 22:21:42 +03:00
mqtt :
enabled : False
2025-12-26 17:45:03 +03:00
cameras : { { } } # No cameras defined, UI wizard should be used
version : { CURRENT_CONFIG_VERSION }
2024-09-28 22:21:42 +03:00
"""
DEFAULT_DETECTORS = { " cpu " : { " type " : " cpu " } }
DEFAULT_DETECT_DIMENSIONS = { " width " : 1280 , " height " : 720 }
# stream info handler
stream_info_retriever = StreamInfoRetriever ( )
class RuntimeMotionConfig ( MotionConfig ) :
2026-02-28 17:04:43 +03:00
""" Runtime version of MotionConfig with rasterized masks. """
Camera profile support (#22482)
* add CameraProfileConfig model for named config overrides
* add profiles field to CameraConfig
* add active_profile field to FrigateConfig
Runtime-only field excluded from YAML serialization, tracks which
profile is currently active.
* add ProfileManager for profile activation and persistence
Handles snapshotting base configs, applying profile overrides via
deep_merge + apply_section_update, publishing ZMQ updates, and
persisting active profile to /config/.active_profile.
* add profile API endpoints (GET /profiles, GET/PUT /profile)
* add MQTT and dispatcher integration for profiles
- Subscribe to frigate/profile/set MQTT topic
- Publish profile/state and profiles/available on connect
- Add _on_profile_command handler to dispatcher
- Broadcast active profile state on WebSocket connect
* wire ProfileManager into app startup and FastAPI
- Create ProfileManager after dispatcher init
- Restore persisted profile on startup
- Pass dispatcher and profile_manager to FastAPI app
* add tests for invalid profile values and keys
Tests that Pydantic rejects: invalid field values (fps: "not_a_number"),
unknown section keys (ffmpeg in profile), invalid nested values, and
invalid profiles in full config parsing.
* formatting
* fix CameraLiveConfig JSON serialization error on profile activation
refactor _publish_updates to only publish ZMQ updates for
sections that actually changed, not all sections on affected cameras.
* consolidate
* add enabled field to camera profiles for enabling/disabling cameras
* add zones support to camera profiles
* add frontend profile types, color utility, and config save support
* add profile state management and save preview support
* add profileName prop to BaseSection for profile-aware config editing
* add profile section dropdown and wire into camera settings pages
* add per-profile camera enable/disable to Camera Management view
* add profiles summary page with card-based layout and fix backend zone comparison bug
* add active profile badge to settings toolbar
* i18n
* add red dot for any pending changes including profiles
* profile support for mask and zone editor
* fix hidden field validation errors caused by lodash wildcard and schema gaps
lodash unset does not support wildcard (*) segments, so hidden fields like
filters.*.mask were never stripped from form data, leaving null raw_coordinates
that fail RJSF anyOf validation. Add unsetWithWildcard helper and also strip
hidden fields from the JSON schema itself as defense-in-depth.
* add face_recognition and lpr to profile-eligible sections
* move profile dropdown from section panes to settings header
* add profiles enable toggle and improve empty state
* formatting
* tweaks
* tweak colors and switch
* fix profile save diff, masksAndZones delete, and config sync
* ui tweaks
* ensure profile manager gets updated config
* rename profile settings to ui settings
* refactor profilesview and add dots/border colors when overridden
* implement an update_config method for profile manager
* fix mask deletion
* more unique colors
* add top-level profiles config section with friendly names
* implement profile friendly names and improve profile UI
- Add ProfileDefinitionConfig type and profiles field to FrigateConfig
- Use ProfilesApiResponse type with friendly_name support throughout
- Replace Record<string, unknown> with proper JsonObject/JsonValue types
- Add profile creation form matching zone pattern (Zod + NameAndIdFields)
- Add pencil icon for renaming profile friendly names in ProfilesView
- Move Profiles menu item to first under Camera Configuration
- Add activity indicators on save/rename/delete buttons
- Display friendly names in CameraManagementView profile selector
- Fix duplicate colored dots in management profile dropdown
- Fix i18n namespace for overridden base config tooltips
- Move profile override deletion from dropdown trash icon to footer
button with confirmation dialog, matching Reset to Global pattern
- Remove Add Profile from section header dropdown to prevent saving
camera overrides before top-level profile definition exists
- Clean up newProfiles state after API profile deletion
- Refresh profiles SWR cache after saving profile definitions
* remove profile badge in settings and add profiles to main menu
* use icon only on mobile
* change color order
* docs
* show activity indicator on trash icon while deleting a profile
* tweak language
* immediately create profiles on backend instead of deferring to Save All
* hide restart-required fields when editing a profile section
fields that require a restart cannot take effect via profile switching,
so they are merged into hiddenFields when profileName is set
* show active profile indicator in desktop status bar
* fix profile config inheritance bug where Pydantic defaults override base values
The /config API was dumping profile overrides with model_dump() which included
all Pydantic defaults. When the frontend merged these over
the camera's base config, explicitly-set base values were
lost. Now profile overrides are re-dumped with exclude_unset=True so only
user-specified fields are returned.
Also fixes the Save All path generating spurious deletion markers for
restart-required fields that are hidden during profile
editing but not excluded from the raw data sanitization in
prepareSectionSavePayload.
* docs tweaks
* docs tweak
* formatting
* formatting
* fix typing
* fix test pollution
test_maintainer was injecting MagicMock() into sys.modules["frigate.config.camera.updater"] at module load time and never restoring it. When the profile tests later imported CameraConfigUpdateEnum and CameraConfigUpdateTopic from that module, they got mock objects instead of the real dataclass/enum, so equality comparisons always failed
* remove
* fix settings showing profile-merged values when editing base config
When a profile is active, the in-memory config contains effective
(profile-merged) values. The settings UI was displaying these merged
values even when the "Base Config" view was selected.
Backend: snapshot pre-profile base configs in ProfileManager and expose
them via a `base_config` key in the /api/config camera response when a
profile is active. The top-level sections continue to reflect the
effective running config.
Frontend: read from `base_config` when available in BaseSection,
useConfigOverride, useAllCameraOverrides, and prepareSectionSavePayload.
Include formData labels in Object/Audio switches widgets so that labels
added only by a profile override remain visible when editing that profile.
* use rasterized_mask as field
makes it easier to exclude from the schema with exclude=True
prevents leaking of the field when using model_dump for profiles
* fix zones
- Fix zone colors not matching across profiles by falling back to base zone color when profile zone data lacks a color field
- Use base_config for base-layer values in masks/zones view so profile-merged values don't pollute the base config editing view
- Handle zones separately in profile manager snapshot/restore since ZoneConfig requires special serialization (color as private attr, contour generation)
- Inherit base zone color and generate contours for profile zone overrides in profile manager
* formatting
* don't require restart for camera enabled change for profiles
* publish camera state when changing profiles
* formatting
* remove available profiles from mqtt
* improve typing
2026-03-19 17:47:57 +03: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 17:04:43 +03: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
super ( ) . __init__ ( * * config )
2024-09-28 22:21:42 +03:00
2026-02-28 17:04:43 +03: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 )
2024-09-28 22:21:42 +03:00
else :
empty_mask = np . zeros ( frame_shape , np . uint8 )
empty_mask [ : ] = 255
2026-02-28 17:04:43 +03:00
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 17:04:43 +03:00
""" Runtime version of FilterConfig with rasterized masks. """
Camera profile support (#22482)
* add CameraProfileConfig model for named config overrides
* add profiles field to CameraConfig
* add active_profile field to FrigateConfig
Runtime-only field excluded from YAML serialization, tracks which
profile is currently active.
* add ProfileManager for profile activation and persistence
Handles snapshotting base configs, applying profile overrides via
deep_merge + apply_section_update, publishing ZMQ updates, and
persisting active profile to /config/.active_profile.
* add profile API endpoints (GET /profiles, GET/PUT /profile)
* add MQTT and dispatcher integration for profiles
- Subscribe to frigate/profile/set MQTT topic
- Publish profile/state and profiles/available on connect
- Add _on_profile_command handler to dispatcher
- Broadcast active profile state on WebSocket connect
* wire ProfileManager into app startup and FastAPI
- Create ProfileManager after dispatcher init
- Restore persisted profile on startup
- Pass dispatcher and profile_manager to FastAPI app
* add tests for invalid profile values and keys
Tests that Pydantic rejects: invalid field values (fps: "not_a_number"),
unknown section keys (ffmpeg in profile), invalid nested values, and
invalid profiles in full config parsing.
* formatting
* fix CameraLiveConfig JSON serialization error on profile activation
refactor _publish_updates to only publish ZMQ updates for
sections that actually changed, not all sections on affected cameras.
* consolidate
* add enabled field to camera profiles for enabling/disabling cameras
* add zones support to camera profiles
* add frontend profile types, color utility, and config save support
* add profile state management and save preview support
* add profileName prop to BaseSection for profile-aware config editing
* add profile section dropdown and wire into camera settings pages
* add per-profile camera enable/disable to Camera Management view
* add profiles summary page with card-based layout and fix backend zone comparison bug
* add active profile badge to settings toolbar
* i18n
* add red dot for any pending changes including profiles
* profile support for mask and zone editor
* fix hidden field validation errors caused by lodash wildcard and schema gaps
lodash unset does not support wildcard (*) segments, so hidden fields like
filters.*.mask were never stripped from form data, leaving null raw_coordinates
that fail RJSF anyOf validation. Add unsetWithWildcard helper and also strip
hidden fields from the JSON schema itself as defense-in-depth.
* add face_recognition and lpr to profile-eligible sections
* move profile dropdown from section panes to settings header
* add profiles enable toggle and improve empty state
* formatting
* tweaks
* tweak colors and switch
* fix profile save diff, masksAndZones delete, and config sync
* ui tweaks
* ensure profile manager gets updated config
* rename profile settings to ui settings
* refactor profilesview and add dots/border colors when overridden
* implement an update_config method for profile manager
* fix mask deletion
* more unique colors
* add top-level profiles config section with friendly names
* implement profile friendly names and improve profile UI
- Add ProfileDefinitionConfig type and profiles field to FrigateConfig
- Use ProfilesApiResponse type with friendly_name support throughout
- Replace Record<string, unknown> with proper JsonObject/JsonValue types
- Add profile creation form matching zone pattern (Zod + NameAndIdFields)
- Add pencil icon for renaming profile friendly names in ProfilesView
- Move Profiles menu item to first under Camera Configuration
- Add activity indicators on save/rename/delete buttons
- Display friendly names in CameraManagementView profile selector
- Fix duplicate colored dots in management profile dropdown
- Fix i18n namespace for overridden base config tooltips
- Move profile override deletion from dropdown trash icon to footer
button with confirmation dialog, matching Reset to Global pattern
- Remove Add Profile from section header dropdown to prevent saving
camera overrides before top-level profile definition exists
- Clean up newProfiles state after API profile deletion
- Refresh profiles SWR cache after saving profile definitions
* remove profile badge in settings and add profiles to main menu
* use icon only on mobile
* change color order
* docs
* show activity indicator on trash icon while deleting a profile
* tweak language
* immediately create profiles on backend instead of deferring to Save All
* hide restart-required fields when editing a profile section
fields that require a restart cannot take effect via profile switching,
so they are merged into hiddenFields when profileName is set
* show active profile indicator in desktop status bar
* fix profile config inheritance bug where Pydantic defaults override base values
The /config API was dumping profile overrides with model_dump() which included
all Pydantic defaults. When the frontend merged these over
the camera's base config, explicitly-set base values were
lost. Now profile overrides are re-dumped with exclude_unset=True so only
user-specified fields are returned.
Also fixes the Save All path generating spurious deletion markers for
restart-required fields that are hidden during profile
editing but not excluded from the raw data sanitization in
prepareSectionSavePayload.
* docs tweaks
* docs tweak
* formatting
* formatting
* fix typing
* fix test pollution
test_maintainer was injecting MagicMock() into sys.modules["frigate.config.camera.updater"] at module load time and never restoring it. When the profile tests later imported CameraConfigUpdateEnum and CameraConfigUpdateTopic from that module, they got mock objects instead of the real dataclass/enum, so equality comparisons always failed
* remove
* fix settings showing profile-merged values when editing base config
When a profile is active, the in-memory config contains effective
(profile-merged) values. The settings UI was displaying these merged
values even when the "Base Config" view was selected.
Backend: snapshot pre-profile base configs in ProfileManager and expose
them via a `base_config` key in the /api/config camera response when a
profile is active. The top-level sections continue to reflect the
effective running config.
Frontend: read from `base_config` when available in BaseSection,
useConfigOverride, useAllCameraOverrides, and prepareSectionSavePayload.
Include formData labels in Object/Audio switches widgets so that labels
added only by a profile override remain visible when editing that profile.
* use rasterized_mask as field
makes it easier to exclude from the schema with exclude=True
prevents leaking of the field when using model_dump for profiles
* fix zones
- Fix zone colors not matching across profiles by falling back to base zone color when profile zone data lacks a color field
- Use base_config for base-layer values in masks/zones view so profile-merged values don't pollute the base config editing view
- Handle zones separately in profile manager snapshot/restore since ZoneConfig requires special serialization (color as private attr, contour generation)
- Inherit base zone color and generate contours for profile zone overrides in profile manager
* formatting
* don't require restart for camera enabled change for profiles
* publish camera state when changing profiles
* formatting
* remove available profiles from mqtt
* improve typing
2026-03-19 17:47:57 +03:00
rasterized_mask : Optional [ 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 17:04:43 +03: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-10 00:48:23 +03: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 17:04:43 +03: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 19:42:35 +03: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 19:42:35 +03: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-12-01 03:21:50 +03:00
raise ValueError (
f " Camera { camera_config . name } has no segment_time in \
recording output args , segment args are required for record . "
)
2024-09-28 22:21:42 +03:00
if int ( record_args [ seg_arg_index + 1 ] ) > 60 :
2024-12-01 03:21:50 +03: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. "
)
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 14:49:14 +03: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 18:38:14 +03: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
2025-03-27 14:49:14 +03: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-02-27 18:55:36 +03:00
version : Optional [ str ] = Field (
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 19:47:15 +03:00
safe_mode : bool = Field (
2026-02-27 18:55:36 +03:00
default = False ,
title = " Safe mode " ,
description = " When enabled, start Frigate in safe mode with reduced features for troubleshooting. " ,
2025-05-24 19:47:15 +03: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 18:55:36 +03: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 18:55:36 +03: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 18:55:36 +03: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 18:55:36 +03: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 18:55:36 +03: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 18:55:36 +03: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-19 02:39:12 +03:00
networking : NetworkingConfig = Field (
2026-02-27 18:55:36 +03:00
default_factory = NetworkingConfig ,
title = " Networking " ,
description = " Network-related settings such as IPv6 enablement for Frigate endpoints. " ,
2025-08-19 02:39:12 +03:00
)
2024-09-28 22:21:42 +03:00
proxy : ProxyConfig = Field (
2026-02-27 18:55:36 +03: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 18:55:36 +03: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
detectors : Dict [ str , BaseDetectorConfig ] = Field (
default = DEFAULT_DETECTORS ,
2026-02-27 18:55:36 +03: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 18:55:36 +03: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 18:35:33 +03:00
# GenAI config (named provider configs: name -> GenAIConfig)
genai : Dict [ str , GenAIConfig ] = Field (
2026-02-27 18:55:36 +03:00
default_factory = dict ,
2026-03-20 16:24:34 +03:00
title = " Generative AI configuration " ,
2026-02-27 18:55:36 +03:00
description = " Settings for integrated generative AI providers used to generate object descriptions and review summaries. " ,
2025-08-09 01:33:11 +03:00
)
2024-09-28 22:21:42 +03:00
# Camera config
2026-02-27 18:55:36 +03:00
cameras : Dict [ str , CameraConfig ] = Field ( title = " Cameras " , description = " Cameras " )
2024-09-28 22:21:42 +03:00
audio : AudioConfig = Field (
2026-02-27 18:55:36 +03:00
default_factory = AudioConfig ,
title = " Audio events " ,
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 18:55:36 +03: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 18:55:36 +03: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 18:55:36 +03: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 18:55:36 +03:00
default_factory = CameraLiveConfig ,
title = " Live playback " ,
2026-03-25 22:14:32 +03: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
)
motion : Optional [ MotionConfig ] = Field (
2026-02-27 18:55:36 +03: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 18:55:36 +03: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 18:55:36 +03: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 18:55:36 +03: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 18:55:36 +03:00
default_factory = SnapshotsConfig ,
title = " Snapshots " ,
2026-03-22 22:33:04 +03: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 18:55:36 +03: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 18:02:40 +03:00
# Classification Config
2025-05-27 18:26:00 +03:00
audio_transcription : AudioTranscriptionConfig = Field (
2026-02-27 18:55:36 +03:00
default_factory = AudioTranscriptionConfig ,
title = " Audio transcription " ,
description = " Settings for live and speech audio transcription used for events and live captions. " ,
2025-05-27 18:26:00 +03:00
)
2025-03-19 18:02:40 +03:00
classification : ClassificationConfig = Field (
2026-02-27 18:55:36 +03:00
default_factory = ClassificationConfig ,
title = " Object classification " ,
description = " Settings for classification models used to refine object labels or state classification. " ,
2025-03-19 18:02:40 +03:00
)
semantic_search : SemanticSearchConfig = Field (
2026-02-27 18:55:36 +03: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 18:02:40 +03:00
)
face_recognition : FaceRecognitionConfig = Field (
2026-02-27 18:55:36 +03: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 18:02:40 +03:00
)
lpr : LicensePlateRecognitionConfig = Field (
default_factory = LicensePlateRecognitionConfig ,
2026-02-27 18:55:36 +03:00
title = " License Plate Recognition " ,
description = " License plate recognition settings including detection thresholds, formatting, and known plates. " ,
2025-03-19 18:02:40 +03:00
)
2024-09-28 22:21:42 +03:00
camera_groups : Dict [ str , CameraGroupConfig ] = Field (
2026-02-27 18:55:36 +03: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
)
Camera profile support (#22482)
* add CameraProfileConfig model for named config overrides
* add profiles field to CameraConfig
* add active_profile field to FrigateConfig
Runtime-only field excluded from YAML serialization, tracks which
profile is currently active.
* add ProfileManager for profile activation and persistence
Handles snapshotting base configs, applying profile overrides via
deep_merge + apply_section_update, publishing ZMQ updates, and
persisting active profile to /config/.active_profile.
* add profile API endpoints (GET /profiles, GET/PUT /profile)
* add MQTT and dispatcher integration for profiles
- Subscribe to frigate/profile/set MQTT topic
- Publish profile/state and profiles/available on connect
- Add _on_profile_command handler to dispatcher
- Broadcast active profile state on WebSocket connect
* wire ProfileManager into app startup and FastAPI
- Create ProfileManager after dispatcher init
- Restore persisted profile on startup
- Pass dispatcher and profile_manager to FastAPI app
* add tests for invalid profile values and keys
Tests that Pydantic rejects: invalid field values (fps: "not_a_number"),
unknown section keys (ffmpeg in profile), invalid nested values, and
invalid profiles in full config parsing.
* formatting
* fix CameraLiveConfig JSON serialization error on profile activation
refactor _publish_updates to only publish ZMQ updates for
sections that actually changed, not all sections on affected cameras.
* consolidate
* add enabled field to camera profiles for enabling/disabling cameras
* add zones support to camera profiles
* add frontend profile types, color utility, and config save support
* add profile state management and save preview support
* add profileName prop to BaseSection for profile-aware config editing
* add profile section dropdown and wire into camera settings pages
* add per-profile camera enable/disable to Camera Management view
* add profiles summary page with card-based layout and fix backend zone comparison bug
* add active profile badge to settings toolbar
* i18n
* add red dot for any pending changes including profiles
* profile support for mask and zone editor
* fix hidden field validation errors caused by lodash wildcard and schema gaps
lodash unset does not support wildcard (*) segments, so hidden fields like
filters.*.mask were never stripped from form data, leaving null raw_coordinates
that fail RJSF anyOf validation. Add unsetWithWildcard helper and also strip
hidden fields from the JSON schema itself as defense-in-depth.
* add face_recognition and lpr to profile-eligible sections
* move profile dropdown from section panes to settings header
* add profiles enable toggle and improve empty state
* formatting
* tweaks
* tweak colors and switch
* fix profile save diff, masksAndZones delete, and config sync
* ui tweaks
* ensure profile manager gets updated config
* rename profile settings to ui settings
* refactor profilesview and add dots/border colors when overridden
* implement an update_config method for profile manager
* fix mask deletion
* more unique colors
* add top-level profiles config section with friendly names
* implement profile friendly names and improve profile UI
- Add ProfileDefinitionConfig type and profiles field to FrigateConfig
- Use ProfilesApiResponse type with friendly_name support throughout
- Replace Record<string, unknown> with proper JsonObject/JsonValue types
- Add profile creation form matching zone pattern (Zod + NameAndIdFields)
- Add pencil icon for renaming profile friendly names in ProfilesView
- Move Profiles menu item to first under Camera Configuration
- Add activity indicators on save/rename/delete buttons
- Display friendly names in CameraManagementView profile selector
- Fix duplicate colored dots in management profile dropdown
- Fix i18n namespace for overridden base config tooltips
- Move profile override deletion from dropdown trash icon to footer
button with confirmation dialog, matching Reset to Global pattern
- Remove Add Profile from section header dropdown to prevent saving
camera overrides before top-level profile definition exists
- Clean up newProfiles state after API profile deletion
- Refresh profiles SWR cache after saving profile definitions
* remove profile badge in settings and add profiles to main menu
* use icon only on mobile
* change color order
* docs
* show activity indicator on trash icon while deleting a profile
* tweak language
* immediately create profiles on backend instead of deferring to Save All
* hide restart-required fields when editing a profile section
fields that require a restart cannot take effect via profile switching,
so they are merged into hiddenFields when profileName is set
* show active profile indicator in desktop status bar
* fix profile config inheritance bug where Pydantic defaults override base values
The /config API was dumping profile overrides with model_dump() which included
all Pydantic defaults. When the frontend merged these over
the camera's base config, explicitly-set base values were
lost. Now profile overrides are re-dumped with exclude_unset=True so only
user-specified fields are returned.
Also fixes the Save All path generating spurious deletion markers for
restart-required fields that are hidden during profile
editing but not excluded from the raw data sanitization in
prepareSectionSavePayload.
* docs tweaks
* docs tweak
* formatting
* formatting
* fix typing
* fix test pollution
test_maintainer was injecting MagicMock() into sys.modules["frigate.config.camera.updater"] at module load time and never restoring it. When the profile tests later imported CameraConfigUpdateEnum and CameraConfigUpdateTopic from that module, they got mock objects instead of the real dataclass/enum, so equality comparisons always failed
* remove
* fix settings showing profile-merged values when editing base config
When a profile is active, the in-memory config contains effective
(profile-merged) values. The settings UI was displaying these merged
values even when the "Base Config" view was selected.
Backend: snapshot pre-profile base configs in ProfileManager and expose
them via a `base_config` key in the /api/config camera response when a
profile is active. The top-level sections continue to reflect the
effective running config.
Frontend: read from `base_config` when available in BaseSection,
useConfigOverride, useAllCameraOverrides, and prepareSectionSavePayload.
Include formData labels in Object/Audio switches widgets so that labels
added only by a profile override remain visible when editing that profile.
* use rasterized_mask as field
makes it easier to exclude from the schema with exclude=True
prevents leaking of the field when using model_dump for profiles
* fix zones
- Fix zone colors not matching across profiles by falling back to base zone color when profile zone data lacks a color field
- Use base_config for base-layer values in masks/zones view so profile-merged values don't pollute the base config editing view
- Handle zones separately in profile manager snapshot/restore since ZoneConfig requires special serialization (color as private attr, contour generation)
- Inherit base zone color and generate contours for profile zone overrides in profile manager
* formatting
* don't require restart for camera enabled change for profiles
* publish camera state when changing profiles
* formatting
* remove available profiles from mqtt
* improve typing
2026-03-19 17:47:57 +03:00
profiles : Dict [ str , ProfileDefinitionConfig ] = Field (
default_factory = dict ,
title = " Profiles " ,
description = " Named profile definitions with friendly names. Camera profiles must reference names defined here. " ,
)
active_profile : Optional [ str ] = Field (
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 18:35:33 +03: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 18:55:00 +03: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 :
if not self . objects . filters . get ( attribute ) :
self . objects . filters [ attribute ] = FilterConfig ( min_score = 0.7 )
elif self . objects . filters [ attribute ] . min_score == 0.5 :
self . objects . filters [ attribute ] . min_score = 0.7
# auto detect hwaccel args
if self . ffmpeg . hwaccel_args == " auto " :
self . ffmpeg . hwaccel_args = auto_detect_hwaccel ( )
2026-03-26 21:47:24 +03:00
# Populate global audio filters for all audio labels
all_audio_labels = {
label
for label in load_labels ( " /audio-labelmap.txt " , prefill = 521 ) . values ( )
if label
}
if self . audio . filters is None :
self . audio . filters = { }
for key in sorted ( all_audio_labels - self . audio . filters . keys ( ) ) :
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 18:26:00 +03:00
" audio_transcription " : . . . ,
2024-09-28 22:21:42 +03:00
" birdseye " : . . . ,
2025-03-20 05:52:55 +03:00
" face_recognition " : . . . ,
" lpr " : . . . ,
2024-09-28 22:21:42 +03:00
" record " : . . . ,
" snapshots " : . . . ,
" live " : . . . ,
" objects " : . . . ,
" review " : . . . ,
" motion " : . . . ,
2025-02-11 05:47:15 +03:00
" notifications " : . . . ,
2024-09-28 22:21:42 +03:00
" detect " : . . . ,
" ffmpeg " : . . . ,
" timestamp_style " : . . . ,
} ,
exclude_unset = True ,
)
2025-05-22 18:38:14 +03: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 18:55:36 +03: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 18:38:14 +03: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 22:41:04 +03:00
if detector_config . type == " cpu " or detector_config . type . endswith (
" _tfl "
) :
2025-05-22 18:38:14 +03:00
model_config [ " path " ] = " /cpu_model.tflite "
elif detector_config . type == " edgetpu " :
model_config [ " path " ] = " /edgetpu_model.tflite "
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 16:30:10 +03: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 18:26:00 +03:00
" audio_transcription " : [ " enabled " , " live_enabled " ] ,
2025-04-24 16:30:10 +03: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 16:30:10 +03: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 19:10:40 +03: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 17:11:18 +03:00
if need_detect_dimensions :
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 16:45:37 +03: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 16:45:37 +03: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 21:47:24 +03: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 18:30:52 +03: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 18:26:00 +03: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-11 05:47:15 +03: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 17:46:25 +03: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-09 01:33:11 +03:00
camera_config . objects . genai . enabled_in_config = (
camera_config . objects . genai . enabled
)
2025-08-10 14:57:54 +03:00
camera_config . review . genai . enabled_in_config = (
2025-08-10 16:38:04 +03:00
camera_config . review . genai . enabled
2025-08-10 14:57:54 +03:00
)
2024-09-28 22:21:42 +03:00
2026-03-25 22:14:32 +03:00
if camera_config . audio . filters is None :
camera_config . audio . filters = { }
2026-03-26 21:47:24 +03:00
for key in sorted ( all_audio_labels - camera_config . audio . filters . keys ( ) ) :
2026-03-25 22:14:32 +03:00
camera_config . audio . filters [ key ] = AudioFilterConfig ( )
2026-03-26 21:47:24 +03: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 17:04:43 +03: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 (
coords , camera_config . frame_shape
)
# 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 17:04:43 +03: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 17:04:43 +03: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 17:04:43 +03: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 17:04:43 +03: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 16:36:27 +03: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 17:04:43 +03: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 19:42:35 +03: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 19:42:35 +03: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 )
verify_autotrack_zones ( camera_config )
verify_motion_and_detect ( camera_config )
2025-05-22 18:38:14 +03:00
verify_objects_track ( camera_config , labelmap_objects )
2025-03-27 14:49:14 +03:00
verify_lpr_and_face ( self , camera_config )
2024-09-28 22:21:42 +03:00
Camera profile support (#22482)
* add CameraProfileConfig model for named config overrides
* add profiles field to CameraConfig
* add active_profile field to FrigateConfig
Runtime-only field excluded from YAML serialization, tracks which
profile is currently active.
* add ProfileManager for profile activation and persistence
Handles snapshotting base configs, applying profile overrides via
deep_merge + apply_section_update, publishing ZMQ updates, and
persisting active profile to /config/.active_profile.
* add profile API endpoints (GET /profiles, GET/PUT /profile)
* add MQTT and dispatcher integration for profiles
- Subscribe to frigate/profile/set MQTT topic
- Publish profile/state and profiles/available on connect
- Add _on_profile_command handler to dispatcher
- Broadcast active profile state on WebSocket connect
* wire ProfileManager into app startup and FastAPI
- Create ProfileManager after dispatcher init
- Restore persisted profile on startup
- Pass dispatcher and profile_manager to FastAPI app
* add tests for invalid profile values and keys
Tests that Pydantic rejects: invalid field values (fps: "not_a_number"),
unknown section keys (ffmpeg in profile), invalid nested values, and
invalid profiles in full config parsing.
* formatting
* fix CameraLiveConfig JSON serialization error on profile activation
refactor _publish_updates to only publish ZMQ updates for
sections that actually changed, not all sections on affected cameras.
* consolidate
* add enabled field to camera profiles for enabling/disabling cameras
* add zones support to camera profiles
* add frontend profile types, color utility, and config save support
* add profile state management and save preview support
* add profileName prop to BaseSection for profile-aware config editing
* add profile section dropdown and wire into camera settings pages
* add per-profile camera enable/disable to Camera Management view
* add profiles summary page with card-based layout and fix backend zone comparison bug
* add active profile badge to settings toolbar
* i18n
* add red dot for any pending changes including profiles
* profile support for mask and zone editor
* fix hidden field validation errors caused by lodash wildcard and schema gaps
lodash unset does not support wildcard (*) segments, so hidden fields like
filters.*.mask were never stripped from form data, leaving null raw_coordinates
that fail RJSF anyOf validation. Add unsetWithWildcard helper and also strip
hidden fields from the JSON schema itself as defense-in-depth.
* add face_recognition and lpr to profile-eligible sections
* move profile dropdown from section panes to settings header
* add profiles enable toggle and improve empty state
* formatting
* tweaks
* tweak colors and switch
* fix profile save diff, masksAndZones delete, and config sync
* ui tweaks
* ensure profile manager gets updated config
* rename profile settings to ui settings
* refactor profilesview and add dots/border colors when overridden
* implement an update_config method for profile manager
* fix mask deletion
* more unique colors
* add top-level profiles config section with friendly names
* implement profile friendly names and improve profile UI
- Add ProfileDefinitionConfig type and profiles field to FrigateConfig
- Use ProfilesApiResponse type with friendly_name support throughout
- Replace Record<string, unknown> with proper JsonObject/JsonValue types
- Add profile creation form matching zone pattern (Zod + NameAndIdFields)
- Add pencil icon for renaming profile friendly names in ProfilesView
- Move Profiles menu item to first under Camera Configuration
- Add activity indicators on save/rename/delete buttons
- Display friendly names in CameraManagementView profile selector
- Fix duplicate colored dots in management profile dropdown
- Fix i18n namespace for overridden base config tooltips
- Move profile override deletion from dropdown trash icon to footer
button with confirmation dialog, matching Reset to Global pattern
- Remove Add Profile from section header dropdown to prevent saving
camera overrides before top-level profile definition exists
- Clean up newProfiles state after API profile deletion
- Refresh profiles SWR cache after saving profile definitions
* remove profile badge in settings and add profiles to main menu
* use icon only on mobile
* change color order
* docs
* show activity indicator on trash icon while deleting a profile
* tweak language
* immediately create profiles on backend instead of deferring to Save All
* hide restart-required fields when editing a profile section
fields that require a restart cannot take effect via profile switching,
so they are merged into hiddenFields when profileName is set
* show active profile indicator in desktop status bar
* fix profile config inheritance bug where Pydantic defaults override base values
The /config API was dumping profile overrides with model_dump() which included
all Pydantic defaults. When the frontend merged these over
the camera's base config, explicitly-set base values were
lost. Now profile overrides are re-dumped with exclude_unset=True so only
user-specified fields are returned.
Also fixes the Save All path generating spurious deletion markers for
restart-required fields that are hidden during profile
editing but not excluded from the raw data sanitization in
prepareSectionSavePayload.
* docs tweaks
* docs tweak
* formatting
* formatting
* fix typing
* fix test pollution
test_maintainer was injecting MagicMock() into sys.modules["frigate.config.camera.updater"] at module load time and never restoring it. When the profile tests later imported CameraConfigUpdateEnum and CameraConfigUpdateTopic from that module, they got mock objects instead of the real dataclass/enum, so equality comparisons always failed
* remove
* fix settings showing profile-merged values when editing base config
When a profile is active, the in-memory config contains effective
(profile-merged) values. The settings UI was displaying these merged
values even when the "Base Config" view was selected.
Backend: snapshot pre-profile base configs in ProfileManager and expose
them via a `base_config` key in the /api/config camera response when a
profile is active. The top-level sections continue to reflect the
effective running config.
Frontend: read from `base_config` when available in BaseSection,
useConfigOverride, useAllCameraOverrides, and prepareSectionSavePayload.
Include formData labels in Object/Audio switches widgets so that labels
added only by a profile override remain visible when editing that profile.
* use rasterized_mask as field
makes it easier to exclude from the schema with exclude=True
prevents leaking of the field when using model_dump for profiles
* fix zones
- Fix zone colors not matching across profiles by falling back to base zone color when profile zone data lacks a color field
- Use base_config for base-layer values in masks/zones view so profile-merged values don't pollute the base config editing view
- Handle zones separately in profile manager snapshot/restore since ZoneConfig requires special serialization (color as private attr, contour generation)
- Inherit base zone color and generate contours for profile zone overrides in profile manager
* formatting
* don't require restart for camera enabled change for profiles
* publish camera state when changing profiles
* formatting
* remove available profiles from mqtt
* improve typing
2026-03-19 17:47:57 +03: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-30 02:51:32 +03:00
# set names on classification configs
for name , config in self . classification . custom . items ( ) :
config . name = name
2024-10-26 21:14:21 +03: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 18:26:00 +03: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 14:19:29 +03: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
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 ( ) ]
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 19:47:15 +03:00
""" Loads the Frigate config file, runs migrations, and creates the config object. """
2024-12-12 03:46:42 +03: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 15:16:56 +03: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-12 03:46:42 +03: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 17:22:20 +03: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 17:45:03 +03: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 19:47:15 +03: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 19:47:15 +03: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-22 00:40:58 +03:00
# copy over database config for auth and so a new db is not created
safe_config [ " database " ] = config . get ( " database " , { } )
2025-05-24 19:47:15 +03: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 (
cls , obj : Any , * , plus_api : Optional [ PlusApi ] = None , install : bool = False
) :
return cls . model_validate (
obj , context = { " plus_api " : plus_api , " install " : install }
)