From 8d01cc480732683a4ef90b4ebeb94083f6c38375 Mon Sep 17 00:00:00 2001 From: Jason Hunter Date: Thu, 24 Jun 2021 16:45:15 -0400 Subject: [PATCH] fix ffmpeg config and remove side effects --- frigate/config.py | 86 +++++++++++++++++++++++-------------- frigate/test/test_config.py | 13 ++++-- frigate/util.py | 5 ++- 3 files changed, 67 insertions(+), 37 deletions(-) diff --git a/frigate/config.py b/frigate/config.py index 3307fa541..c9295ada8 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -281,27 +281,23 @@ RECORD_FFMPEG_OUTPUT_ARGS_DEFAULT = [ class FfmpegOutputArgsConfig(BaseModel): - detect: List[str] = Field( + detect: Union[str, List[str]] = Field( default=DETECT_FFMPEG_OUTPUT_ARGS_DEFAULT, title="Detect role FFmpeg output arguments.", ) - record: List[str] = Field( + record: Union[str, List[str]] = Field( default=RECORD_FFMPEG_OUTPUT_ARGS_DEFAULT, title="Record role FFmpeg output arguments.", ) - clips: List[str] = Field( + clips: Union[str, List[str]] = Field( default=SAVE_CLIPS_FFMPEG_OUTPUT_ARGS_DEFAULT, title="Clips role FFmpeg output arguments.", ) - rtmp: List[str] = Field( + rtmp: Union[str, List[str]] = Field( default=RTMP_FFMPEG_OUTPUT_ARGS_DEFAULT, 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): global_args: Union[str, List[str]] = Field( @@ -322,32 +318,32 @@ class FfmpegConfig(BaseModel): class CameraInput(BaseModel): path: str = Field(title="Camera input path.") 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." ) - hwaccel_args: List[str] = Field( + hwaccel_args: Union[str, List[str]] = Field( 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") def sub_env_vars(cls, v): 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): 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." ) - hwaccel_args: List[str] = Field( + hwaccel_args: Union[str, List[str]] = Field( 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( default_factory=FfmpegOutputArgsConfig, title="FFmpeg output arguments." ) @@ -365,10 +361,6 @@ class CameraFfmpegConfig(FfmpegConfig): 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): enabled: bool = Field(default=False, title="Snapshots enabled.") @@ -521,26 +513,42 @@ class CameraConfig(BaseModel): def _get_ffmpeg_cmd(self, ffmpeg_input: CameraInput): ffmpeg_output_args = [] if "detect" in ffmpeg_input.roles: - ffmpeg_output_args = ( - self.ffmpeg.output_args.detect + ffmpeg_output_args + ["pipe:"] + detect_args = ( + 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: ffmpeg_output_args = ["-r", str(self.fps)] + ffmpeg_output_args if "rtmp" in ffmpeg_input.roles and self.rtmp.enabled: - ffmpeg_output_args = ( + rtmp_args = ( self.ffmpeg.output_args.rtmp - + [f"rtmp://127.0.0.1/live/{self.name}"] - + ffmpeg_output_args + if isinstance(self.ffmpeg.output_args.rtmp, list) + 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: - ffmpeg_output_args = ( + clips_args = ( 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"] + ffmpeg_output_args ) if "record" in ffmpeg_input.roles and self.record.enabled: - ffmpeg_output_args = ( + record_args = ( 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"] + ffmpeg_output_args ) @@ -549,11 +557,25 @@ class CameraConfig(BaseModel): if len(ffmpeg_output_args) == 0: 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 = ( ["ffmpeg"] - + (ffmpeg_input.global_args or self.ffmpeg.global_args) - + (ffmpeg_input.hwaccel_args or self.ffmpeg.hwaccel_args) - + (ffmpeg_input.input_args or self.ffmpeg.input_args) + + global_args + + hwaccel_args + + input_args + ["-i", ffmpeg_input.path] + ffmpeg_output_args ) diff --git a/frigate/test/test_config.py b/frigate/test/test_config.py index 2530027d0..de2a60309 100644 --- a/frigate/test/test_config.py +++ b/frigate/test/test_config.py @@ -199,7 +199,7 @@ class TestConfig(unittest.TestCase): def test_ffmpeg_params_global(self): config = { - "ffmpeg": {"input_args": ["-re"]}, + "ffmpeg": {"input_args": "-re"}, "mqtt": {"host": "mqtt"}, "cameras": { "back": { @@ -226,6 +226,7 @@ class TestConfig(unittest.TestCase): def test_ffmpeg_params_camera(self): config = { "mqtt": {"host": "mqtt"}, + "ffmpeg": {"input_args": ["test"]}, "cameras": { "back": { "ffmpeg": { @@ -248,10 +249,12 @@ class TestConfig(unittest.TestCase): runtime_config = frigate_config.runtime_config 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): config = { "mqtt": {"host": "mqtt"}, + "ffmpeg": {"input_args": ["test2"]}, "cameras": { "back": { "ffmpeg": { @@ -259,9 +262,10 @@ class TestConfig(unittest.TestCase): { "path": "rtsp://10.0.0.1:554/video", "roles": ["detect"], - "input_args": ["-re"], + "input_args": "-re test", } - ] + ], + "input_args": "test3", }, "height": 1080, "width": 1920, @@ -277,6 +281,9 @@ class TestConfig(unittest.TestCase): runtime_config = frigate_config.runtime_config 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): config = { diff --git a/frigate/util.py b/frigate/util.py index a09d2721a..0ae2848e9 100755 --- a/frigate/util.py +++ b/frigate/util.py @@ -21,7 +21,7 @@ import numpy as np 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 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): merged[k] = deep_merge(v1, v2, override) elif isinstance(v1, list) and isinstance(v2, list): - merged[k] = v1 + v2 + if merge_lists: + merged[k] = v1 + v2 else: if override: merged[k] = copy.deepcopy(v2)