2024-09-28 22:21:42 +03:00
from __future__ import annotations
UI fixes (#23127)
* hide camera overrides badge from system sections
* show empty card on camera metrics page when no cameras are defined
* fix enabled camera state switch after adding via wizard
Cameras added mid-session have no WS state until the dispatcher publishes camera_activity (which only happens on a fresh onConnect). Fall back to the config's enabled value so the switch reflects reality immediately after the wizard closes.
* guard camera enabled access
console would throw errors after adding via camera wizard
* fix useOptimisticState dropping debounced setState under StrictMode
* use openvino on cpu as default model
- faster than tflite on cpu
- add to default generated config
* use an enum for model_size
the frontend will then render this as a select dropdown because of the changes in the json schema
* i18n
* sync object filter entries with tracked labels in camera config form
Filter sub-collapsibles in the camera Objects section are driven by `filters` dict keys, but profile merges and live track-switch edits don't add matching entries, so newly tracked labels (like from a profile override) had no collapsible. Synthesize default filter entries from `track` in the form data so every tracked label renders a collapsible; baseline data also gets the synthesized entries, so save payloads are unchanged.
* revalidate raw paths cache after config save so CameraPathWidget shows fresh credentials
* fix test
* restore masked ffmpeg credentials when persisting camera config
* formatting
* rebuild ffmpeg commands when enabling recording for the first time
Toggling record.enabled from the config UI updated the in-memory config but left ffmpeg running with its original command, so the record output args were never wired in and nothing landed in the cache for the maintainer to move. The record config update now rebuilds ffmpeg_cmds when enabled_in_config transitions, and the camera watchdog restarts ffmpeg on a false to true transition so the record output gets wired in. MQTT toggles, which only flip record.enabled at runtime, are unaffected and continue to work via the maintainer's drop/keep gate.
* keep record toggle switch in single camera view disabled until enabled in config
* fix override detection for sections unset in the global config
Override badges and the blue dot now compare against schema defaults for sections like motion that the API serializes as null when omitted from the global YAML, instead of treating any populated camera config as an override
* add support for config-aware patterns in section hiddenFields
Section configs can now declare dynamic hidden-field entries as functions of the loaded config; objects.ts uses this to hide auto-populated attribute filters (DHL, face, license_plate, etc.) from the form, save flow, and override popover when those labels aren't user-settable
* siimplify object filters handling
live updating was getting very messy. users will just need to save once they enable a new object in order to see filters for that object
* tweaks
* update docs for new detector default
* make genai provider required and add special case for UI
prevent validation errors from appearing on initial creation of genai provider by setting the first option in the select dropdown as default
2026-05-07 16:53:07 +03:00
import io
2024-09-28 22:21:42 +03:00
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 ,
)
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 ( )
Merge detector and model in settings UI (#23216)
* add embedded mode to BaseSection so parents can host the save action
* add optional action slot to current Frigate+ model summary
* add w-full to action slot flex wrapper for explicit width contract
* i18n
* merged detectors and model settings view
* fix document title
* Embed detector form in merged settings view
* add detection model card with tabs and custom model embed
* add Frigate+ model selector with filter popover to merged page
* Add mismatch banner and gate save on detector and model compatibility
* Wire atomic save, restart toast, and undo on detectors and model page
* Clear child pending data on undo
* route merged detectors and model view in settings
* trim Frigate+ page to account-only and remove old detection model view
* basic e2e
* Fix unsaved-changes guard, custom path leak, and post-failure cache resync
* Rename to Detectors and model, float Modified badge, use ConfigMessageBanner for mismatch
* Hide Plus/Custom tabs when Frigate+ is not enabled
* Detect active Plus model via model.plus.id instead of path prefix
* Sync state back to snapshot when child form un-modifies and remount on undo
* Always require restart on save since model changes also need one
* Wrap Frigate+ model selector in SplitCardRow with label and description
* rename tab
* update docs
* sync top-level model with default detector's resolved model
when the user doesn't define a top-level `model:` block, `FrigateConfig.model` stayed at pydantic field defaults (320×320, /labelmap.txt) while the per-detector model picked up `DEFAULT_MODEL` for openvino on cpu (300×300, coco_91cl_bkgr.txt introduced in #23127), causing `RemoteObjectDetector` to fail with "buffer is too small for requested array" because the SHM was sized from the per-detector model but mapped using the top-level one. After the detector loop, copy the first detector's resolved model up to `self.model` so both sides agree on dimensions and labelmap
* revert to cpu detector by default
use openvino cpu for new configs only
* add defaults
2026-05-17 20:54:21 +03: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.
UI fixes (#23127)
* hide camera overrides badge from system sections
* show empty card on camera metrics page when no cameras are defined
* fix enabled camera state switch after adding via wizard
Cameras added mid-session have no WS state until the dispatcher publishes camera_activity (which only happens on a fresh onConnect). Fall back to the config's enabled value so the switch reflects reality immediately after the wizard closes.
* guard camera enabled access
console would throw errors after adding via camera wizard
* fix useOptimisticState dropping debounced setState under StrictMode
* use openvino on cpu as default model
- faster than tflite on cpu
- add to default generated config
* use an enum for model_size
the frontend will then render this as a select dropdown because of the changes in the json schema
* i18n
* sync object filter entries with tracked labels in camera config form
Filter sub-collapsibles in the camera Objects section are driven by `filters` dict keys, but profile merges and live track-switch edits don't add matching entries, so newly tracked labels (like from a profile override) had no collapsible. Synthesize default filter entries from `track` in the form data so every tracked label renders a collapsible; baseline data also gets the synthesized entries, so save payloads are unchanged.
* revalidate raw paths cache after config save so CameraPathWidget shows fresh credentials
* fix test
* restore masked ffmpeg credentials when persisting camera config
* formatting
* rebuild ffmpeg commands when enabling recording for the first time
Toggling record.enabled from the config UI updated the in-memory config but left ffmpeg running with its original command, so the record output args were never wired in and nothing landed in the cache for the maintainer to move. The record config update now rebuilds ffmpeg_cmds when enabled_in_config transitions, and the camera watchdog restarts ffmpeg on a false to true transition so the record output gets wired in. MQTT toggles, which only flip record.enabled at runtime, are unaffected and continue to work via the maintainer's drop/keep gate.
* keep record toggle switch in single camera view disabled until enabled in config
* fix override detection for sections unset in the global config
Override badges and the blue dot now compare against schema defaults for sections like motion that the API serializes as null when omitted from the global YAML, instead of treating any populated camera config as an override
* add support for config-aware patterns in section hiddenFields
Section configs can now declare dynamic hidden-field entries as functions of the loaded config; objects.ts uses this to hide auto-populated attribute filters (DHL, face, license_plate, etc.) from the form, save flow, and override popover when those labels aren't user-settable
* siimplify object filters handling
live updating was getting very messy. users will just need to save once they enable a new object in order to see filters for that object
* tweaks
* update docs for new detector default
* make genai provider required and add special case for UI
prevent validation errors from appearing on initial creation of genai provider by setting the first option in the select dropdown as default
2026-05-07 16:53:07 +03: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 " ,
}
Merge detector and model in settings UI (#23216)
* add embedded mode to BaseSection so parents can host the save action
* add optional action slot to current Frigate+ model summary
* add w-full to action slot flex wrapper for explicit width contract
* i18n
* merged detectors and model settings view
* fix document title
* Embed detector form in merged settings view
* add detection model card with tabs and custom model embed
* add Frigate+ model selector with filter popover to merged page
* Add mismatch banner and gate save on detector and model compatibility
* Wire atomic save, restart toast, and undo on detectors and model page
* Clear child pending data on undo
* route merged detectors and model view in settings
* trim Frigate+ page to account-only and remove old detection model view
* basic e2e
* Fix unsaved-changes guard, custom path leak, and post-failure cache resync
* Rename to Detectors and model, float Modified badge, use ConfigMessageBanner for mismatch
* Hide Plus/Custom tabs when Frigate+ is not enabled
* Detect active Plus model via model.plus.id instead of path prefix
* Sync state back to snapshot when child form un-modifies and remount on undo
* Always require restart on save since model changes also need one
* Wrap Frigate+ model selector in SplitCardRow with label and description
* rename tab
* update docs
* sync top-level model with default detector's resolved model
when the user doesn't define a top-level `model:` block, `FrigateConfig.model` stayed at pydantic field defaults (320×320, /labelmap.txt) while the per-detector model picked up `DEFAULT_MODEL` for openvino on cpu (300×300, coco_91cl_bkgr.txt introduced in #23127), causing `RemoteObjectDetector` to fail with "buffer is too small for requested array" because the SHM was sized from the per-detector model but mapped using the top-level one. After the detector loop, copy the first detector's resolved model up to `self.model` so both sides agree on dimensions and labelmap
* revert to cpu detector by default
use openvino cpu for new configs only
* add defaults
2026-05-17 20:54:21 +03:00
NEW_CONFIG_DETECTORS = { " ov " : { " type " : " openvino " , " device " : " CPU " } }
UI fixes (#23127)
* hide camera overrides badge from system sections
* show empty card on camera metrics page when no cameras are defined
* fix enabled camera state switch after adding via wizard
Cameras added mid-session have no WS state until the dispatcher publishes camera_activity (which only happens on a fresh onConnect). Fall back to the config's enabled value so the switch reflects reality immediately after the wizard closes.
* guard camera enabled access
console would throw errors after adding via camera wizard
* fix useOptimisticState dropping debounced setState under StrictMode
* use openvino on cpu as default model
- faster than tflite on cpu
- add to default generated config
* use an enum for model_size
the frontend will then render this as a select dropdown because of the changes in the json schema
* i18n
* sync object filter entries with tracked labels in camera config form
Filter sub-collapsibles in the camera Objects section are driven by `filters` dict keys, but profile merges and live track-switch edits don't add matching entries, so newly tracked labels (like from a profile override) had no collapsible. Synthesize default filter entries from `track` in the form data so every tracked label renders a collapsible; baseline data also gets the synthesized entries, so save payloads are unchanged.
* revalidate raw paths cache after config save so CameraPathWidget shows fresh credentials
* fix test
* restore masked ffmpeg credentials when persisting camera config
* formatting
* rebuild ffmpeg commands when enabling recording for the first time
Toggling record.enabled from the config UI updated the in-memory config but left ffmpeg running with its original command, so the record output args were never wired in and nothing landed in the cache for the maintainer to move. The record config update now rebuilds ffmpeg_cmds when enabled_in_config transitions, and the camera watchdog restarts ffmpeg on a false to true transition so the record output gets wired in. MQTT toggles, which only flip record.enabled at runtime, are unaffected and continue to work via the maintainer's drop/keep gate.
* keep record toggle switch in single camera view disabled until enabled in config
* fix override detection for sections unset in the global config
Override badges and the blue dot now compare against schema defaults for sections like motion that the API serializes as null when omitted from the global YAML, instead of treating any populated camera config as an override
* add support for config-aware patterns in section hiddenFields
Section configs can now declare dynamic hidden-field entries as functions of the loaded config; objects.ts uses this to hide auto-populated attribute filters (DHL, face, license_plate, etc.) from the form, save flow, and override popover when those labels aren't user-settable
* siimplify object filters handling
live updating was getting very messy. users will just need to save once they enable a new object in order to see filters for that object
* tweaks
* update docs for new detector default
* make genai provider required and add special case for UI
prevent validation errors from appearing on initial creation of genai provider by setting the first option in the select dropdown as default
2026-05-07 16:53:07 +03: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 17:45:03 +03:00
DEFAULT_CONFIG = f """
2024-09-28 22:21:42 +03:00
mqtt :
enabled : False
Merge detector and model in settings UI (#23216)
* add embedded mode to BaseSection so parents can host the save action
* add optional action slot to current Frigate+ model summary
* add w-full to action slot flex wrapper for explicit width contract
* i18n
* merged detectors and model settings view
* fix document title
* Embed detector form in merged settings view
* add detection model card with tabs and custom model embed
* add Frigate+ model selector with filter popover to merged page
* Add mismatch banner and gate save on detector and model compatibility
* Wire atomic save, restart toast, and undo on detectors and model page
* Clear child pending data on undo
* route merged detectors and model view in settings
* trim Frigate+ page to account-only and remove old detection model view
* basic e2e
* Fix unsaved-changes guard, custom path leak, and post-failure cache resync
* Rename to Detectors and model, float Modified badge, use ConfigMessageBanner for mismatch
* Hide Plus/Custom tabs when Frigate+ is not enabled
* Detect active Plus model via model.plus.id instead of path prefix
* Sync state back to snapshot when child form un-modifies and remount on undo
* Always require restart on save since model changes also need one
* Wrap Frigate+ model selector in SplitCardRow with label and description
* rename tab
* update docs
* sync top-level model with default detector's resolved model
when the user doesn't define a top-level `model:` block, `FrigateConfig.model` stayed at pydantic field defaults (320×320, /labelmap.txt) while the per-detector model picked up `DEFAULT_MODEL` for openvino on cpu (300×300, coco_91cl_bkgr.txt introduced in #23127), causing `RemoteObjectDetector` to fail with "buffer is too small for requested array" because the SHM was sized from the per-detector model but mapped using the top-level one. After the detector loop, copy the first detector's resolved model up to `self.model` so both sides agree on dimensions and labelmap
* revert to cpu detector by default
use openvino cpu for new configs only
* add defaults
2026-05-17 20:54:21 +03:00
{ _render_default_yaml ( { " detectors " : NEW_CONFIG_DETECTORS , " model " : DEFAULT_MODEL } ) }
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
"""
# 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. "
)
2026-05-25 16:04:00 +03: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 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 ,
2026-05-07 21:23:02 +03:00
title = " Audio detection " ,
2026-02-27 18:55:36 +03: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 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 :
2026-05-19 17:31:50 +03: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 17:31:50 +03: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 18:19:11 +03: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 18:06:38 +03: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 21:47:24 +03:00
if self . audio . filters is None :
self . audio . filters = { }
2026-05-15 18:06:38 +03:00
for key in sorted ( set ( self . audio . listen ) - self . audio . filters . keys ( ) ) :
2026-03-26 21:47:24 +03: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 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 "
UI fixes (#23127)
* hide camera overrides badge from system sections
* show empty card on camera metrics page when no cameras are defined
* fix enabled camera state switch after adding via wizard
Cameras added mid-session have no WS state until the dispatcher publishes camera_activity (which only happens on a fresh onConnect). Fall back to the config's enabled value so the switch reflects reality immediately after the wizard closes.
* guard camera enabled access
console would throw errors after adding via camera wizard
* fix useOptimisticState dropping debounced setState under StrictMode
* use openvino on cpu as default model
- faster than tflite on cpu
- add to default generated config
* use an enum for model_size
the frontend will then render this as a select dropdown because of the changes in the json schema
* i18n
* sync object filter entries with tracked labels in camera config form
Filter sub-collapsibles in the camera Objects section are driven by `filters` dict keys, but profile merges and live track-switch edits don't add matching entries, so newly tracked labels (like from a profile override) had no collapsible. Synthesize default filter entries from `track` in the form data so every tracked label renders a collapsible; baseline data also gets the synthesized entries, so save payloads are unchanged.
* revalidate raw paths cache after config save so CameraPathWidget shows fresh credentials
* fix test
* restore masked ffmpeg credentials when persisting camera config
* formatting
* rebuild ffmpeg commands when enabling recording for the first time
Toggling record.enabled from the config UI updated the in-memory config but left ffmpeg running with its original command, so the record output args were never wired in and nothing landed in the cache for the maintainer to move. The record config update now rebuilds ffmpeg_cmds when enabled_in_config transitions, and the camera watchdog restarts ffmpeg on a false to true transition so the record output gets wired in. MQTT toggles, which only flip record.enabled at runtime, are unaffected and continue to work via the maintainer's drop/keep gate.
* keep record toggle switch in single camera view disabled until enabled in config
* fix override detection for sections unset in the global config
Override badges and the blue dot now compare against schema defaults for sections like motion that the API serializes as null when omitted from the global YAML, instead of treating any populated camera config as an override
* add support for config-aware patterns in section hiddenFields
Section configs can now declare dynamic hidden-field entries as functions of the loaded config; objects.ts uses this to hide auto-populated attribute filters (DHL, face, license_plate, etc.) from the form, save flow, and override popover when those labels aren't user-settable
* siimplify object filters handling
live updating was getting very messy. users will just need to save once they enable a new object in order to see filters for that object
* tweaks
* update docs for new detector default
* make genai provider required and add special case for UI
prevent validation errors from appearing on initial creation of genai provider by setting the first option in the select dropdown as default
2026-05-07 16:53:07 +03: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 18:38:14 +03: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 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 :
2026-04-18 16:10:50 +03: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 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-05-15 18:06:38 +03:00
for key in sorted (
set ( camera_config . audio . listen ) - 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 (
2026-05-10 23:21:44 +03:00
coords ,
camera_config . frame_shape ,
camera_name = camera_config . name ,
2026-02-28 17:04:43 +03: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 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 )
2026-05-25 16:04:00 +03: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 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 }
)