From 826671c008505edca5af031e7f42b6852febc83f Mon Sep 17 00:00:00 2001 From: "Galindo, Alex" Date: Mon, 6 Mar 2023 11:39:08 +0100 Subject: [PATCH] Add rotate camera feature to record option --- frigate/config.py | 6 +- frigate/ffmpeg_presets.py | 180 ++++++++++++---------------- frigate/test/test_ffmpeg_presets.py | 120 +++++++++++++++++++ 3 files changed, 199 insertions(+), 107 deletions(-) diff --git a/frigate/config.py b/frigate/config.py index 2488ef192..55771ed96 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -690,7 +690,11 @@ class CameraConfig(FrigateBaseModel): ) if "record" in ffmpeg_input.roles and self.record.enabled: record_args = get_ffmpeg_arg_list( - parse_preset_output_record(self.ffmpeg.output_args.record) + parse_preset_output_record( + self.ffmpeg.output_args.record, + ffmpeg_input.hwaccel_args or self.ffmpeg.hwaccel_args, + self.rotate, + ) or self.ffmpeg.output_args.record ) diff --git a/frigate/ffmpeg_presets.py b/frigate/ffmpeg_presets.py index 52b736d8f..e8102f5ad 100644 --- a/frigate/ffmpeg_presets.py +++ b/frigate/ffmpeg_presets.py @@ -119,14 +119,38 @@ PRESETS_HW_ACCEL_SCALE = { } 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}", + "preset-rpi-32-h264": { + "detect": " -vf transpose={0}", + "record": " -vf transpose={0}", + }, + "preset-rpi-64-h264": { + "detect": " -vf transpose={0}", + "record": " -vf transpose={0}", + }, + "preset-vaapi": { + "detect": "transpose_vaapi={0},", + "record": " -vf transpose_vaapi={0}", + }, + "preset-intel-qsv-h264": { + "detect": "transpose={0}:", + "record": " -vf vpp_qsv=transpose={0}", + }, + "preset-intel-qsv-h265": { + "detect": "transpose={0}:", + "record": " -vf vpp_qsv=transpose={0}", + }, + "preset-nvidia-h264": { + "detect": "transpose={0},", + "record": " -vf transpose={0}", + }, + "preset-nvidia-h265": { + "detect": "transpose={0},", + "record": " -vf transpose={0}", + }, + "default": { + "detect": " -vf transpose={0}", + "record": " -vf transpose={0}", + }, } PRESETS_HW_ACCEL_ENCODE = { @@ -151,6 +175,7 @@ def parse_preset_hardware_acceleration_decode(arg: Any) -> list[str]: def _parse_rotation_scale( arg: Any, + mode: str, rotate: int, ) -> str: """Return the correct rotation scale or "" if preset none is set.""" @@ -169,7 +194,7 @@ def _parse_rotation_scale( else : # Rotation not need or not supported return "" - return PRESETS_HW_ACCEL_SCALE_ROTATION.get(arg, "").format(transpose) + return PRESETS_HW_ACCEL_SCALE_ROTATION.get(arg, "").get(mode).format(transpose) def parse_preset_hardware_acceleration_scale( @@ -186,7 +211,7 @@ def parse_preset_hardware_acceleration_scale( scale.extend(detect_args) return scale - transpose =_parse_rotation_scale(arg, rotate) + transpose =_parse_rotation_scale(arg, "detect", rotate) scale = PRESETS_HW_ACCEL_SCALE.get(arg, "") @@ -363,109 +388,52 @@ def parse_preset_input(arg: Any, detect_fps: int) -> list[str]: return PRESETS_INPUT.get(arg, None) -PRESETS_RECORD_OUTPUT = { - "preset-record-generic": [ - "-f", - "segment", - "-segment_time", - "10", - "-segment_format", - "mp4", - "-reset_timestamps", - "1", - "-strftime", - "1", - "-c", - "copy", - "-an", - ], - "preset-record-generic-audio-aac": [ - "-f", - "segment", - "-segment_time", - "10", - "-segment_format", - "mp4", - "-reset_timestamps", - "1", - "-strftime", - "1", - "-c:v", - "copy", - "-c:a", - "aac", - ], - "preset-record-generic-audio-copy": [ - "-f", - "segment", - "-segment_time", - "10", - "-segment_format", - "mp4", - "-reset_timestamps", - "1", - "-strftime", - "1", - "-c", - "copy", - ], - "preset-record-mjpeg": [ - "-f", - "segment", - "-segment_time", - "10", - "-segment_format", - "mp4", - "-reset_timestamps", - "1", - "-strftime", - "1", - "-c:v", - "libx264", - "-an", - ], - "preset-record-jpeg": [ - "-f", - "segment", - "-segment_time", - "10", - "-segment_format", - "mp4", - "-reset_timestamps", - "1", - "-strftime", - "1", - "-c:v", - "libx264", - "-an", - ], - "preset-record-ubiquiti": [ - "-f", - "segment", - "-segment_time", - "10", - "-segment_format", - "mp4", - "-reset_timestamps", - "1", - "-strftime", - "1", - "-c:v", - "copy", - "-ar", - "44100", - "-c:a", - "aac", - ], +PRESETS_RECORD_OUTPUT = "-f segment -segment_time 10 -segment_format mp4 -reset_timestamps 1 -strftime 1" +PRESETS_RECORD_VIDEO_AUDIO = { + "preset-record-generic": { + "video": " -c:v copy", + "audio": " -an", + }, + "preset-record-generic-audio-aac": { + "video": " -c:v copy", + "audio": " -c:a aac", + }, + "preset-record-generic-audio-copy": { + "video": " -c:v copy", + "audio": " -c:a copy", + }, + "preset-record-mjpeg": { + "video": " -c:v libx264", + "audio": " -an", + }, + "preset-record-jpeg": { + "video": " -c:v libx264", + "audio": " -an", + }, + "preset-record-ubiquiti": { + "video": " -c:v copy", + "audio": " -ar 44100 -c:a aac", + }, } -def parse_preset_output_record(arg: Any) -> list[str]: +def parse_preset_output_record(arg: Any, hw_acc: Any, rotate: int) -> list[str]: """Return the correct preset if in preset format otherwise return None.""" if not isinstance(arg, str): return None - return PRESETS_RECORD_OUTPUT.get(arg, None) + preset_record_video_audio = PRESETS_RECORD_VIDEO_AUDIO.get(arg, None) + if not preset_record_video_audio: + return None + + audio = preset_record_video_audio["audio"] + + video = preset_record_video_audio["video"] + transpose =_parse_rotation_scale(hw_acc, "record", rotate) + if transpose != "" or not "copy" in video: + video = transpose + " -c:v libx264" + + return (PRESETS_RECORD_OUTPUT + video + audio).split(" ") PRESETS_RTMP_OUTPUT = { diff --git a/frigate/test/test_ffmpeg_presets.py b/frigate/test/test_ffmpeg_presets.py index f91139846..e6e0357bf 100644 --- a/frigate/test/test_ffmpeg_presets.py +++ b/frigate/test/test_ffmpeg_presets.py @@ -291,6 +291,126 @@ class TestFfmpegPresets(unittest.TestCase): " ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]) ) + def test_ffmpeg_output_record_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"]["ffmpeg"]["output_args"][ + "record" + ] = "preset-record-generic-audio-aac" + + frigate_config = FrigateConfig(**self.default_ffmpeg) + frigate_config.cameras["back"].create_ffmpeg_cmds() + assert "preset-record-generic-audio-aac" not in ( + " ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]) + ) + assert "-vf transpose=clock -c:v libx264 -c:a aac" in ( + " ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]) + ) + + def test_ffmpeg_output_record_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"]["ffmpeg"]["output_args"][ + "record" + ] = "preset-record-generic-audio-aac" + + 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 "-vf transpose=clock,transpose=clock -c:v libx264 -c:a aac" in ( + " ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]) + ) + + def test_ffmpeg_output_record_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"]["ffmpeg"]["output_args"][ + "record" + ] = "preset-record-generic-audio-aac" + + 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 "-vf transpose_vaapi=reverse -c:v libx264 -c:a aac" in ( + " ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]) + ) + + def test_ffmpeg_output_record_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"]["ffmpeg"]["output_args"][ + "record" + ] = "preset-record-generic-audio-aac" + + 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 "-vf vpp_qsv=transpose=reverse -c:v libx264 -c:a aac" in ( + " ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]) + ) + + def test_ffmpeg_output_record_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"]["ffmpeg"]["output_args"][ + "record" + ] = "preset-record-generic-audio-aac" + + 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 "-vf transpose=cclock -c:v libx264 -c:a aac" in ( + " ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]) + ) + + def test_ffmpeg_output_record_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"]["ffmpeg"]["output_args"][ + "record" + ] = "preset-record-generic-audio-aac" + + 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 "-c:v copy -c:a aac" in ( + " ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]) + ) + def test_ffmpeg_output_rtmp_preset(self): self.default_ffmpeg["cameras"]["back"]["ffmpeg"]["output_args"][ "rtmp"