浏览代码

fix ffmpeg config and remove side effects

Jason Hunter 3 年之前
父节点
当前提交
8d01cc4807
共有 3 个文件被更改,包括 67 次插入37 次删除
  1. 54 32
      frigate/config.py
  2. 10 3
      frigate/test/test_config.py
  3. 3 2
      frigate/util.py

+ 54 - 32
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(" ")
             )
-        if "clips" in ffmpeg_input.roles:
             ffmpeg_output_args = (
+                rtmp_args + [f"rtmp://127.0.0.1/live/{self.name}"] + ffmpeg_output_args
+            )
+        if "clips" in ffmpeg_input.roles:
+            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
         )

+ 10 - 3
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 = {

+ 3 - 2
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)