From fd941cd0086400d77267a3f32e1989fd6a337612 Mon Sep 17 00:00:00 2001 From: "Galindo, Alex" Date: Mon, 6 Mar 2023 10:21:13 +0100 Subject: [PATCH] Add rotate camera feature to detect option --- frigate/config.py | 2 + frigate/ffmpeg_presets.py | 55 ++++++++++-- frigate/test/test_ffmpeg_presets.py | 135 ++++++++++++++++++++++++++++ 3 files changed, 183 insertions(+), 9 deletions(-) diff --git a/frigate/config.py b/frigate/config.py index 9a96b642a..2488ef192 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -576,6 +576,7 @@ class CameraUiConfig(FrigateBaseModel): class CameraConfig(FrigateBaseModel): name: Optional[str] = Field(title="Camera name.", regex=REGEX_CAMERA_NAME) enabled: bool = Field(default=True, title="Enable camera.") + rotate: int = Field(default=0, title="Rotate camera: 0º, 90º, 180º or 270º(-90º)") ffmpeg: CameraFfmpegConfig = Field(title="FFmpeg configuration for the camera.") best_image_timeout: int = Field( default=60, @@ -674,6 +675,7 @@ class CameraConfig(FrigateBaseModel): self.detect.fps, self.detect.width, self.detect.height, + self.rotate, ) ffmpeg_output_args = scale_detect_args + ffmpeg_output_args + ["pipe:"] diff --git a/frigate/ffmpeg_presets.py b/frigate/ffmpeg_presets.py index 67348569c..52b736d8f 100644 --- a/frigate/ffmpeg_presets.py +++ b/frigate/ffmpeg_presets.py @@ -108,16 +108,27 @@ PRESETS_HW_ACCEL_DECODE = { } PRESETS_HW_ACCEL_SCALE = { - "preset-rpi-32-h264": "-r {0} -s {1}x{2}", - "preset-rpi-64-h264": "-r {0} -s {1}x{2}", - "preset-vaapi": "-r {0} -vf fps={0},scale_vaapi=w={1}:h={2},hwdownload,format=yuv420p", - "preset-intel-qsv-h264": "-r {0} -vf vpp_qsv=framerate={0}:w={1}:h={2}:format=nv12,hwdownload,format=nv12,format=yuv420p", - "preset-intel-qsv-h265": "-r {0} -vf vpp_qsv=framerate={0}:w={1}:h={2}:format=nv12,hwdownload,format=nv12,format=yuv420p", - "preset-nvidia-h264": "-r {0} -vf fps={0},scale_cuda=w={1}:h={2}:format=nv12,hwdownload,format=nv12,format=yuv420p", - "preset-nvidia-h265": "-r {0} -vf fps={0},scale_cuda=w={1}:h={2}:format=nv12,hwdownload,format=nv12,format=yuv420p", + "preset-rpi-32-h264": "-r {0}{3} -s {1}x{2}", + "preset-rpi-64-h264": "-r {0}{3} -s {1}x{2}", + "preset-vaapi": "-r {0} -vf fps={0},{3}scale_vaapi=w={1}:h={2},hwdownload,format=yuv420p", + "preset-intel-qsv-h264": "-r {0} -vf vpp_qsv=framerate={0}:{3}w={1}:h={2}:format=nv12,hwdownload,format=nv12,format=yuv420p", + "preset-intel-qsv-h265": "-r {0} -vf vpp_qsv=framerate={0}:{3}w={1}:h={2}:format=nv12,hwdownload,format=nv12,format=yuv420p", + "preset-nvidia-h264": "-r {0} -vf fps={0},{3}scale_cuda=w={1}:h={2}:format=nv12,hwdownload,format=nv12,format=yuv420p", + "preset-nvidia-h265": "-r {0} -vf fps={0},{3}scale_cuda=w={1}:h={2}:format=nv12,hwdownload,format=nv12,format=yuv420p", "default": "-r {0} -s {1}x{2}", } +PRESETS_HW_ACCEL_SCALE_ROTATION = { + "preset-rpi-32-h264": " -vf transpose={0}", + "preset-rpi-64-h264": " -vf transpose={0}", + "preset-vaapi": "transpose_vaapi={0},", + "preset-intel-qsv-h264": "transpose={0}:", + "preset-intel-qsv-h265": "transpose={0}:", + "preset-nvidia-h264": "transpose={0},", + "preset-nvidia-h265": "transpose={0},", + "default": " -vf transpose={0}", +} + PRESETS_HW_ACCEL_ENCODE = { "preset-rpi-32-h264": "ffmpeg -hide_banner {0} -c:v h264_v4l2m2m {1}", "preset-rpi-64-h264": "ffmpeg -hide_banner {0} -c:v h264_v4l2m2m {1}", @@ -138,12 +149,36 @@ def parse_preset_hardware_acceleration_decode(arg: Any) -> list[str]: return PRESETS_HW_ACCEL_DECODE.get(arg, None) +def _parse_rotation_scale( + arg: Any, + rotate: int, +) -> str: + """Return the correct rotation scale or "" if preset none is set.""" + if not isinstance(arg, str) or " " in arg: + return "" + + if rotate == 90: + transpose = "clock" + elif rotate == 180: + if arg.startswith("preset-vaapi") or arg.startswith("preset-intel-qsv"): + transpose = "reverse" + else: # No 'reverse' option suported, then 2 'clocks' rotations + transpose = "clock,transpose=clock" + elif rotate == 270: + transpose = "cclock" + else : # Rotation not need or not supported + return "" + + return PRESETS_HW_ACCEL_SCALE_ROTATION.get(arg, "").format(transpose) + + def parse_preset_hardware_acceleration_scale( arg: Any, detect_args: list[str], fps: int, width: int, height: int, + rotate: int, ) -> list[str]: """Return the correct scaling preset or default preset if none is set.""" if not isinstance(arg, str) or " " in arg: @@ -151,14 +186,16 @@ def parse_preset_hardware_acceleration_scale( scale.extend(detect_args) return scale + transpose =_parse_rotation_scale(arg, rotate) + scale = PRESETS_HW_ACCEL_SCALE.get(arg, "") if scale: - scale = scale.format(fps, width, height).split(" ") + scale = scale.format(fps, width, height, transpose).split(" ") scale.extend(detect_args) return scale else: - scale = scale.format(fps, width, height).split(" ") + scale = scale.format(fps, width, height, transpose).split(" ") scale.extend(detect_args) return scale diff --git a/frigate/test/test_ffmpeg_presets.py b/frigate/test/test_ffmpeg_presets.py index 6ea623790..f91139846 100644 --- a/frigate/test/test_ffmpeg_presets.py +++ b/frigate/test/test_ffmpeg_presets.py @@ -85,6 +85,141 @@ class TestFfmpegPresets(unittest.TestCase): in (" ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"])) ) + def test_ffmpeg_hwaccel_rotate_90_preset(self): + self.default_ffmpeg["cameras"]["back"][ + "rotate" + ] = 90 + self.default_ffmpeg["cameras"]["back"]["ffmpeg"][ + "hwaccel_args" + ] = "preset-nvidia-h264" + self.default_ffmpeg["cameras"]["back"]["detect"] = { + "height": 1920, + "width": 2560, + "fps": 10, + } + frigate_config = FrigateConfig(**self.default_ffmpeg) + frigate_config.cameras["back"].create_ffmpeg_cmds() + assert "preset-nvidia-h264" not in ( + " ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]) + ) + assert ( + "fps=10,transpose=clock,scale_cuda=w=2560:h=1920:format=nv12,hwdownload,format=nv12,format=yuv420p" + in (" ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"])) + ) + + def test_ffmpeg_hwaccel_rotate_180_preset(self): + self.default_ffmpeg["cameras"]["back"][ + "rotate" + ] = 180 + self.default_ffmpeg["cameras"]["back"]["ffmpeg"][ + "hwaccel_args" + ] = "preset-rpi-64-h264" + self.default_ffmpeg["cameras"]["back"]["detect"] = { + "height": 1920, + "width": 2560, + "fps": 10, + } + + frigate_config = FrigateConfig(**self.default_ffmpeg) + frigate_config.cameras["back"].create_ffmpeg_cmds() + assert "preset-rpi-64-h264" not in ( + " ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]) + ) + assert ( + "-r 10 -vf transpose=clock,transpose=clock -s 2560x1920" + in (" ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"])) + ) + + def test_ffmpeg_hwaccel_rotate_180_vaapi_preset(self): + self.default_ffmpeg["cameras"]["back"][ + "rotate" + ] = 180 + self.default_ffmpeg["cameras"]["back"]["ffmpeg"][ + "hwaccel_args" + ] = "preset-vaapi" + self.default_ffmpeg["cameras"]["back"]["detect"] = { + "height": 1920, + "width": 2560, + "fps": 10, + } + + frigate_config = FrigateConfig(**self.default_ffmpeg) + frigate_config.cameras["back"].create_ffmpeg_cmds() + assert "preset-vaapi" not in ( + " ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]) + ) + assert ( + "-r 10 -vf fps=10,transpose_vaapi=reverse,scale_vaapi=w=2560:h=1920,hwdownload,format=yuv420p" + in (" ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"])) + ) + + def test_ffmpeg_hwaccel_rotate_180_qsv_preset(self): + self.default_ffmpeg["cameras"]["back"][ + "rotate" + ] = 180 + self.default_ffmpeg["cameras"]["back"]["ffmpeg"][ + "hwaccel_args" + ] = "preset-intel-qsv-h264" + self.default_ffmpeg["cameras"]["back"]["detect"] = { + "height": 1920, + "width": 2560, + "fps": 10, + } + + frigate_config = FrigateConfig(**self.default_ffmpeg) + frigate_config.cameras["back"].create_ffmpeg_cmds() + assert "preset-intel-qsv-h264" not in ( + " ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]) + ) + assert ( + "-r 10 -vf vpp_qsv=framerate=10:transpose=reverse:w=2560:h=1920:format=nv12,hwdownload,format=nv12,format=yuv420p" + in (" ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"])) + ) + + def test_ffmpeg_hwaccel_rotate_270_preset(self): + self.default_ffmpeg["cameras"]["back"][ + "rotate" + ] = 270 + self.default_ffmpeg["cameras"]["back"]["ffmpeg"][ + "hwaccel_args" + ] = "preset-nvidia-h264" + self.default_ffmpeg["cameras"]["back"]["detect"] = { + "height": 1920, + "width": 2560, + "fps": 10, + } + frigate_config = FrigateConfig(**self.default_ffmpeg) + frigate_config.cameras["back"].create_ffmpeg_cmds() + assert "preset-nvidia-h264" not in ( + " ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]) + ) + assert ( + "fps=10,transpose=cclock,scale_cuda=w=2560:h=1920:format=nv12,hwdownload,format=nv12,format=yuv420p" + in (" ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"])) + ) + + def test_ffmpeg_hwaccel_rotate_wrong_preset(self): + self.default_ffmpeg["cameras"]["back"][ + "rotate" + ] = 20 + self.default_ffmpeg["cameras"]["back"]["ffmpeg"][ + "hwaccel_args" + ] = "preset-nvidia-h264" + self.default_ffmpeg["cameras"]["back"]["detect"] = { + "height": 1920, + "width": 2560, + "fps": 10, + } + frigate_config = FrigateConfig(**self.default_ffmpeg) + frigate_config.cameras["back"].create_ffmpeg_cmds() + assert "preset-nvidia-h264" not in ( + " ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]) + ) + assert ( + "fps=10,scale_cuda=w=2560:h=1920:format=nv12,hwdownload,format=nv12,format=yuv420p" + in (" ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"])) + ) + def test_default_ffmpeg_input_arg_preset(self): frigate_config = FrigateConfig(**self.default_ffmpeg)