mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-10-21 14:12:47 +03:00
CI / AMD64 Build (push) Canceled after 0s
CI / ARM Build (push) Canceled after 0s
CI / Jetson Jetpack 6 (push) Canceled after 0s
CI / AMD64 Extra Build (push) Canceled after 0s
CI / ARM Extra Build (push) Canceled after 0s
CI / Synaptics Build (push) Canceled after 0s
CI / Assemble and push default build (push) Canceled after 0s
* restrict go2rtc ffmpeg and ngrok options without arbitrary exec The GO2RTC_ALLOW_ARBITRARY_EXEC checks only looked at go2rtc.streams, so an admin could save a config with a custom ffmpeg.bin or ngrok.command and run any command as the go2rtc user after a restart. go2rtc builds every ffmpeg: source into a command that starts with ffmpeg.bin, and it runs ngrok.command at startup. A custom template also brought back arbitrary ffmpeg arguments, since a stream can reference any template key with #video=<name>. config/save and config/set now reject any added or changed key under go2rtc.ffmpeg or go2rtc.ngrok unless arbitrary exec is enabled. Keys already in the saved config are left alone, the same as stream sources. At startup, ffmpeg.bin is always Frigate's ffmpeg and ngrok is dropped unless arbitrary exec is enabled. Templates already in the config file, such as the documented vaapi global, keep working. * reject ffmpeg paths with spaces, quotes or shell characters ffmpeg.path was used as-is when absolute, and several ffmpeg commands are built as strings and split on spaces before running. A path like `/bin/sh -c <cmd>;#` ran a shell command instead of ffmpeg in the preview and export processes, and in go2rtc through the birdseye restream exec source. The path is now validated as a version alias or an absolute path of plain characters, so config/save and config/set reject an unsafe value. resolve_ffmpeg_path falls back to the default ffmpeg for one, and the go2rtc startup script prints an error.
159 lines
4.9 KiB
Python
159 lines
4.9 KiB
Python
from enum import Enum
|
|
|
|
from pydantic import Field, field_validator
|
|
|
|
from frigate.util.config import is_valid_ffmpeg_path, resolve_ffmpeg_path
|
|
|
|
from ..base import FrigateBaseModel
|
|
from ..env import EnvString
|
|
|
|
__all__ = [
|
|
"CameraFfmpegConfig",
|
|
"CameraInput",
|
|
"CameraRoleEnum",
|
|
"FfmpegConfig",
|
|
"FfmpegOutputArgsConfig",
|
|
]
|
|
|
|
# Note: Setting threads to less than 2 caused several issues with recording segments
|
|
# https://github.com/blakeblackshear/frigate/issues/5659
|
|
FFMPEG_GLOBAL_ARGS_DEFAULT = ["-hide_banner", "-loglevel", "warning", "-threads", "2"]
|
|
FFMPEG_INPUT_ARGS_DEFAULT = "preset-rtsp-generic"
|
|
|
|
RECORD_FFMPEG_OUTPUT_ARGS_DEFAULT = "preset-record-generic-audio-aac"
|
|
DETECT_FFMPEG_OUTPUT_ARGS_DEFAULT = [
|
|
"-threads",
|
|
"2",
|
|
"-f",
|
|
"rawvideo",
|
|
"-pix_fmt",
|
|
"yuv420p",
|
|
]
|
|
|
|
|
|
class FfmpegOutputArgsConfig(FrigateBaseModel):
|
|
detect: str | list[str] = Field(
|
|
default=DETECT_FFMPEG_OUTPUT_ARGS_DEFAULT,
|
|
title="Detect output arguments",
|
|
description="Default output arguments for detect role streams.",
|
|
)
|
|
record: str | list[str] = Field(
|
|
default=RECORD_FFMPEG_OUTPUT_ARGS_DEFAULT,
|
|
title="Record output arguments",
|
|
description="Default output arguments for record role streams.",
|
|
)
|
|
|
|
|
|
class FfmpegConfig(FrigateBaseModel):
|
|
path: str = Field(
|
|
default="default",
|
|
title="FFmpeg path",
|
|
description='Path to the FFmpeg binary to use or a version alias ("7.0" or "8.0").',
|
|
)
|
|
global_args: str | list[str] = Field(
|
|
default=FFMPEG_GLOBAL_ARGS_DEFAULT,
|
|
title="FFmpeg global arguments",
|
|
description="Global arguments passed to FFmpeg processes.",
|
|
)
|
|
hwaccel_args: str | list[str] = Field(
|
|
default="auto",
|
|
title="Hardware acceleration arguments",
|
|
description="Hardware acceleration arguments for FFmpeg. Provider-specific presets are recommended.",
|
|
)
|
|
input_args: str | list[str] = Field(
|
|
default=FFMPEG_INPUT_ARGS_DEFAULT,
|
|
title="Input arguments",
|
|
description="Input arguments applied to FFmpeg input streams.",
|
|
)
|
|
output_args: FfmpegOutputArgsConfig = Field(
|
|
default_factory=FfmpegOutputArgsConfig,
|
|
title="Output arguments",
|
|
description="Default output arguments used for different FFmpeg roles such as detect and record.",
|
|
)
|
|
retry_interval: float = Field(
|
|
default=10.0,
|
|
title="FFmpeg retry time",
|
|
description="Seconds to wait before attempting to reconnect a camera stream after failure. Default is 10.",
|
|
gt=0.0,
|
|
)
|
|
apple_compatibility: bool = Field(
|
|
default=False,
|
|
title="Apple compatibility",
|
|
description="Enable HEVC tagging for better Apple player compatibility when recording H.265.",
|
|
)
|
|
gpu: int = Field(
|
|
default=0,
|
|
title="GPU index",
|
|
description="Default GPU index used for hardware acceleration if available.",
|
|
)
|
|
|
|
@field_validator("path")
|
|
@classmethod
|
|
def validate_path(cls, v: str) -> str:
|
|
if not is_valid_ffmpeg_path(v):
|
|
raise ValueError(
|
|
"FFmpeg path must be a version alias or an absolute path without "
|
|
"spaces, quotes or shell characters"
|
|
)
|
|
return v
|
|
|
|
@property
|
|
def ffmpeg_path(self) -> str:
|
|
return resolve_ffmpeg_path(self.path, "ffmpeg")
|
|
|
|
@property
|
|
def ffprobe_path(self) -> str:
|
|
return resolve_ffmpeg_path(self.path, "ffprobe")
|
|
|
|
|
|
class CameraRoleEnum(str, Enum):
|
|
audio = "audio"
|
|
record = "record"
|
|
detect = "detect"
|
|
|
|
|
|
class CameraInput(FrigateBaseModel):
|
|
path: EnvString = Field(
|
|
title="Input path",
|
|
description="Camera input stream URL or path.",
|
|
)
|
|
roles: list[CameraRoleEnum] = Field(
|
|
title="Input roles",
|
|
description="Roles for this input stream.",
|
|
)
|
|
global_args: str | list[str] = Field(
|
|
default_factory=list,
|
|
title="FFmpeg global arguments",
|
|
description="FFmpeg global arguments for this input stream.",
|
|
)
|
|
hwaccel_args: str | list[str] = Field(
|
|
default_factory=list,
|
|
title="Hardware acceleration arguments",
|
|
description="Hardware acceleration arguments for this input stream.",
|
|
)
|
|
input_args: str | list[str] = Field(
|
|
default_factory=list,
|
|
title="Input arguments",
|
|
description="Input arguments specific to this stream.",
|
|
)
|
|
|
|
|
|
class CameraFfmpegConfig(FfmpegConfig):
|
|
inputs: list[CameraInput] = Field(
|
|
title="Camera inputs",
|
|
description="List of input stream definitions (paths and roles) for this camera.",
|
|
)
|
|
|
|
@field_validator("inputs")
|
|
@classmethod
|
|
def validate_roles(cls, v):
|
|
roles = [role for input in v for role in input.roles]
|
|
|
|
if len(roles) != len(set(roles)):
|
|
raise ValueError("Each input role may only be used once.")
|
|
|
|
if "detect" not in roles:
|
|
raise ValueError("The detect role is required.")
|
|
|
|
return v
|