diff --git a/src/tbc_video_export/common/enums.py b/src/tbc_video_export/common/enums.py index 441333f..80ecdaa 100644 --- a/src/tbc_video_export/common/enums.py +++ b/src/tbc_video_export/common/enums.py @@ -183,6 +183,16 @@ class VideoBitDepthType(Enum): BIT16 = "16bit" +class HardwareAccelType(Enum): + """Hardware accel types for profiles.""" + + VAAPI = "vaapi" + NVENC = "nvenc" + QUICKSYNC = "quicksync" + AMF = "amf" + VIDEOTOOLBOX = "videotoolbox" + + class ProfileVideoType(Enum): """Video types for profiles.""" diff --git a/src/tbc_video_export/common/exceptions.py b/src/tbc_video_export/common/exceptions.py index 758dd6b..2277041 100644 --- a/src/tbc_video_export/common/exceptions.py +++ b/src/tbc_video_export/common/exceptions.py @@ -17,7 +17,7 @@ def _print_exception(e: BaseException) -> None: if len(message := getattr(e, "message", str(e))) > 1: logging.getLogger("console").exception( ansi.error_color(f"{e.__class__.__name__}: {message}"), - exc_info=False, + exc_info=True, ) diff --git a/src/tbc_video_export/config/config.py b/src/tbc_video_export/config/config.py index c0f05d5..99446bf 100644 --- a/src/tbc_video_export/config/config.py +++ b/src/tbc_video_export/config/config.py @@ -19,7 +19,11 @@ ) if TYPE_CHECKING: - from tbc_video_export.common.enums import ProfileVideoType, VideoSystem + from tbc_video_export.common.enums import ( + HardwareAccelType, + ProfileVideoType, + VideoSystem, + ) from tbc_video_export.config.json import JsonConfig @@ -318,6 +322,7 @@ class GetProfileFilter: name: str video_type: ProfileVideoType | None = None + hwaccel_type: HardwareAccelType | None = None video_system: VideoSystem | None = None def match(self, profile: Profile) -> bool: @@ -333,6 +338,12 @@ def match(self, profile: Profile) -> bool: ): return False + if ( + self.hwaccel_type is not None + and video_profile.hardware_accel is not self.hwaccel_type + ): + return False + if ( self.video_system is not None and video_profile.video_system is not None ) and video_profile.video_system is not self.video_system: diff --git a/src/tbc_video_export/config/default.py b/src/tbc_video_export/config/default.py index e655bd2..0c56e53 100644 --- a/src/tbc_video_export/config/default.py +++ b/src/tbc_video_export/config/default.py @@ -16,13 +16,19 @@ }, { "name": "prores_hq", - "video_profile": "prores_422_hq", + "video_profile": [ + "prores_422_hq", + "prores_422_hq_videotoolbox", + ], "audio_profile": "pcm_24", "filter_profiles": [], }, { "name": "prores_4444xq", - "video_profile": "prores_4444_xq", + "video_profile": [ + "prores_4444_xq", + "prores_4444_xq_videotoolbox", + ], "audio_profile": "pcm_24", "filter_profiles": [], }, @@ -51,6 +57,11 @@ "name": "x264", "video_profile": [ "x264_web", + "x264_web_vaapi", + "x264_web_nvenc", + "x264_web_quicksync", + "x264_web_amf", + "x264_web_videotoolbox", "x264_lossless", ], "audio_profile": "aac", @@ -60,6 +71,11 @@ "name": "x265", "video_profile": [ "x265_web", + "x265_web_vaapi", + "x265_web_nvenc", + "x265_web_quicksync", + "x265_web_amf", + "x265_web_videotoolbox", "x265_lossless", ], "audio_profile": "aac", @@ -69,6 +85,10 @@ "name": "av1", "video_profile": [ "av1_web", + "av1_web_vaapi", + "av1_web_nvenc", + "av1_web_quicksync", + "av1_web_amf", "av1_lossless", ], "audio_profile": "aac", @@ -117,6 +137,18 @@ 8, ], }, + { + "name": "prores_422_hq_videotoolbox", + "description": "ProRes 422 HQ - Apple Video Toolbox", + "codec": "prores_videotoolbox", + "video_format": "uyvy422", + "container": "mov", + "opts": [ + "-profile:v", + "hq", + ], + "hardware_accel": "videotoolbox", + }, { "name": "prores_4444_xq", "description": "ProRes 4444 XQ", @@ -136,6 +168,18 @@ 8, ], }, + { + "name": "prores_4444_xq_videotoolbox", + "description": "ProRes 4444 XQ - Apple Video Toolbox", + "codec": "prores_videotoolbox", + "video_format": "p416le", + "container": "mov", + "opts": [ + "-profile:v", + "xq", + ], + "hardware_accel": "videotoolbox", + }, { "name": "v210", "description": "V210", @@ -253,6 +297,110 @@ "interlaced=0", ], }, + { + "name": "x264_web_vaapi", + "description": "H.264 AVC - VA-API - Web", + "codec": "h264_vaapi", + "video_format": "nv12", + "container": "mov", + "opts": [ + "-rc_mode", + "CQP", + "-global_quality", + 23, + "-maxrate", + "8M", + "-bufsize", + "16M", + "-movflags", + "+faststart", + ], + "hardware_accel": "vaapi", + }, + { + "name": "x264_web_nvenc", + "description": "H.264 AVC - Nvidia NVENC - Web", + "codec": "h264_nvenc", + "video_format": "nv12", + "container": "mov", + "opts": [ + "-rc", + "constqp", + "-qp", + 23, + "-maxrate", + "8M", + "-bufsize", + "16M", + "-movflags", + "+faststart", + ], + "hardware_accel": "nvenc", + }, + { + "name": "x264_web_quicksync", + "description": "H.264 AVC - Intel QuickSync - Web", + "codec": "h264_qsv", + "video_format": "nv12", + "container": "mov", + "opts": [ + "-look_ahead", + 1, + "-global_quality", + 23, + "-maxrate", + "8M", + "-bufsize", + "16M", + "-movflags", + "+faststart", + ], + "hardware_accel": "quicksync", + }, + { + "name": "x264_web_amf", + "description": "H.264 AVC - AMD AMF - Web", + "codec": "h264_amf", + "video_format": "nv12", + "container": "mov", + "opts": [ + "-quality", + 2, + "-rc", + "cqp", + "-rc_i", + 23, + "-rc_p", + 23, + "-maxrate", + "8M", + "-bufsize", + "16M", + "-movflags", + "+faststart", + ], + "hardware_accel": "amf", + }, + { + "name": "x264_web_videotoolbox", + "description": "H.264 AVC - Apple Video Toolbox - Web", + "codec": "h264_videotoolbox", + "video_format": "yuv420p", + "container": "mov", + "opts": [ + "-profile:v", + "main", + "-q:v", + 60, + "-maxrate", + "8M", + "-bufsize", + "16M", + "-tag:v", + "hvc1", + ], + "hardware_accel": "videotoolbox", + }, { "name": "x264_lossless", "description": "H.264 AVC - Lossless", @@ -289,6 +437,113 @@ "interlace=false", ], }, + { + "name": "x265_web_vaapi", + "description": "H.265 HEVC - VA-API - Web", + "codec": "hevc_vaapi", + "video_format": "nv12", + "container": "mov", + "opts": [ + "-rc_mode", + "CQP", + "-qp", + 25, + "-maxrate", + "8M", + "-bufsize", + "16M", + "-movflags", + "+faststart", + ], + "hardware_accel": "vaapi", + }, + { + "name": "x265_web_nvenc", + "description": "H.265 HEVC - Nvidia NVENC - Web", + "codec": "hevc_nvenc", + "video_format": "nv12", + "container": "mov", + "opts": [ + "-rc", + "constqp", + "-qp", + 25, + "-maxrate", + "8M", + "-bufsize", + "16M", + "-movflags", + "+faststart", + ], + "hardware_accel": "nvenc", + }, + { + "name": "x265_web_quicksync", + "description": "H.265 HEVC - Intel QuickSync - Web", + "codec": "hevc_qsv", + "video_format": "nv12", + "container": "mov", + "opts": [ + "-look_ahead", + 1, + "-global_quality", + 25, + "-maxrate", + "8M", + "-bufsize", + "16M", + "-movflags", + "+faststart", + ], + "hardware_accel": "quicksync", + }, + { + "name": "x265_web_amf", + "description": "H.265 HEVC - AMD AMF - Web", + "codec": "hevc_amf", + "video_format": "nv12", + "container": "mov", + "opts": [ + "-quality", + 2, + "-rc", + "cqp", + "-rc_i", + 23, + "-rc_p", + 23, + "-maxrate", + "8M", + "-bufsize", + "16M", + "-movflags", + "+faststart", + ], + "hardware_accel": "amf", + }, + { + "name": "x265_web_videotoolbox", + "description": "H.265 HEVC - Apple Video Toolbox - Web", + "codec": "hevc_videotoolbox", + "video_format": "yuv420p", + "container": "mov", + "opts": [ + "-profile:v", + "main", + "-q:v", + 60, + "-maxrate", + "8M", + "-bufsize", + "16M", + "-tag:v", + "hvc1", + "-movflags", + "+faststart", + ], + "hardware_accel": "videotoolbox", + "type": "videotoolbox", + }, { "name": "x265_lossless", "description": "H.265 HEVC - Lossless", @@ -327,6 +582,90 @@ "+faststart", ], }, + { + "name": "av1_web_vaapi", + "description": "AV1 - VA-API - Web", + "codec": "av1_vaapi", + "video_format": "nv12", + "container": "mov", + "opts": [ + "-rc_mode", + "CQP", + "-global_quality", + 30, + "-maxrate", + "8M", + "-bufsize", + "16M", + "-movflags", + "+faststart", + ], + "hardware_accel": "vaapi", + }, + { + "name": "av1_web_nvenc", + "description": "AV1 - Nvidia NVENC - Web", + "codec": "av1_nvenc", + "video_format": "nv12", + "container": "mov", + "opts": [ + "-rc", + "constqp", + "-qp", + 30, + "-maxrate", + "8M", + "-bufsize", + "16M", + "-movflags", + "+faststart", + ], + "hardware_accel": "nvenc", + }, + { + "name": "xav1_web_quicksync", + "description": "AV1 - Intel QuickSync - Web", + "codec": "av1_qsv", + "video_format": "nv12", + "container": "mov", + "opts": [ + "-look_ahead", + 1, + "-global_quality", + 30, + "-maxrate", + "8M", + "-bufsize", + "16M", + "-movflags", + "+faststart", + ], + "hardware_accel": "quicksync", + }, + { + "name": "av1_web_amf", + "description": "AV1 - AMD AMF - Web", + "codec": "av1_amf", + "video_format": "nv12", + "container": "mov", + "opts": [ + "-quality", + 2, + "-rc", + "cqp", + "-rc_i", + 30, + "-rc_p", + 30, + "-maxrate", + "8M", + "-bufsize", + "16M", + "-movflags", + "+faststart", + ], + "hardware_accel": "amf", + }, { "name": "av1_lossless", "description": "AV1 - Lossless", diff --git a/src/tbc_video_export/config/json.py b/src/tbc_video_export/config/json.py index 3de8cab..d75769d 100644 --- a/src/tbc_video_export/config/json.py +++ b/src/tbc_video_export/config/json.py @@ -51,6 +51,7 @@ class JsonSubProfileVideo(JsonSubProfile): video_format: str filter_profiles_additions: NotRequired[list[str]] filter_profiles_override: NotRequired[list[str]] + hardware_accel: NotRequired[str] type: NotRequired[str] # noqa: A003 diff --git a/src/tbc_video_export/config/profile.py b/src/tbc_video_export/config/profile.py index 877a0cf..28b3987 100644 --- a/src/tbc_video_export/config/profile.py +++ b/src/tbc_video_export/config/profile.py @@ -3,7 +3,11 @@ from typing import TYPE_CHECKING from tbc_video_export.common import exceptions -from tbc_video_export.common.enums import ProfileVideoType, VideoSystem +from tbc_video_export.common.enums import ( + HardwareAccelType, + ProfileVideoType, + VideoSystem, +) from tbc_video_export.common.utils import FlatList, ansi if TYPE_CHECKING: @@ -145,6 +149,14 @@ def filter_profiles_override(self) -> list[str] | None: else None ) + @property + def hardware_accel(self) -> HardwareAccelType | None: + """Return the hardware accel opt.""" + try: + return HardwareAccelType(self._profile["hardware_accel"]) + except (KeyError, ValueError): + return None + @property def profile_type(self) -> ProfileVideoType | None: """Return the video profile type.""" @@ -175,6 +187,12 @@ def __str__(self) -> str: # noqa: D105 else "" ) + data += ( + f"--{ansi.bold(self.hardware_accel.value)} " + if self.hardware_accel is not None + else "" + ) + if data == " ": data += "default" diff --git a/src/tbc_video_export/opts/opt_actions.py b/src/tbc_video_export/opts/opt_actions.py index f001abd..49d96d3 100644 --- a/src/tbc_video_export/opts/opt_actions.py +++ b/src/tbc_video_export/opts/opt_actions.py @@ -6,6 +6,7 @@ from tbc_video_export.common import consts from tbc_video_export.common.enums import ( + HardwareAccelType, ProfileVideoType, VideoBitDepthType, VideoFormatType, @@ -114,6 +115,25 @@ def _print_profiles(self) -> None: logging.getLogger("console").info(data) +class ActionSetVideoHardwareAccelType(argparse.Action): + """Set video format type with alias opts.""" + + def __init__(self, nargs: int = 0, **kwargs: Any) -> None: + super().__init__(nargs=nargs, **kwargs) + + def __call__( # noqa: D102 + self, + parser: argparse.ArgumentParser, # noqa: ARG002 + namespace: argparse.Namespace, + values: str | Sequence[Any] | None, # noqa: ARG002 + option_strings: str, + **kwargs: Any, # noqa: ARG002 + ) -> None: + # no need to check errors here, as option_strings can only be + # VideoBitDepthType values + namespace.hwaccel_type = HardwareAccelType(option_strings[2:].lower()) + + class ActionSetVideoBitDepthType(argparse.Action): """Set video format type with alias opts.""" diff --git a/src/tbc_video_export/opts/opt_validators.py b/src/tbc_video_export/opts/opt_validators.py index 20825d8..fde5685 100644 --- a/src/tbc_video_export/opts/opt_validators.py +++ b/src/tbc_video_export/opts/opt_validators.py @@ -82,6 +82,15 @@ def _validate_video_system( def _validate_video_format(parser: argparse.ArgumentParser, opts: Opts) -> None: + # no support for hardware accelerated profiles and bitdepth/format selection + if ( + opts.video_bitdepth is not None or opts.video_format is not None + ) and opts.hwaccel_type is not None: + parser.error( + "unable to set bitdepth or video format when using a hardware " + "accelerated profile.\n" + ) + # require bitdepth if format set if opts.video_format is not None and opts.video_bitdepth is None: parser.error("setting a video format requires a bitdepth.\n") diff --git a/src/tbc_video_export/opts/opts.py b/src/tbc_video_export/opts/opts.py index e011703..28a9ea1 100644 --- a/src/tbc_video_export/opts/opts.py +++ b/src/tbc_video_export/opts/opts.py @@ -10,6 +10,7 @@ from tbc_video_export.common.enums import ( ChromaDecoder, FieldOrder, + HardwareAccelType, ProfileVideoType, VideoFormatType, VideoSystem, @@ -97,6 +98,7 @@ class Opts(argparse.Namespace): field_order: FieldOrder force_anamorphic: bool force_black_level: tuple[int, int, int] | None + hwaccel_device: str | None thread_queue_size: int checksum: bool @@ -110,6 +112,7 @@ class Opts(argparse.Namespace): append_video_filter: str | None append_other_filter: str | None + hwaccel_type: HardwareAccelType | None video_profile: ProfileVideoType | None video_format: VideoFormatType | None video_bitdepth: int | None diff --git a/src/tbc_video_export/opts/opts_ffmpeg.py b/src/tbc_video_export/opts/opts_ffmpeg.py index ec2504c..338dae2 100644 --- a/src/tbc_video_export/opts/opts_ffmpeg.py +++ b/src/tbc_video_export/opts/opts_ffmpeg.py @@ -112,6 +112,14 @@ def add_ffmpeg_opts(parent: argparse.ArgumentParser) -> None: "\n\n", ) + ffmpeg_opts.add_argument( + "--hwaccel-device", + type=str, + help="Hardware acceleration device.\n" + "Specify a hardware device to use when a supported video profile is selected." + "\n\n", + ) + ffmpeg_opts.add_argument( "--thread-queue-size", type=int, diff --git a/src/tbc_video_export/opts/opts_profile.py b/src/tbc_video_export/opts/opts_profile.py index 09911e8..4d61e1d 100644 --- a/src/tbc_video_export/opts/opts_profile.py +++ b/src/tbc_video_export/opts/opts_profile.py @@ -5,6 +5,7 @@ from tbc_video_export.common import consts from tbc_video_export.common.enums import ( + HardwareAccelType, ProfileVideoType, VideoBitDepthType, VideoFormatType, @@ -123,6 +124,16 @@ def add_profile_opts(config: Config, parent: argparse.ArgumentParser) -> None: ) # add aliases + # hw accel aliases + hwaccel_opts = parent.add_argument_group("hwaccel opts aliases") + for hwaccel in HardwareAccelType: + hwaccel_opts.add_argument( + f"--{hwaccel.value}", + dest="hwaccel_type", + action=opt_actions.ActionSetVideoHardwareAccelType, + help=argparse.SUPPRESS, + ) + # video bit depth alias video_bitdepth_opts = parent.add_argument_group("video bitdepth aliases") for bitdepth in VideoBitDepthType: diff --git a/src/tbc_video_export/process/wrapper/wrapper_ffmpeg.py b/src/tbc_video_export/process/wrapper/wrapper_ffmpeg.py index 4fa122c..312919d 100644 --- a/src/tbc_video_export/process/wrapper/wrapper_ffmpeg.py +++ b/src/tbc_video_export/process/wrapper/wrapper_ffmpeg.py @@ -5,9 +5,10 @@ from pathlib import Path from typing import TYPE_CHECKING -from tbc_video_export.common import consts +from tbc_video_export.common import consts, exceptions from tbc_video_export.common.enums import ( ExportMode, + HardwareAccelType, PipeType, ProcessName, TBCType, @@ -30,8 +31,11 @@ class WrapperFFmpeg(Wrapper): def __init__( self, state: ProgramState, config: WrapperConfig[tuple[Pipe], None] ) -> None: + self._additional_vopts: FlatList = FlatList() + self._hwaccel_opts: FlatList = FlatList() super().__init__(state, config) self._config = config + self._parse_hwaccel() def post_fn(self) -> None: # noqa: D102 pass @@ -45,8 +49,7 @@ def command(self) -> FlatList: # noqa: D102 self._get_overwrite_opt(), self._get_threads_opt(), "-nostdin", - "-hwaccel", - "auto", + self._get_hwaccel_opts(), self._get_color_range_opt(), self._get_input_opts(), self._get_filter_complex_opts(), @@ -90,6 +93,45 @@ def _get_threads_opt(self) -> FlatList: # TODO add complex filter threads? return FlatList(("-threads", str(self._state.opts.threads))) + def _parse_hwaccel(self) -> None: + """Parse hardware acceleration opts.""" + match self._get_profile().video_profile.hardware_accel: + case HardwareAccelType.VAAPI: + self._hwaccel_opts.append( + ("-hwaccel", "vaapi", "-hwaccel_output_format", "vaapi") + ) + + if (hwaccel_device := self._state.opts.hwaccel_device) is not None: + self._hwaccel_opts.append(("-vaapi_device", hwaccel_device)) + + case HardwareAccelType.NVENC: + if (hwaccel_device := self._state.opts.hwaccel_device) is not None: + self._additional_vopts.append(("-gpu", hwaccel_device)) + + case HardwareAccelType.QUICKSYNC: + self._hwaccel_opts.append( + ( + "-hwaccel", + "qsv", + ) + ) + + if (hwaccel_device := self._state.opts.hwaccel_device) is not None: + self._hwaccel_opts.append(("-qsv_device", hwaccel_device)) + + case HardwareAccelType.AMF: + if self._state.opts.hwaccel_device is not None: + raise exceptions.InvalidProfileError( + "Unable to set device for AMD AMF encoding due to FFmpeg " + "limitiations." + ) + + case _: + pass + + def _get_hwaccel_opts(self) -> FlatList: + return self._hwaccel_opts + @staticmethod def _get_color_range_opt() -> FlatList | None: """Return opts for color range.""" @@ -217,6 +259,10 @@ def _get_vbi_crop_filter(self) -> str: case VideoSystem.NTSC | VideoSystem.PAL_M: return "crop=iw:ih-19:0:17" + def _get_hwaccel_filter(self) -> str: + """Return filter for hardware accelerated hwupload.""" + return f"format={self._get_video_profile().video_format},hwupload" + def _get_filters(self) -> tuple[list[str], list[str]]: """Return tuple containing video and other filters.""" video_filters: list[str] = [] @@ -246,6 +292,9 @@ def _get_filters(self) -> tuple[list[str], list[str]]: if self._state.opts.append_video_filter is not None: video_filters.append(self._state.opts.append_video_filter) + if self._state.opts.hwaccel_type is not None: + video_filters.append(self._get_hwaccel_filter()) + # set other filters other_filters += _of @@ -419,6 +468,7 @@ def _get_codec_opts(self) -> FlatList: "-c:v", self._get_profile().video_profile.codec, self._get_profile().video_profile.opts, + self._additional_vopts, ) ) diff --git a/src/tbc_video_export/program_state.py b/src/tbc_video_export/program_state.py index d7d0e94..d0db246 100644 --- a/src/tbc_video_export/program_state.py +++ b/src/tbc_video_export/program_state.py @@ -153,6 +153,7 @@ def profile(self) -> Profile: GetProfileFilter( self.opts.profile, self.opts.video_profile, + self.opts.hwaccel_type, self.video_system, ) ) diff --git a/tests/test_wrappers_ffmpeg_hwaccel.py b/tests/test_wrappers_ffmpeg_hwaccel.py new file mode 100644 index 0000000..274ac7b --- /dev/null +++ b/tests/test_wrappers_ffmpeg_hwaccel.py @@ -0,0 +1,272 @@ +from __future__ import annotations + +import unittest +from functools import partial +from pathlib import Path + +from tbc_video_export.common.enums import TBCType +from tbc_video_export.common.exceptions import InvalidProfileError +from tbc_video_export.common.file_helper import FileHelper +from tbc_video_export.config import Config as ProgramConfig +from tbc_video_export.opts import opts_parser +from tbc_video_export.process.wrapper import WrapperConfig +from tbc_video_export.process.wrapper.pipe import Pipe, PipeFactory +from tbc_video_export.process.wrapper.wrapper_ffmpeg import WrapperFFmpeg +from tbc_video_export.program_state import ProgramState + + +class TestWrappersFFmpeg(unittest.TestCase): + """Tests for ffmpeg wrapper.""" + + def setUp(self) -> None: # noqa: D102 + self.path = Path.joinpath(Path(__file__).parent, "files", "pal_svideo") + + self.config = ProgramConfig() + self.parse_opts = partial(opts_parser.parse_opts, self.config) + + self.pipe = PipeFactory.create_dummy_pipe() + + def test_ffmpeg_hwaccel_vaapi(self) -> None: # noqa: D102 + _, opts = self.parse_opts( + [ + str(self.path), + "pal_svideo", + "--x264", + "--vaapi", + "--hwaccel-device", + "TEST", + ] + ) + self.files = FileHelper(opts, self.config) + state = ProgramState(opts, self.config, self.files) + + ffmpeg_wrapper = WrapperFFmpeg( + state, + WrapperConfig[tuple[Pipe], None]( + state.current_export_mode, + TBCType.CHROMA, + input_pipes=(self.pipe, self.pipe), + output_pipes=None, + ), + ) + + self.assertTrue( + { + "-hwaccel", + "vaapi", + }.issubset(ffmpeg_wrapper.command.data) + ) + + self.assertTrue( + { + "-hwaccel_output_format", + "vaapi", + }.issubset(ffmpeg_wrapper.command.data) + ) + + self.assertTrue( + { + "-vaapi_device", + "TEST", + }.issubset(ffmpeg_wrapper.command.data) + ) + + self.assertTrue( + { + "-pix_fmt", + "nv12", + "-c:v", + "h264_vaapi", + }.issubset(ffmpeg_wrapper.command.data) + ) + + cmd = ffmpeg_wrapper.command.data + self.assertTrue(any(",format=nv12,hwupload[v_output]" in cmds for cmds in cmd)) + + def test_ffmpeg_hwaccel_nvenc(self) -> None: # noqa: D102 + _, opts = self.parse_opts( + [ + str(self.path), + "pal_svideo", + "--x264", + "--nvenc", + "--hwaccel-device", + "TEST", + ] + ) + self.files = FileHelper(opts, self.config) + state = ProgramState(opts, self.config, self.files) + + ffmpeg_wrapper = WrapperFFmpeg( + state, + WrapperConfig[tuple[Pipe], None]( + state.current_export_mode, + TBCType.CHROMA, + input_pipes=(self.pipe, self.pipe), + output_pipes=None, + ), + ) + + self.assertTrue( + { + "-gpu", + "TEST", + }.issubset(ffmpeg_wrapper.command.data) + ) + + self.assertTrue( + { + "-pix_fmt", + "nv12", + "-c:v", + "h264_nvenc", + }.issubset(ffmpeg_wrapper.command.data) + ) + + cmd = ffmpeg_wrapper.command.data + self.assertTrue(any(",format=nv12,hwupload[v_output]" in cmds for cmds in cmd)) + + def test_ffmpeg_hwaccel_quicksync(self) -> None: # noqa: D102 + _, opts = self.parse_opts( + [ + str(self.path), + "pal_svideo", + "--x264", + "--quicksync", + "--hwaccel-device", + "TEST", + ] + ) + self.files = FileHelper(opts, self.config) + state = ProgramState(opts, self.config, self.files) + + ffmpeg_wrapper = WrapperFFmpeg( + state, + WrapperConfig[tuple[Pipe], None]( + state.current_export_mode, + TBCType.CHROMA, + input_pipes=(self.pipe, self.pipe), + output_pipes=None, + ), + ) + + self.assertTrue( + { + "-hwaccel", + "qsv", + }.issubset(ffmpeg_wrapper.command.data) + ) + + self.assertTrue( + { + "-qsv_device", + "TEST", + }.issubset(ffmpeg_wrapper.command.data) + ) + + self.assertTrue( + { + "-pix_fmt", + "nv12", + "-c:v", + "h264_qsv", + }.issubset(ffmpeg_wrapper.command.data) + ) + + cmd = ffmpeg_wrapper.command.data + self.assertTrue(any(",format=nv12,hwupload[v_output]" in cmds for cmds in cmd)) + + def test_ffmpeg_hwaccel_amf(self) -> None: # noqa: D102 + _, opts = self.parse_opts( + [ + str(self.path), + "pal_svideo", + "--x264", + "--amf", + ] + ) + self.files = FileHelper(opts, self.config) + state = ProgramState(opts, self.config, self.files) + + ffmpeg_wrapper = WrapperFFmpeg( + state, + WrapperConfig[tuple[Pipe], None]( + state.current_export_mode, + TBCType.CHROMA, + input_pipes=(self.pipe, self.pipe), + output_pipes=None, + ), + ) + + self.assertTrue( + { + "-pix_fmt", + "nv12", + "-c:v", + "h264_amf", + }.issubset(ffmpeg_wrapper.command.data) + ) + + cmd = ffmpeg_wrapper.command.data + self.assertTrue(any(",format=nv12,hwupload[v_output]" in cmds for cmds in cmd)) + + def test_ffmpeg_hwaccel_amf_device(self) -> None: # noqa: D102 + _, opts = self.parse_opts( + [ + str(self.path), + "pal_svideo", + "--x264", + "--amf", + "--hwaccel-device", + "TEST", + ] + ) + self.files = FileHelper(opts, self.config) + state = ProgramState(opts, self.config, self.files) + + with self.assertRaises(InvalidProfileError): + WrapperFFmpeg( + state, + WrapperConfig[tuple[Pipe], None]( + state.current_export_mode, + TBCType.CHROMA, + input_pipes=(self.pipe, self.pipe), + output_pipes=None, + ), + ) + + def test_ffmpeg_hwaccel_videotoolbox(self) -> None: # noqa: D102 + _, opts = self.parse_opts( + [ + str(self.path), + "pal_svideo", + "--x264", + "--videotoolbox", + ] + ) + self.files = FileHelper(opts, self.config) + state = ProgramState(opts, self.config, self.files) + + ffmpeg_wrapper = WrapperFFmpeg( + state, + WrapperConfig[tuple[Pipe], None]( + state.current_export_mode, + TBCType.CHROMA, + input_pipes=(self.pipe, self.pipe), + output_pipes=None, + ), + ) + + self.assertTrue( + { + "-pix_fmt", + "yuv420p", + "-c:v", + "h264_videotoolbox", + }.issubset(ffmpeg_wrapper.command.data) + ) + + cmd = ffmpeg_wrapper.command.data + self.assertTrue( + any(",format=yuv420p,hwupload[v_output]" in cmds for cmds in cmd) + ) diff --git a/tests/test_wrappers_ntsc_composite.py b/tests/test_wrappers_ntsc_composite.py index 6894126..9fd36f3 100644 --- a/tests/test_wrappers_ntsc_composite.py +++ b/tests/test_wrappers_ntsc_composite.py @@ -165,7 +165,6 @@ def test_ffmpeg_opts_ntsc_cvbs(self) -> None: "-progress pipe:2", "-threads 4", "-nostdin", - "-hwaccel auto", "-color_range tv", "-thread_queue_size 1024", "-i PIPE_IN", @@ -279,7 +278,6 @@ def test_ffmpeg_opts_ntsc_cvbs_luma(self) -> None: "-progress pipe:2", "-threads 4", "-nostdin", - "-hwaccel auto", "-color_range tv", "-thread_queue_size 1024", "-i PIPE_IN", @@ -392,7 +390,6 @@ def test_ffmpeg_opts_ntsc_cvbs_luma_4fsc(self) -> None: "-progress pipe:2", "-threads 4", "-nostdin", - "-hwaccel auto", "-color_range tv", "-f rawvideo", "-pix_fmt gray16le", diff --git a/tests/test_wrappers_ntsc_composite_ld.py b/tests/test_wrappers_ntsc_composite_ld.py index 0fdb7a4..7fec044 100644 --- a/tests/test_wrappers_ntsc_composite_ld.py +++ b/tests/test_wrappers_ntsc_composite_ld.py @@ -183,7 +183,6 @@ def test_ffmpeg_opts_ntsc_ld(self) -> None: "-progress pipe:2", "-threads 4", "-nostdin", - "-hwaccel auto", "-color_range tv", "-thread_queue_size 1024", "-i PIPE_IN", @@ -297,7 +296,6 @@ def test_ffmpeg_opts_ntsc_ld_luma(self) -> None: "-progress pipe:2", "-threads 4", "-nostdin", - "-hwaccel auto", "-color_range tv", "-thread_queue_size 1024", "-i PIPE_IN", @@ -410,7 +408,6 @@ def test_ffmpeg_opts_ntsc_ld_luma_4fsc(self) -> None: "-progress pipe:2", "-threads 4", "-nostdin", - "-hwaccel auto", "-color_range tv", "-f rawvideo", "-pix_fmt gray16le", diff --git a/tests/test_wrappers_ntsc_svideo.py b/tests/test_wrappers_ntsc_svideo.py index 047b9ca..740870b 100644 --- a/tests/test_wrappers_ntsc_svideo.py +++ b/tests/test_wrappers_ntsc_svideo.py @@ -210,7 +210,6 @@ def test_ffmpeg_opts_ntsc_svideo(self) -> None: "-progress pipe:2", "-threads 4", "-nostdin", - "-hwaccel auto", "-color_range tv", "-thread_queue_size 1024", "-i PIPE_IN", @@ -324,7 +323,6 @@ def test_ffmpeg_opts_ntsc_svideo_luma(self) -> None: "-progress pipe:2", "-threads 4", "-nostdin", - "-hwaccel auto", "-color_range tv", "-thread_queue_size 1024", "-i PIPE_IN", @@ -437,7 +435,6 @@ def test_ffmpeg_opts_ntsc_svideo_luma_4fsc(self) -> None: "-progress pipe:2", "-threads 4", "-nostdin", - "-hwaccel auto", "-color_range tv", "-f rawvideo", "-pix_fmt gray16le", diff --git a/tests/test_wrappers_pal_composite_.py b/tests/test_wrappers_pal_composite_.py index a7ac0d7..3df773e 100644 --- a/tests/test_wrappers_pal_composite_.py +++ b/tests/test_wrappers_pal_composite_.py @@ -164,7 +164,6 @@ def test_default_ffmpeg_opts_cvbs_pal(self) -> None: "-progress pipe:2", "-threads 4", "-nostdin", - "-hwaccel auto", "-color_range tv", "-thread_queue_size 1024", "-i PIPE_IN", @@ -278,7 +277,6 @@ def test_ffmpeg_opts_pal_cvbs_luma(self) -> None: "-progress pipe:2", "-threads 4", "-nostdin", - "-hwaccel auto", "-color_range tv", "-thread_queue_size 1024", "-i PIPE_IN", @@ -391,7 +389,6 @@ def test_ffmpeg_opts_pal_cvbs_luma_4fsc(self) -> None: "-progress pipe:2", "-threads 4", "-nostdin", - "-hwaccel auto", "-color_range tv", "-f rawvideo", "-pix_fmt gray16le", diff --git a/tests/test_wrappers_pal_composite_ld.py b/tests/test_wrappers_pal_composite_ld.py index 1fc592f..39358fd 100644 --- a/tests/test_wrappers_pal_composite_ld.py +++ b/tests/test_wrappers_pal_composite_ld.py @@ -171,7 +171,6 @@ def test_ffmpeg_opts_pal_ld(self) -> None: "-progress pipe:2", "-threads 4", "-nostdin", - "-hwaccel auto", "-color_range tv", "-thread_queue_size 1024", "-i PIPE_IN", @@ -285,7 +284,6 @@ def test_ffmpeg_opts_pal_ld_luma(self) -> None: "-progress pipe:2", "-threads 4", "-nostdin", - "-hwaccel auto", "-color_range tv", "-thread_queue_size 1024", "-i PIPE_IN", @@ -398,7 +396,6 @@ def test_ffmpeg_opts_pal_ld_luma_4fsc(self) -> None: "-progress pipe:2", "-threads 4", "-nostdin", - "-hwaccel auto", "-color_range tv", "-f rawvideo", "-pix_fmt gray16le", diff --git a/tests/test_wrappers_pal_svideo.py b/tests/test_wrappers_pal_svideo.py index 4a8f6e0..1b851ff 100644 --- a/tests/test_wrappers_pal_svideo.py +++ b/tests/test_wrappers_pal_svideo.py @@ -208,7 +208,6 @@ def test_ffmpeg_opts_pal_svideo(self) -> None: "-progress pipe:2", "-threads 4", "-nostdin", - "-hwaccel auto", "-color_range tv", "-thread_queue_size 1024", "-i PIPE_IN", @@ -322,7 +321,6 @@ def test_ffmpeg_opts_pal_svideo_luma(self) -> None: "-progress pipe:2", "-threads 4", "-nostdin", - "-hwaccel auto", "-color_range tv", "-thread_queue_size 1024", "-i PIPE_IN", @@ -435,7 +433,6 @@ def test_ffmpeg_opts_pal_svideo_luma_4fsc(self) -> None: "-progress pipe:2", "-threads 4", "-nostdin", - "-hwaccel auto", "-color_range tv", "-f rawvideo", "-pix_fmt gray16le", diff --git a/tests/test_wrappers_palm_svideo.py b/tests/test_wrappers_palm_svideo.py index 7501468..459ad36 100644 --- a/tests/test_wrappers_palm_svideo.py +++ b/tests/test_wrappers_palm_svideo.py @@ -170,7 +170,6 @@ def test_ffmpeg_opts_palm_svideo(self) -> None: "-progress pipe:2", "-threads 4", "-nostdin", - "-hwaccel auto", "-color_range tv", "-thread_queue_size 1024", "-i PIPE_IN", @@ -284,7 +283,6 @@ def test_ffmpeg_opts_palm_svideo_luma(self) -> None: "-progress pipe:2", "-threads 4", "-nostdin", - "-hwaccel auto", "-color_range tv", "-thread_queue_size 1024", "-i PIPE_IN", @@ -397,7 +395,6 @@ def test_ffmpeg_opts_palm_svideo_luma_4fsc(self) -> None: "-progress pipe:2", "-threads 4", "-nostdin", - "-hwaccel auto", "-color_range tv", "-f rawvideo", "-pix_fmt gray16le",