fix ffmpeg config and remove side effects

This commit is contained in:
Jason Hunter 2021-06-24 16:45:15 -04:00 committed by Blake Blackshear
parent 1e21a62851
commit 8d01cc4807
3 changed files with 67 additions and 37 deletions

View File

@ -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
) )

View File

@ -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 = {

View File

@ -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)