2024-09-28 22:21:42 +03:00
import os
2025-03-23 14:30:48 -05:00
from enum import Enum
2024-09-28 22:21:42 +03:00
from typing import Optional
2025-08-27 01:15:01 +08:00
from pydantic import Field , PrivateAttr , model_validator
2024-09-28 22:21:42 +03:00
from frigate.const import CACHE_DIR , CACHE_SEGMENT_FORMAT , REGEX_CAMERA_NAME
from frigate.ffmpeg_presets import (
parse_preset_hardware_acceleration_decode ,
parse_preset_hardware_acceleration_scale ,
parse_preset_input ,
parse_preset_output_record ,
)
from frigate.util.builtin import (
escape_special_characters ,
generate_color_palette ,
get_ffmpeg_arg_list ,
)
from ..base import FrigateBaseModel
2025-03-19 09:02:40 -06:00
from ..classification import (
2025-10-08 18:06:03 -05:00
CameraAudioTranscriptionConfig ,
2025-03-19 09:02:40 -06:00
CameraFaceRecognitionConfig ,
CameraLicensePlateRecognitionConfig ,
2025-07-07 09:03:57 -05:00
CameraSemanticSearchConfig ,
2025-03-19 09:02:40 -06:00
)
2024-09-28 22:21:42 +03:00
from .audio import AudioConfig
from .birdseye import BirdseyeCameraConfig
from .detect import DetectConfig
from .ffmpeg import CameraFfmpegConfig , CameraInput
from .live import CameraLiveConfig
from .motion import MotionConfig
from .mqtt import CameraMqttConfig
2025-02-10 20:47:15 -06:00
from .notification import NotificationConfig
2024-09-28 22:21:42 +03:00
from .objects import ObjectConfig
from .onvif import OnvifConfig
2026-03-19 09:47:57 -05:00
from .profile import CameraProfileConfig
2024-09-28 22:21:42 +03:00
from .record import RecordConfig
from .review import ReviewConfig
from .snapshots import SnapshotsConfig
from .timestamp import TimestampStyleConfig
from .ui import CameraUiConfig
from .zone import ZoneConfig
__all__ = [ "CameraConfig" ]
2025-03-23 14:30:48 -05:00
class CameraTypeEnum ( str , Enum ):
generic = "generic"
lpr = "lpr"
2024-09-28 22:21:42 +03:00
class CameraConfig ( FrigateBaseModel ):
2026-02-27 09:55:36 -06:00
name : Optional [ str ] = Field (
None ,
title = "Camera name" ,
description = "Camera name is required" ,
pattern = REGEX_CAMERA_NAME ,
)
2025-08-27 01:15:01 +08:00
2025-08-26 15:29:52 -05:00
friendly_name : Optional [ str ] = Field (
2026-02-27 09:55:36 -06:00
None ,
title = "Friendly name" ,
description = "Camera friendly name used in the Frigate UI" ,
2025-08-26 15:29:52 -05:00
)
2025-08-27 01:15:01 +08:00
@model_validator ( mode = "before" )
@classmethod
2025-08-26 15:29:52 -05:00
def handle_friendly_name ( cls , values ):
if isinstance ( values , dict ) and "friendly_name" in values :
2025-08-27 01:15:01 +08:00
pass
return values
2026-02-27 09:55:36 -06:00
enabled : bool = Field ( default = True , title = "Enabled" , description = "Enabled" )
2024-09-28 22:21:42 +03:00
# Options with global fallback
audio : AudioConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = AudioConfig ,
2026-05-07 13:23:02 -05:00
title = "Audio detection" ,
2026-02-27 09:55:36 -06:00
description = "Settings for audio-based event detection for this camera." ,
2024-09-28 22:21:42 +03:00
)
2025-10-08 18:06:03 -05:00
audio_transcription : CameraAudioTranscriptionConfig = Field (
default_factory = CameraAudioTranscriptionConfig ,
2026-02-27 09:55:36 -06:00
title = "Audio transcription" ,
description = "Settings for live and speech audio transcription used for events and live captions." ,
2025-05-27 10:26:00 -05:00
)
2024-09-28 22:21:42 +03:00
birdseye : BirdseyeCameraConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = BirdseyeCameraConfig ,
title = "Birdseye" ,
description = "Settings for the Birdseye composite view that composes multiple camera feeds into a single layout." ,
2024-09-28 22:21:42 +03:00
)
detect : DetectConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = DetectConfig ,
title = "Object Detection" ,
description = "Settings for the detection/detect role used to run object detection and initialize trackers." ,
2024-09-28 22:21:42 +03:00
)
2025-03-19 09:02:40 -06:00
face_recognition : CameraFaceRecognitionConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = CameraFaceRecognitionConfig ,
title = "Face recognition" ,
description = "Settings for face detection and recognition for this camera." ,
)
ffmpeg : CameraFfmpegConfig = Field (
2026-06-16 08:56:52 -05:00
title = "Streams (FFmpeg)" ,
description = "Camera stream inputs and FFmpeg options, including binary path, args, hwaccel, and per-role output args." ,
2025-03-19 09:02:40 -06:00
)
2024-09-28 22:21:42 +03:00
live : CameraLiveConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = CameraLiveConfig ,
title = "Live playback" ,
description = "Settings used by the Web UI to control live stream selection, resolution and quality." ,
2024-09-28 22:21:42 +03:00
)
2025-03-19 09:02:40 -06:00
lpr : CameraLicensePlateRecognitionConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = CameraLicensePlateRecognitionConfig ,
title = "License Plate Recognition" ,
description = "License plate recognition settings including detection thresholds, formatting, and known plates." ,
)
motion : MotionConfig = Field (
None ,
title = "Motion detection" ,
description = "Default motion detection settings for this camera." ,
2025-03-19 09:02:40 -06:00
)
2024-09-28 22:21:42 +03:00
objects : ObjectConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = ObjectConfig ,
title = "Objects" ,
description = "Object tracking defaults including which labels to track and per-object filters." ,
2024-09-28 22:21:42 +03:00
)
record : RecordConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = RecordConfig ,
title = "Recording" ,
description = "Recording and retention settings for this camera." ,
2024-09-28 22:21:42 +03:00
)
review : ReviewConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = ReviewConfig ,
title = "Review" ,
description = "Settings that control alerts, detections, and GenAI review summaries used by the UI and storage for this camera." ,
2024-09-28 22:21:42 +03:00
)
2025-07-07 09:03:57 -05:00
semantic_search : CameraSemanticSearchConfig = Field (
default_factory = CameraSemanticSearchConfig ,
2026-02-27 09:55:36 -06:00
title = "Semantic Search" ,
description = "Settings for semantic search which builds and queries object embeddings to find similar items." ,
2025-07-07 09:03:57 -05:00
)
2024-09-28 22:21:42 +03:00
snapshots : SnapshotsConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = SnapshotsConfig ,
title = "Snapshots" ,
2026-03-22 19:33:04 +00:00
description = "Settings for API-generated snapshots of tracked objects for this camera." ,
2024-09-28 22:21:42 +03:00
)
timestamp_style : TimestampStyleConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = TimestampStyleConfig ,
title = "Timestamp style" ,
2026-05-31 16:09:10 -05:00
description = "Styling options for timestamps applied to snapshots and Debug view." ,
2024-09-28 22:21:42 +03:00
)
# Options without global fallback
best_image_timeout : int = Field (
default = 60 ,
2026-02-27 09:55:36 -06:00
title = "Best image timeout" ,
description = "How long to wait for the image with the highest confidence score." ,
2024-09-28 22:21:42 +03:00
)
mqtt : CameraMqttConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = CameraMqttConfig ,
title = "MQTT" ,
description = "MQTT image publishing settings." ,
2024-09-28 22:21:42 +03:00
)
2025-02-10 20:47:15 -06:00
notifications : NotificationConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = NotificationConfig ,
title = "Notifications" ,
description = "Settings to enable and control notifications for this camera." ,
2025-02-10 20:47:15 -06:00
)
2024-09-28 22:21:42 +03:00
onvif : OnvifConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = OnvifConfig ,
title = "ONVIF" ,
description = "ONVIF connection and PTZ autotracking settings for this camera." ,
)
type : CameraTypeEnum = Field (
default = CameraTypeEnum . generic ,
title = "Camera type" ,
description = "Camera Type" ,
2024-09-28 22:21:42 +03:00
)
ui : CameraUiConfig = Field (
2026-02-27 09:55:36 -06:00
default_factory = CameraUiConfig ,
title = "Camera UI" ,
description = "Display ordering and visibility for this camera in the UI. Ordering affects the default dashboard. For more granular control, use camera groups." ,
2024-09-28 22:21:42 +03:00
)
webui_url : Optional [ str ] = Field (
None ,
2026-02-27 09:55:36 -06:00
title = "Camera URL" ,
description = "URL to visit the camera directly from system page" ,
2024-09-28 22:21:42 +03:00
)
2026-03-19 09:47:57 -05:00
profiles : dict [ str , CameraProfileConfig ] = Field (
default_factory = dict ,
title = "Profiles" ,
description = "Named config profiles with partial overrides that can be activated at runtime." ,
)
2024-09-28 22:21:42 +03:00
zones : dict [ str , ZoneConfig ] = Field (
2026-02-27 09:55:36 -06:00
default_factory = dict ,
title = "Zones" ,
description = "Zones allow you to define a specific area of the frame so you can determine whether or not an object is within a particular area." ,
2024-09-28 22:21:42 +03:00
)
2025-03-03 09:30:52 -06:00
enabled_in_config : Optional [ bool ] = Field (
2026-02-27 09:55:36 -06:00
default = None ,
title = "Original camera state" ,
description = "Keep track of original state of camera." ,
2025-03-03 09:30:52 -06:00
)
2024-09-28 22:21:42 +03:00
_ffmpeg_cmds : list [ dict [ str , list [ str ]]] = PrivateAttr ()
def __init__ ( self , ** config ):
# Set zone colors
if "zones" in config :
colors = generate_color_palette ( len ( config [ "zones" ]))
config [ "zones" ] = {
name : { ** z , "color" : color }
for ( name , z ), color in zip ( config [ "zones" ] . items (), colors )
}
# add roles to the input if there is only one
if len ( config [ "ffmpeg" ][ "inputs" ]) == 1 :
has_audio = "audio" in config [ "ffmpeg" ][ "inputs" ][ 0 ] . get ( "roles" , [])
config [ "ffmpeg" ][ "inputs" ][ 0 ][ "roles" ] = [
"record" ,
"detect" ,
]
if has_audio :
config [ "ffmpeg" ][ "inputs" ][ 0 ][ "roles" ] . append ( "audio" )
super () . __init__ ( ** config )
@property
def frame_shape ( self ) -> tuple [ int , int ]:
return self . detect . height , self . detect . width
@property
def frame_shape_yuv ( self ) -> tuple [ int , int ]:
return self . detect . height * 3 // 2 , self . detect . width
@property
def ffmpeg_cmds ( self ) -> list [ dict [ str , list [ str ]]]:
return self . _ffmpeg_cmds
2025-11-10 11:03:56 -06:00
def get_formatted_name ( self ) -> str :
"""Return the friendly name if set, otherwise return a formatted version of the camera name."""
if self . friendly_name :
return self . friendly_name
return self . name . replace ( "_" , " " ) . title () if self . name else ""
2024-09-28 22:21:42 +03:00
def create_ffmpeg_cmds ( self ):
if "_ffmpeg_cmds" in self :
return
2026-03-06 14:45:39 -06:00
self . _build_ffmpeg_cmds ()
def recreate_ffmpeg_cmds ( self ):
"""Force regeneration of ffmpeg commands from current config."""
self . _build_ffmpeg_cmds ()
def _build_ffmpeg_cmds ( self ):
"""Build ffmpeg commands from the current ffmpeg config."""
2024-09-28 22:21:42 +03:00
ffmpeg_cmds = []
for ffmpeg_input in self . ffmpeg . inputs :
ffmpeg_cmd = self . _get_ffmpeg_cmd ( ffmpeg_input )
if ffmpeg_cmd is None :
continue
ffmpeg_cmds . append ({ "roles" : ffmpeg_input . roles , "cmd" : ffmpeg_cmd })
self . _ffmpeg_cmds = ffmpeg_cmds
def _get_ffmpeg_cmd ( self , ffmpeg_input : CameraInput ):
ffmpeg_output_args = []
if "detect" in ffmpeg_input . roles :
detect_args = get_ffmpeg_arg_list ( self . ffmpeg . output_args . detect )
scale_detect_args = parse_preset_hardware_acceleration_scale (
ffmpeg_input . hwaccel_args or self . ffmpeg . hwaccel_args ,
detect_args ,
self . detect . fps ,
self . detect . width ,
self . detect . height ,
)
ffmpeg_output_args = scale_detect_args + ffmpeg_output_args + [ "pipe:" ]
if "record" in ffmpeg_input . roles and self . record . enabled :
record_args = get_ffmpeg_arg_list (
parse_preset_output_record (
self . ffmpeg . output_args . record ,
2025-01-03 08:11:18 -06:00
self . ffmpeg . apple_compatibility ,
2024-09-28 22:21:42 +03:00
)
or self . ffmpeg . output_args . record
)
ffmpeg_output_args = (
record_args
+ [ f " { os . path . join ( CACHE_DIR , self . name ) } @ { CACHE_SEGMENT_FORMAT } .mp4" ]
+ ffmpeg_output_args
)
# if there aren't any outputs enabled for this input
if len ( ffmpeg_output_args ) == 0 :
return None
global_args = get_ffmpeg_arg_list (
ffmpeg_input . global_args or self . ffmpeg . global_args
)
camera_arg = (
self . ffmpeg . hwaccel_args if self . ffmpeg . hwaccel_args != "auto" else None
)
hwaccel_args = get_ffmpeg_arg_list (
parse_preset_hardware_acceleration_decode (
ffmpeg_input . hwaccel_args ,
self . detect . fps ,
self . detect . width ,
self . detect . height ,
2025-10-13 09:59:38 -06:00
self . ffmpeg . gpu ,
2024-09-28 22:21:42 +03:00
)
or ffmpeg_input . hwaccel_args
or parse_preset_hardware_acceleration_decode (
camera_arg ,
self . detect . fps ,
self . detect . width ,
self . detect . height ,
2025-10-13 09:59:38 -06:00
self . ffmpeg . gpu ,
2024-09-28 22:21:42 +03:00
)
or camera_arg
or []
)
input_args = get_ffmpeg_arg_list (
parse_preset_input ( ffmpeg_input . input_args , self . detect . fps )
or ffmpeg_input . input_args
or parse_preset_input ( self . ffmpeg . input_args , self . detect . fps )
or self . ffmpeg . input_args
)
cmd = (
[ self . ffmpeg . ffmpeg_path ]
+ global_args
+ ( hwaccel_args if "detect" in ffmpeg_input . roles else [])
+ input_args
+ [ "-i" , escape_special_characters ( ffmpeg_input . path )]
+ ffmpeg_output_args
)
return [ part for part in cmd if part != "" ]