mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +01:00
fix ffmpeg config and remove side effects
This commit is contained in:
parent
1e21a62851
commit
8d01cc4807
@ -281,27 +281,23 @@ RECORD_FFMPEG_OUTPUT_ARGS_DEFAULT = [
|
|||||||
|
|
||||||
|
|
||||||
class FfmpegOutputArgsConfig(BaseModel):
|
class FfmpegOutputArgsConfig(BaseModel):
|
||||||
detect: List[str] = Field(
|
detect: Union[str, List[str]] = Field(
|
||||||
default=DETECT_FFMPEG_OUTPUT_ARGS_DEFAULT,
|
default=DETECT_FFMPEG_OUTPUT_ARGS_DEFAULT,
|
||||||
title="Detect role FFmpeg output arguments.",
|
title="Detect role FFmpeg output arguments.",
|
||||||
)
|
)
|
||||||
record: List[str] = Field(
|
record: Union[str, List[str]] = Field(
|
||||||
default=RECORD_FFMPEG_OUTPUT_ARGS_DEFAULT,
|
default=RECORD_FFMPEG_OUTPUT_ARGS_DEFAULT,
|
||||||
title="Record role FFmpeg output arguments.",
|
title="Record role FFmpeg output arguments.",
|
||||||
)
|
)
|
||||||
clips: List[str] = Field(
|
clips: Union[str, List[str]] = Field(
|
||||||
default=SAVE_CLIPS_FFMPEG_OUTPUT_ARGS_DEFAULT,
|
default=SAVE_CLIPS_FFMPEG_OUTPUT_ARGS_DEFAULT,
|
||||||
title="Clips role FFmpeg output arguments.",
|
title="Clips role FFmpeg output arguments.",
|
||||||
)
|
)
|
||||||
rtmp: List[str] = Field(
|
rtmp: Union[str, List[str]] = Field(
|
||||||
default=RTMP_FFMPEG_OUTPUT_ARGS_DEFAULT,
|
default=RTMP_FFMPEG_OUTPUT_ARGS_DEFAULT,
|
||||||
title="RTMP role FFmpeg output arguments.",
|
title="RTMP role FFmpeg output arguments.",
|
||||||
)
|
)
|
||||||
|
|
||||||
@validator("detect", "record", "clips", "rtmp", pre=True)
|
|
||||||
def extract_args(cls, args):
|
|
||||||
return args if isinstance(args, list) else args.split(" ")
|
|
||||||
|
|
||||||
|
|
||||||
class FfmpegConfig(BaseModel):
|
class FfmpegConfig(BaseModel):
|
||||||
global_args: Union[str, List[str]] = Field(
|
global_args: Union[str, List[str]] = Field(
|
||||||
@ -322,32 +318,32 @@ class FfmpegConfig(BaseModel):
|
|||||||
class CameraInput(BaseModel):
|
class CameraInput(BaseModel):
|
||||||
path: str = Field(title="Camera input path.")
|
path: str = Field(title="Camera input path.")
|
||||||
roles: List[str] = Field(title="Roles assigned to this input.")
|
roles: List[str] = Field(title="Roles assigned to this input.")
|
||||||
global_args: List[str] = Field(
|
global_args: Union[str, List[str]] = Field(
|
||||||
default_factory=list, title="FFmpeg global arguments."
|
default_factory=list, title="FFmpeg global arguments."
|
||||||
)
|
)
|
||||||
hwaccel_args: List[str] = Field(
|
hwaccel_args: Union[str, List[str]] = Field(
|
||||||
default_factory=list, title="FFmpeg hardware acceleration arguments."
|
default_factory=list, title="FFmpeg hardware acceleration arguments."
|
||||||
)
|
)
|
||||||
input_args: List[str] = Field(default_factory=list, title="FFmpeg input arguments.")
|
input_args: Union[str, List[str]] = Field(
|
||||||
|
default_factory=list, title="FFmpeg input arguments."
|
||||||
|
)
|
||||||
|
|
||||||
@validator("path")
|
@validator("path")
|
||||||
def sub_env_vars(cls, v):
|
def sub_env_vars(cls, v):
|
||||||
return v.format(**FRIGATE_ENV_VARS)
|
return v.format(**FRIGATE_ENV_VARS)
|
||||||
|
|
||||||
@validator("global_args", "hwaccel_args", "input_args")
|
|
||||||
def extract_args(cls, args):
|
|
||||||
return args if isinstance(args, list) else args.split(" ")
|
|
||||||
|
|
||||||
|
|
||||||
class CameraFfmpegConfig(FfmpegConfig):
|
class CameraFfmpegConfig(FfmpegConfig):
|
||||||
inputs: List[CameraInput] = Field(title="Camera inputs.")
|
inputs: List[CameraInput] = Field(title="Camera inputs.")
|
||||||
global_args: List[str] = Field(
|
global_args: Union[str, List[str]] = Field(
|
||||||
default_factory=list, title="FFmpeg global arguments."
|
default_factory=list, title="FFmpeg global arguments."
|
||||||
)
|
)
|
||||||
hwaccel_args: List[str] = Field(
|
hwaccel_args: Union[str, List[str]] = Field(
|
||||||
default_factory=list, title="FFmpeg hardware acceleration arguments."
|
default_factory=list, title="FFmpeg hardware acceleration arguments."
|
||||||
)
|
)
|
||||||
input_args: List[str] = Field(default_factory=list, title="FFmpeg input arguments.")
|
input_args: Union[str, List[str]] = Field(
|
||||||
|
default_factory=list, title="FFmpeg input arguments."
|
||||||
|
)
|
||||||
output_args: FfmpegOutputArgsConfig = Field(
|
output_args: FfmpegOutputArgsConfig = Field(
|
||||||
default_factory=FfmpegOutputArgsConfig, title="FFmpeg output arguments."
|
default_factory=FfmpegOutputArgsConfig, title="FFmpeg output arguments."
|
||||||
)
|
)
|
||||||
@ -365,10 +361,6 @@ class CameraFfmpegConfig(FfmpegConfig):
|
|||||||
|
|
||||||
return v
|
return v
|
||||||
|
|
||||||
@validator("global_args", "hwaccel_args", "input_args")
|
|
||||||
def extract_args(cls, args):
|
|
||||||
return args if isinstance(args, list) else args.split(" ")
|
|
||||||
|
|
||||||
|
|
||||||
class CameraSnapshotsConfig(BaseModel):
|
class CameraSnapshotsConfig(BaseModel):
|
||||||
enabled: bool = Field(default=False, title="Snapshots enabled.")
|
enabled: bool = Field(default=False, title="Snapshots enabled.")
|
||||||
@ -521,26 +513,42 @@ class CameraConfig(BaseModel):
|
|||||||
def _get_ffmpeg_cmd(self, ffmpeg_input: CameraInput):
|
def _get_ffmpeg_cmd(self, ffmpeg_input: CameraInput):
|
||||||
ffmpeg_output_args = []
|
ffmpeg_output_args = []
|
||||||
if "detect" in ffmpeg_input.roles:
|
if "detect" in ffmpeg_input.roles:
|
||||||
ffmpeg_output_args = (
|
detect_args = (
|
||||||
self.ffmpeg.output_args.detect + ffmpeg_output_args + ["pipe:"]
|
self.ffmpeg.output_args.detect
|
||||||
|
if isinstance(self.ffmpeg.output_args.detect, list)
|
||||||
|
else self.ffmpeg.output_args.detect.split(" ")
|
||||||
)
|
)
|
||||||
|
ffmpeg_output_args = detect_args + ffmpeg_output_args + ["pipe:"]
|
||||||
if self.fps:
|
if self.fps:
|
||||||
ffmpeg_output_args = ["-r", str(self.fps)] + ffmpeg_output_args
|
ffmpeg_output_args = ["-r", str(self.fps)] + ffmpeg_output_args
|
||||||
if "rtmp" in ffmpeg_input.roles and self.rtmp.enabled:
|
if "rtmp" in ffmpeg_input.roles and self.rtmp.enabled:
|
||||||
ffmpeg_output_args = (
|
rtmp_args = (
|
||||||
self.ffmpeg.output_args.rtmp
|
self.ffmpeg.output_args.rtmp
|
||||||
+ [f"rtmp://127.0.0.1/live/{self.name}"]
|
if isinstance(self.ffmpeg.output_args.rtmp, list)
|
||||||
+ ffmpeg_output_args
|
else self.ffmpeg.output_args.rtmp.split(" ")
|
||||||
|
)
|
||||||
|
ffmpeg_output_args = (
|
||||||
|
rtmp_args + [f"rtmp://127.0.0.1/live/{self.name}"] + ffmpeg_output_args
|
||||||
)
|
)
|
||||||
if "clips" in ffmpeg_input.roles:
|
if "clips" in ffmpeg_input.roles:
|
||||||
ffmpeg_output_args = (
|
clips_args = (
|
||||||
self.ffmpeg.output_args.clips
|
self.ffmpeg.output_args.clips
|
||||||
|
if isinstance(self.ffmpeg.output_args.clips, list)
|
||||||
|
else self.ffmpeg.output_args.clips.split(" ")
|
||||||
|
)
|
||||||
|
ffmpeg_output_args = (
|
||||||
|
clips_args
|
||||||
+ [f"{os.path.join(CACHE_DIR, self.name)}-%Y%m%d%H%M%S.mp4"]
|
+ [f"{os.path.join(CACHE_DIR, self.name)}-%Y%m%d%H%M%S.mp4"]
|
||||||
+ ffmpeg_output_args
|
+ ffmpeg_output_args
|
||||||
)
|
)
|
||||||
if "record" in ffmpeg_input.roles and self.record.enabled:
|
if "record" in ffmpeg_input.roles and self.record.enabled:
|
||||||
ffmpeg_output_args = (
|
record_args = (
|
||||||
self.ffmpeg.output_args.record
|
self.ffmpeg.output_args.record
|
||||||
|
if isinstance(self.ffmpeg.output_args.record, list)
|
||||||
|
else self.ffmpeg.output_args.record.split(" ")
|
||||||
|
)
|
||||||
|
ffmpeg_output_args = (
|
||||||
|
record_args
|
||||||
+ [f"{os.path.join(RECORD_DIR, self.name)}-%Y%m%d%H%M%S.mp4"]
|
+ [f"{os.path.join(RECORD_DIR, self.name)}-%Y%m%d%H%M%S.mp4"]
|
||||||
+ ffmpeg_output_args
|
+ ffmpeg_output_args
|
||||||
)
|
)
|
||||||
@ -549,11 +557,25 @@ class CameraConfig(BaseModel):
|
|||||||
if len(ffmpeg_output_args) == 0:
|
if len(ffmpeg_output_args) == 0:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
global_args = ffmpeg_input.global_args or self.ffmpeg.global_args
|
||||||
|
hwaccel_args = ffmpeg_input.hwaccel_args or self.ffmpeg.hwaccel_args
|
||||||
|
input_args = ffmpeg_input.input_args or self.ffmpeg.input_args
|
||||||
|
|
||||||
|
global_args = (
|
||||||
|
global_args if isinstance(global_args, list) else global_args.split(" ")
|
||||||
|
)
|
||||||
|
hwaccel_args = (
|
||||||
|
hwaccel_args if isinstance(hwaccel_args, list) else hwaccel_args.split(" ")
|
||||||
|
)
|
||||||
|
input_args = (
|
||||||
|
input_args if isinstance(input_args, list) else input_args.split(" ")
|
||||||
|
)
|
||||||
|
|
||||||
cmd = (
|
cmd = (
|
||||||
["ffmpeg"]
|
["ffmpeg"]
|
||||||
+ (ffmpeg_input.global_args or self.ffmpeg.global_args)
|
+ global_args
|
||||||
+ (ffmpeg_input.hwaccel_args or self.ffmpeg.hwaccel_args)
|
+ hwaccel_args
|
||||||
+ (ffmpeg_input.input_args or self.ffmpeg.input_args)
|
+ input_args
|
||||||
+ ["-i", ffmpeg_input.path]
|
+ ["-i", ffmpeg_input.path]
|
||||||
+ ffmpeg_output_args
|
+ ffmpeg_output_args
|
||||||
)
|
)
|
||||||
|
@ -199,7 +199,7 @@ class TestConfig(unittest.TestCase):
|
|||||||
|
|
||||||
def test_ffmpeg_params_global(self):
|
def test_ffmpeg_params_global(self):
|
||||||
config = {
|
config = {
|
||||||
"ffmpeg": {"input_args": ["-re"]},
|
"ffmpeg": {"input_args": "-re"},
|
||||||
"mqtt": {"host": "mqtt"},
|
"mqtt": {"host": "mqtt"},
|
||||||
"cameras": {
|
"cameras": {
|
||||||
"back": {
|
"back": {
|
||||||
@ -226,6 +226,7 @@ class TestConfig(unittest.TestCase):
|
|||||||
def test_ffmpeg_params_camera(self):
|
def test_ffmpeg_params_camera(self):
|
||||||
config = {
|
config = {
|
||||||
"mqtt": {"host": "mqtt"},
|
"mqtt": {"host": "mqtt"},
|
||||||
|
"ffmpeg": {"input_args": ["test"]},
|
||||||
"cameras": {
|
"cameras": {
|
||||||
"back": {
|
"back": {
|
||||||
"ffmpeg": {
|
"ffmpeg": {
|
||||||
@ -248,10 +249,12 @@ class TestConfig(unittest.TestCase):
|
|||||||
|
|
||||||
runtime_config = frigate_config.runtime_config
|
runtime_config = frigate_config.runtime_config
|
||||||
assert "-re" in runtime_config.cameras["back"].ffmpeg_cmds[0]["cmd"]
|
assert "-re" in runtime_config.cameras["back"].ffmpeg_cmds[0]["cmd"]
|
||||||
|
assert "test" not in runtime_config.cameras["back"].ffmpeg_cmds[0]["cmd"]
|
||||||
|
|
||||||
def test_ffmpeg_params_input(self):
|
def test_ffmpeg_params_input(self):
|
||||||
config = {
|
config = {
|
||||||
"mqtt": {"host": "mqtt"},
|
"mqtt": {"host": "mqtt"},
|
||||||
|
"ffmpeg": {"input_args": ["test2"]},
|
||||||
"cameras": {
|
"cameras": {
|
||||||
"back": {
|
"back": {
|
||||||
"ffmpeg": {
|
"ffmpeg": {
|
||||||
@ -259,9 +262,10 @@ class TestConfig(unittest.TestCase):
|
|||||||
{
|
{
|
||||||
"path": "rtsp://10.0.0.1:554/video",
|
"path": "rtsp://10.0.0.1:554/video",
|
||||||
"roles": ["detect"],
|
"roles": ["detect"],
|
||||||
"input_args": ["-re"],
|
"input_args": "-re test",
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"input_args": "test3",
|
||||||
},
|
},
|
||||||
"height": 1080,
|
"height": 1080,
|
||||||
"width": 1920,
|
"width": 1920,
|
||||||
@ -277,6 +281,9 @@ class TestConfig(unittest.TestCase):
|
|||||||
|
|
||||||
runtime_config = frigate_config.runtime_config
|
runtime_config = frigate_config.runtime_config
|
||||||
assert "-re" in runtime_config.cameras["back"].ffmpeg_cmds[0]["cmd"]
|
assert "-re" in runtime_config.cameras["back"].ffmpeg_cmds[0]["cmd"]
|
||||||
|
assert "test" in runtime_config.cameras["back"].ffmpeg_cmds[0]["cmd"]
|
||||||
|
assert "test2" not in runtime_config.cameras["back"].ffmpeg_cmds[0]["cmd"]
|
||||||
|
assert "test3" not in runtime_config.cameras["back"].ffmpeg_cmds[0]["cmd"]
|
||||||
|
|
||||||
def test_inherit_clips_retention(self):
|
def test_inherit_clips_retention(self):
|
||||||
config = {
|
config = {
|
||||||
|
@ -21,7 +21,7 @@ import numpy as np
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def deep_merge(dct1: dict, dct2: dict, override=False) -> dict:
|
def deep_merge(dct1: dict, dct2: dict, override=False, merge_lists=False) -> dict:
|
||||||
"""
|
"""
|
||||||
:param dct1: First dict to merge
|
:param dct1: First dict to merge
|
||||||
:param dct2: Second dict to merge
|
:param dct2: Second dict to merge
|
||||||
@ -35,7 +35,8 @@ def deep_merge(dct1: dict, dct2: dict, override=False) -> dict:
|
|||||||
if isinstance(v1, dict) and isinstance(v2, collections.Mapping):
|
if isinstance(v1, dict) and isinstance(v2, collections.Mapping):
|
||||||
merged[k] = deep_merge(v1, v2, override)
|
merged[k] = deep_merge(v1, v2, override)
|
||||||
elif isinstance(v1, list) and isinstance(v2, list):
|
elif isinstance(v1, list) and isinstance(v2, list):
|
||||||
merged[k] = v1 + v2
|
if merge_lists:
|
||||||
|
merged[k] = v1 + v2
|
||||||
else:
|
else:
|
||||||
if override:
|
if override:
|
||||||
merged[k] = copy.deepcopy(v2)
|
merged[k] = copy.deepcopy(v2)
|
||||||
|
Loading…
Reference in New Issue
Block a user