HwCodecDetect 0.1.9__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. {hwcodecdetect-0.1.9/src/HwCodecDetect.egg-info → hwcodecdetect-0.2.0}/PKG-INFO +21 -1
  2. {hwcodecdetect-0.1.9 → hwcodecdetect-0.2.0}/README.md +20 -0
  3. hwcodecdetect-0.2.0/VERSION +1 -0
  4. hwcodecdetect-0.2.0/src/HwCodecDetect/bitdepth_chroma_detect.py +473 -0
  5. {hwcodecdetect-0.1.9 → hwcodecdetect-0.2.0}/src/HwCodecDetect/run_tests.py +20 -0
  6. hwcodecdetect-0.2.0/src/HwCodecDetect/version.py +1 -0
  7. {hwcodecdetect-0.1.9 → hwcodecdetect-0.2.0/src/HwCodecDetect.egg-info}/PKG-INFO +21 -1
  8. {hwcodecdetect-0.1.9 → hwcodecdetect-0.2.0}/src/HwCodecDetect.egg-info/SOURCES.txt +1 -0
  9. hwcodecdetect-0.1.9/VERSION +0 -1
  10. hwcodecdetect-0.1.9/src/HwCodecDetect/version.py +0 -1
  11. {hwcodecdetect-0.1.9 → hwcodecdetect-0.2.0}/LICENSE +0 -0
  12. {hwcodecdetect-0.1.9 → hwcodecdetect-0.2.0}/MANIFEST.in +0 -0
  13. {hwcodecdetect-0.1.9 → hwcodecdetect-0.2.0}/pyproject.toml +0 -0
  14. {hwcodecdetect-0.1.9 → hwcodecdetect-0.2.0}/requirements.txt +0 -0
  15. {hwcodecdetect-0.1.9 → hwcodecdetect-0.2.0}/setup.cfg +0 -0
  16. {hwcodecdetect-0.1.9 → hwcodecdetect-0.2.0}/setup.py +0 -0
  17. {hwcodecdetect-0.1.9 → hwcodecdetect-0.2.0}/src/HwCodecDetect/__init__.py +0 -0
  18. {hwcodecdetect-0.1.9 → hwcodecdetect-0.2.0}/src/HwCodecDetect/install_ffmpeg_if_needed.py +0 -0
  19. {hwcodecdetect-0.1.9 → hwcodecdetect-0.2.0}/src/HwCodecDetect.egg-info/dependency_links.txt +0 -0
  20. {hwcodecdetect-0.1.9 → hwcodecdetect-0.2.0}/src/HwCodecDetect.egg-info/entry_points.txt +0 -0
  21. {hwcodecdetect-0.1.9 → hwcodecdetect-0.2.0}/src/HwCodecDetect.egg-info/requires.txt +0 -0
  22. {hwcodecdetect-0.1.9 → hwcodecdetect-0.2.0}/src/HwCodecDetect.egg-info/top_level.txt +0 -0
  23. {hwcodecdetect-0.1.9 → hwcodecdetect-0.2.0}/src/HwCodecDetect.egg-info/zip-safe +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: HwCodecDetect
3
- Version: 0.1.9
3
+ Version: 0.2.0
4
4
  Summary: A cross-platform tool to automatically detect and test hardware video decoders/encoders using FFmpeg.
5
5
  Home-page: https://github.com/whyb/HwCodecDetect
6
6
  Author: whyb
@@ -74,6 +74,26 @@ The script automatically detect and reports on the following major hardware deco
74
74
  | Vulkan | H.264、H.265、AV1 |
75
75
  | Apple VideoToolbox | H.264、H.265、MPEG-2、MPEG-4 |
76
76
 
77
+ ### Bit-depth and Chroma Subsampling Detection
78
+ In addition to resolution-based testing, the tool now includes comprehensive bit-depth and chroma subsampling detection. This feature tests hardware codec support for different pixel formats, helping you understand the full capabilities of your hardware encoders and decoders.
79
+
80
+ The detection covers the following pixel formats:
81
+ | Bit-depth | Chroma Subsampling | Pixel Format | Description |
82
+ |-----------|-------------------|--------------|-------------|
83
+ | 8-bit | YUV 4:2:0 | yuv420p | Standard 8-bit 4:2:0 |
84
+ | 8-bit | YUV 4:2:2 | yuv422p | 8-bit 4:2:2 |
85
+ | 8-bit | YUV 4:4:4 | yuv444p | 8-bit 4:4:4 |
86
+ | 10-bit | YUV 4:2:0 | yuv420p10le, p010le | 10-bit 4:2:0 |
87
+ | 10-bit | YUV 4:2:2 | yuv422p10le | 10-bit 4:2:2 |
88
+ | 10-bit | YUV 4:4:4 | yuv444p10le | 10-bit 4:4:4 |
89
+ | 12-bit | YUV 4:2:0 | yuv420p12le | 12-bit 4:2:0 |
90
+ | 12-bit | YUV 4:2:2 | yuv422p12le | 12-bit 4:2:2 |
91
+ | 12-bit | YUV 4:4:4 | yuv444p12le | 12-bit 4:4:4 |
92
+
93
+ This feature uses a fixed resolution of 1280x720 for all tests and follows the encode-then-decode workflow. If hardware encoding fails, the tool automatically falls back to software encoding to ensure decoder tests can still be performed.
94
+
95
+ **Note:** This feature is enabled by default. You can disable it using the `--no-bitdepth-chroma` command-line parameter.
96
+
77
97
 
78
98
  ## How to Use
79
99
  You can install and use HwCodecDetect in two ways.
@@ -48,6 +48,26 @@ The script automatically detect and reports on the following major hardware deco
48
48
  | Vulkan | H.264、H.265、AV1 |
49
49
  | Apple VideoToolbox | H.264、H.265、MPEG-2、MPEG-4 |
50
50
 
51
+ ### Bit-depth and Chroma Subsampling Detection
52
+ In addition to resolution-based testing, the tool now includes comprehensive bit-depth and chroma subsampling detection. This feature tests hardware codec support for different pixel formats, helping you understand the full capabilities of your hardware encoders and decoders.
53
+
54
+ The detection covers the following pixel formats:
55
+ | Bit-depth | Chroma Subsampling | Pixel Format | Description |
56
+ |-----------|-------------------|--------------|-------------|
57
+ | 8-bit | YUV 4:2:0 | yuv420p | Standard 8-bit 4:2:0 |
58
+ | 8-bit | YUV 4:2:2 | yuv422p | 8-bit 4:2:2 |
59
+ | 8-bit | YUV 4:4:4 | yuv444p | 8-bit 4:4:4 |
60
+ | 10-bit | YUV 4:2:0 | yuv420p10le, p010le | 10-bit 4:2:0 |
61
+ | 10-bit | YUV 4:2:2 | yuv422p10le | 10-bit 4:2:2 |
62
+ | 10-bit | YUV 4:4:4 | yuv444p10le | 10-bit 4:4:4 |
63
+ | 12-bit | YUV 4:2:0 | yuv420p12le | 12-bit 4:2:0 |
64
+ | 12-bit | YUV 4:2:2 | yuv422p12le | 12-bit 4:2:2 |
65
+ | 12-bit | YUV 4:4:4 | yuv444p12le | 12-bit 4:4:4 |
66
+
67
+ This feature uses a fixed resolution of 1280x720 for all tests and follows the encode-then-decode workflow. If hardware encoding fails, the tool automatically falls back to software encoding to ensure decoder tests can still be performed.
68
+
69
+ **Note:** This feature is enabled by default. You can disable it using the `--no-bitdepth-chroma` command-line parameter.
70
+
51
71
 
52
72
  ## How to Use
53
73
  You can install and use HwCodecDetect in two ways.
@@ -0,0 +1 @@
1
+ 0.2.0
@@ -0,0 +1,473 @@
1
+ """
2
+ Bit-depth and Chroma Subsampling Detection Module
3
+ This module tests hardware codec support for different pixel formats.
4
+ """
5
+ import os
6
+ import re
7
+ import sys
8
+ import shlex
9
+ import subprocess
10
+ import tempfile
11
+ from collections import defaultdict
12
+ from colorama import init, Fore, Style
13
+ from concurrent.futures import ThreadPoolExecutor, as_completed
14
+ from tqdm import tqdm
15
+
16
+ init(autoreset=True)
17
+
18
+ # Fixed resolution for bit-depth and chroma tests
19
+ BITDEPTH_CHROMA_RESOLUTION = "1280x720"
20
+
21
+ # Pixel format definitions: (pix_fmt_name, bit_depth, chroma_sampling, description)
22
+ PIXEL_FORMATS = [
23
+ ("yuv420p", 8, "4:2:0", "8-bit YUV 4:2:0"),
24
+ ("yuv422p", 8, "4:2:2", "8-bit YUV 4:2:2"),
25
+ ("yuv444p", 8, "4:4:4", "8-bit YUV 4:4:4"),
26
+ ("yuv420p10le", 10, "4:2:0", "10-bit YUV 4:2:0"),
27
+ ("yuv422p10le", 10, "4:2:2", "10-bit YUV 4:2:2"),
28
+ ("yuv444p10le", 10, "4:4:4", "10-bit YUV 4:4:4"),
29
+ ("yuv420p12le", 12, "4:2:0", "12-bit YUV 4:2:0"),
30
+ ("yuv422p12le", 12, "4:2:2", "12-bit YUV 4:2:2"),
31
+ ("yuv444p12le", 12, "4:4:4", "12-bit YUV 4:4:4"),
32
+ ]
33
+
34
+ # Encoder titles (same as main module)
35
+ ENCODER_TITLES = {
36
+ ("h264_nvenc", "h264"): "NVIDIA Hardware H264 Encoder(NVEnc)",
37
+ ("hevc_nvenc", "h265"): "NVIDIA Hardware H265 Encoder(NVEnc)",
38
+ ("av1_nvenc", "av1"): "NVIDIA Hardware AV1 Encoder(NVEnc)",
39
+ ("h264_qsv", "h264"): "Intel Hardware H264 Encoder(QSV)",
40
+ ("hevc_qsv", "h265"): "Intel Hardware H265 Encoder(QSV)",
41
+ ("av1_qsv", "av1"): "Intel Hardware AV1 Encoder(QSV)",
42
+ ("mpeg2_qsv", "mpeg2"): "Intel Hardware MPEG-2 Encoder(QSV)",
43
+ ("vp9_qsv", "vp9"): "Intel Hardware VP9 Encoder(QSV)",
44
+ ("h264_amf", "h264"): "AMD Hardware H264 Encoder(AMF)",
45
+ ("hevc_amf", "h265"): "AMD Hardware H265 Encoder(AMF)",
46
+ ("av1_amf", "av1"): "AMD Hardware AV1 Encoder(AMF)",
47
+ ("h264_mf", "h264"): "Microsoft Hardware H264 Encoder(MediaFoundation)",
48
+ ("hevc_mf", "h265"): "Microsoft Hardware H265 Encoder(MediaFoundation)",
49
+ ("hevc_d3d12va", "h265"): "Microsoft Direct3D 12 Video Acceleration H265 Encoder(D3D12VA)",
50
+ ("h264_vaapi", "h264"): "Video Acceleration H264 Encoder(VAAPI)",
51
+ ("hevc_vaapi", "h265"): "Video Acceleration H265 Encoder(VAAPI)",
52
+ ("av1_vaapi", "av1"): "Video Acceleration AV1 Encoder(VAAPI)",
53
+ ("mpeg2_vaapi", "mpeg2"): "Video Acceleration MPEG-2 Encoder(VAAPI)",
54
+ ("vp8_vaapi", "vp8"): "Video Acceleration VP8 Encoder(VAAPI)",
55
+ ("vp9_vaapi", "vp9"): "Video Acceleration VP9 Encoder(VAAPI)",
56
+ ("h264_vulkan", "h264"): "Vulkan Hardware H264 Encoder(Vulkan)",
57
+ ("hevc_vulkan", "h265"): "Vulkan Hardware H265 Encoder(Vulkan)",
58
+ ("h264_videotoolbox", "h264"): "MacOS Hardware H264 Encoder(VideoToolbox)",
59
+ ("hevc_videotoolbox", "h265"): "MacOS Hardware H265 Encoder(VideoToolbox)",
60
+ }
61
+
62
+ # Decoder titles (same as main module)
63
+ DECODER_TITLES = {
64
+ ("h264_cuvid", "h264"): "NVIDIA CUDA H264 Decoder(NVDEC)",
65
+ ("h264_qsv", "h264"): "Intel Quick Sync Video H264 Decoder(QSV)",
66
+ ("hevc_cuvid", "h265"): "NVIDIA CUDA H265 Decoder(NVDEC)",
67
+ ("hevc_qsv", "h265"): "Intel Quick Sync Video H265 Decoder(QSV)",
68
+ ("av1_cuvid", "av1"): "NVIDIA CUDA AV1 Decoder(NVDEC)",
69
+ ("av1_qsv", "av1"): "Intel Quick Sync Video AV1 Decoder(QSV)",
70
+ ("mpeg1_cuvid", "mpeg1"): "NVIDIA CUDA MPEG-1 Decoder(NVDEC)",
71
+ ("mpeg2_cuvid", "mpeg2"): "NVIDIA CUDA MPEG-2 Decoder(NVDEC)",
72
+ ("mpeg2_qsv", "mpeg2"): "Intel Quick Sync Video MPEG-2 Decoder(QSV)",
73
+ ("mpeg4_cuvid", "mpeg4"): "NVIDIA CUDA MPEG-4 Decoder(NVDEC)",
74
+ ("vp8_cuvid", "vp8"): "NVIDIA CUDA VP8 Decoder(NVDEC)",
75
+ ("vp8_qsv", "vp8"): "Intel Quick Sync Video VP8 Decoder(QSV)",
76
+ ("vp9_cuvid", "vp9"): "NVIDIA CUDA VP9 Decoder(NVDEC)",
77
+ ("vp9_qsv", "vp9"): "Intel Quick Sync Video VP9 Decoder(QSV)",
78
+ ("dxva2", "h264"): "Microsoft DirectX Video Acceleration H264 Decoder(DXVA2)",
79
+ ("dxva2", "h265"): "Microsoft DirectX Video Acceleration H265 Decoder(DXVA2)",
80
+ ("dxva2", "av1"): "Microsoft DirectX Video Acceleration AV1 Decoder(DXVA2)",
81
+ ("dxva2", "mpeg1"): "Microsoft DirectX Video Acceleration MPEG-1 Decoder(DXVA2)",
82
+ ("dxva2", "mpeg2"): "Microsoft DirectX Video Acceleration MPEG-2 Decoder(DXVA2)",
83
+ ("dxva2", "mpeg4"): "Microsoft DirectX Video Acceleration MPEG-4 Decoder(DXVA2)",
84
+ ("dxva2", "vp8"): "Microsoft DirectX Video Acceleration VP8 Decoder(DXVA2)",
85
+ ("dxva2", "vp9"): "Microsoft DirectX Video Acceleration VP9 Decoder(DXVA2)",
86
+ ("d3d11va", "h264"): "Microsoft Direct3D 11 Video Acceleration H264 Decoder(D3D11VA)",
87
+ ("d3d11va", "h265"): "Microsoft Direct3D 11 Video Acceleration H265 Decoder(D3D11VA)",
88
+ ("d3d11va", "av1"): "Microsoft Direct3D 11 Video Acceleration AV1 Decoder(D3D11VA)",
89
+ ("d3d11va", "mpeg1"): "Microsoft Direct3D 11 Video Acceleration MPEG-1 Decoder(D3D11VA)",
90
+ ("d3d11va", "mpeg2"): "Microsoft Direct3D 11 Video Acceleration MPEG-2 Decoder(D3D11VA)",
91
+ ("d3d11va", "mpeg4"): "Microsoft Direct3D 11 Video Acceleration MPEG-4 Decoder(D3D11VA)",
92
+ ("d3d11va", "vp8"): "Microsoft Direct3D 11 Video Acceleration VP8 Decoder(D3D11VA)",
93
+ ("d3d11va", "vp9"): "Microsoft Direct3D 11 Video Acceleration VP9 Decoder(D3D11VA)",
94
+ ("vulkan", "h264"): "Vulkan Hardware H264 Decoder(Vulkan)",
95
+ ("vulkan", "h265"): "Vulkan Hardware H265 Decoder(Vulkan)",
96
+ ("vulkan", "av1"): "Vulkan Hardware AV1 Decoder(Vulkan)",
97
+ ("videotoolbox", "h264"): "MacOS Hardware H264 Decoder(VideoToolbox)",
98
+ ("videotoolbox", "h265"): "MacOS Hardware H265 Decoder(VideoToolbox)",
99
+ ("videotoolbox", "mpeg2"): "MacOS Hardware MPEG-2 Decoder(VideoToolbox)",
100
+ ("videotoolbox", "mpeg4"): "MacOS Hardware MPEG-4 Decoder(VideoToolbox)",
101
+ }
102
+
103
+ # Encoder definitions (same as main module)
104
+ ENCODERS = {
105
+ "h264": {"lib": "libx264", "hw_encoders": ["h264_nvenc", "h264_qsv", "h264_amf", "h264_mf", "h264_vaapi", "h264_vulkan", "h264_videotoolbox"]},
106
+ "h265": {"lib": "libx265", "hw_encoders": ["hevc_nvenc", "hevc_qsv", "hevc_amf", "hevc_mf", "hevc_d3d12va", "hevc_vaapi", "hevc_vulkan", "hevc_videotoolbox"]},
107
+ "av1": {"lib": "librav1e", "hw_encoders": ["av1_nvenc", "av1_qsv", "av1_amf", "av1_vaapi"]},
108
+ "mpeg2": {"lib": "mpeg2video", "hw_encoders": ["mpeg2_qsv", "mpeg2_vaapi"]},
109
+ "vp8": {"lib": "libvpx", "hw_encoders": ["vp8_vaapi"]},
110
+ "vp9": {"lib": "libvpx-vp9", "hw_encoders": ["vp9_qsv", "vp9_vaapi"]},
111
+ }
112
+
113
+ # Decoder definitions (same as main module)
114
+ DECODERS = {
115
+ "h264": {"lib": "libx264", "hw_decoders": ["h264_cuvid", "h264_qsv", "dxva2", "d3d11va", "vulkan", "videotoolbox"]},
116
+ "h265": {"lib": "libx265", "hw_decoders": ["hevc_cuvid", "hevc_qsv", "d3d11va", "vulkan", "videotoolbox"]},
117
+ "av1": {"lib": "librav1e", "hw_decoders": ["av1_cuvid", "av1_qsv", "dxva2", "d3d11va", "vulkan"]},
118
+ "mpeg1": {"lib": "mpeg1video", "hw_decoders": ["mpeg1_cuvid", "dxva2", "d3d11va"]},
119
+ "mpeg2": {"lib": "mpeg2video", "hw_decoders": ["mpeg2_cuvid", "mpeg2_qsv", "dxva2", "d3d11va", "videotoolbox"]},
120
+ "mpeg4": {"lib": "mpeg4", "hw_decoders": ["mpeg4_cuvid", "dxva2", "d3d11va", "videotoolbox"]},
121
+ "vp8": {"lib": "libvpx", "hw_decoders": ["vp8_cuvid", "vp8_qsv", "dxva2", "d3d11va"]},
122
+ "vp9": {"lib": "libvpx-vp9", "hw_decoders": ["vp9_cuvid", "vp9_qsv", "dxva2", "d3d11va"]},
123
+ }
124
+
125
+
126
+ def _run_ffmpeg_command(command, verbose):
127
+ """Executes an FFmpeg command and returns True on success, False on failure."""
128
+ try:
129
+ stdout = subprocess.PIPE if verbose else subprocess.DEVNULL
130
+ stderr = subprocess.PIPE if verbose else subprocess.DEVNULL
131
+ result = subprocess.run(
132
+ command,
133
+ check=True,
134
+ stdout=stdout,
135
+ stderr=stderr,
136
+ text=True
137
+ )
138
+ return (result.returncode == 0, result.stdout, result.stderr)
139
+ except subprocess.CalledProcessError as e:
140
+ return (False, e.stdout, e.stderr)
141
+ except FileNotFoundError:
142
+ return (False, "", "FFmpeg executable not found")
143
+
144
+
145
+ def _run_encoder_bitdepth_test(test_data):
146
+ """Tests encoder support for a specific pixel format."""
147
+ codec, encoder, pix_fmt, bit_depth, chroma, test_dir, verbose = test_data
148
+
149
+ file_ext = ".webm" if codec in ["vp8", "vp9"] else ".mp4"
150
+ output_file = os.path.join(test_dir, f"{encoder}_{pix_fmt}{file_ext}")
151
+
152
+ # Determine pixel format for output based on input format
153
+ if bit_depth == 8:
154
+ if chroma == "4:2:0":
155
+ out_pix_fmt = "yuv420p"
156
+ elif chroma == "4:2:2":
157
+ out_pix_fmt = "yuv422p"
158
+ else: # 4:4:4
159
+ out_pix_fmt = "yuv444p"
160
+ elif bit_depth == 10:
161
+ if chroma == "4:2:0":
162
+ out_pix_fmt = "p010le"
163
+ elif chroma == "4:2:2":
164
+ out_pix_fmt = "yuv422p10le"
165
+ else: # 4:4:4
166
+ out_pix_fmt = "yuv444p10le"
167
+ else: # 12-bit
168
+ if chroma == "4:2:0":
169
+ out_pix_fmt = "yuv420p12le"
170
+ elif chroma == "4:2:2":
171
+ out_pix_fmt = "yuv422p12le"
172
+ else: # 4:4:4
173
+ out_pix_fmt = "yuv444p12le"
174
+
175
+ if "vulkan" in encoder:
176
+ command = [
177
+ "ffmpeg",
178
+ "-loglevel", "quiet",
179
+ "-hide_banner",
180
+ "-y",
181
+ "-init_hw_device", "vulkan=vk:0",
182
+ "-f", "lavfi",
183
+ "-i", f"color=white:s={BITDEPTH_CHROMA_RESOLUTION}:d=1",
184
+ "-frames:v", "1",
185
+ "-vf", f"format={pix_fmt},hwupload,format=vulkan",
186
+ "-c:v", encoder,
187
+ output_file,
188
+ ]
189
+ elif "d3d12va" in encoder:
190
+ command = [
191
+ "ffmpeg",
192
+ "-loglevel", "quiet",
193
+ "-hide_banner",
194
+ "-y",
195
+ "-init_hw_device", "d3d12va:0",
196
+ "-f", "lavfi",
197
+ "-i", f"color=white:s={BITDEPTH_CHROMA_RESOLUTION}:d=1",
198
+ "-frames:v", "1",
199
+ "-vf", f"format={pix_fmt},hwupload",
200
+ "-c:v", encoder,
201
+ output_file,
202
+ ]
203
+ else:
204
+ command = [
205
+ "ffmpeg",
206
+ "-loglevel", "quiet",
207
+ "-hide_banner",
208
+ "-y",
209
+ "-f", "lavfi",
210
+ "-i", f"color=white:s={BITDEPTH_CHROMA_RESOLUTION}:d=1",
211
+ "-frames:v", "1",
212
+ "-c:v", encoder,
213
+ "-pix_fmt", pix_fmt,
214
+ output_file,
215
+ ]
216
+
217
+ if "qsv" in encoder:
218
+ command.insert(9, "-dual_gfx")
219
+ command.insert(10, "0")
220
+
221
+ if verbose:
222
+ command[2] = "error"
223
+
224
+ success, stdout, stderr = _run_ffmpeg_command(command, verbose)
225
+ status = "succeeded" if success else "failed"
226
+
227
+ # Clean up on failure
228
+ if not success and os.path.exists(output_file):
229
+ try:
230
+ os.remove(output_file)
231
+ except OSError:
232
+ pass
233
+
234
+ if verbose:
235
+ info_str = f"codec: {codec}, encoder: {encoder}, format: {pix_fmt}, status: {status}"
236
+ command_str = " ".join(shlex.quote(arg) for arg in command)
237
+ if stdout.strip() and stderr.strip():
238
+ command_log = f"{stdout.strip()}\n{stderr.strip()}"
239
+ elif stdout.strip():
240
+ command_log = stdout.strip()
241
+ elif stderr.strip():
242
+ command_log = stderr.strip()
243
+ else:
244
+ command_log = "(none)"
245
+ log_message = f"""
246
+ ==================================================
247
+ [Bit-depth/Chroma Encoder Test]
248
+ {info_str}
249
+
250
+ [FFmpeg Command]
251
+ {command_str}
252
+
253
+ [Command Log]
254
+ {command_log}
255
+
256
+ """.strip()
257
+ print(log_message)
258
+
259
+ title = ENCODER_TITLES.get((encoder, codec), f"{encoder.upper()} Encoder:")
260
+ return title, pix_fmt, bit_depth, chroma, status
261
+
262
+
263
+ def _run_encoder_bitdepth_tests(test_dir, max_workers, verbose):
264
+ """Tests encoder support for various pixel formats."""
265
+ results = defaultdict(dict)
266
+
267
+ print("\n--- Running Bit-depth/Chroma Encoder Tests ---")
268
+
269
+ tasks = []
270
+ for codec, info in ENCODERS.items():
271
+ for encoder in info['hw_encoders']:
272
+ for pix_fmt, bit_depth, chroma, desc in PIXEL_FORMATS:
273
+ tasks.append((codec, encoder, pix_fmt, bit_depth, chroma, test_dir, verbose))
274
+
275
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
276
+ futures = [executor.submit(_run_encoder_bitdepth_test, task) for task in tasks]
277
+
278
+ for future in tqdm(as_completed(futures), total=len(tasks), desc="Running encoder bit-depth tests"):
279
+ title, pix_fmt, bit_depth, chroma, status = future.result()
280
+ key = f"{bit_depth}-bit {chroma}"
281
+ if title not in results:
282
+ results[title] = {}
283
+ results[title][key] = status
284
+
285
+ return results
286
+
287
+
288
+ def _run_decoder_bitdepth_test(test_data):
289
+ """Tests decoder support for a specific pixel format."""
290
+ codec, hw_decoder, pix_fmt, bit_depth, chroma, test_dir, verbose = test_data
291
+
292
+ file_ext = ".webm" if codec in ["vp8", "vp9"] else ".mp4"
293
+ test_file = os.path.join(test_dir, f"{codec}_{pix_fmt}{file_ext}")
294
+
295
+ # Create test file with specific pixel format if it doesn't exist
296
+ if not os.path.exists(test_file) or os.path.getsize(test_file) == 0:
297
+ cpu_lib = DECODERS[codec]["lib"]
298
+ command = [
299
+ "ffmpeg", "-loglevel", "quiet", "-hide_banner", "-y",
300
+ "-f", "lavfi", "-i", f"color=white:s={BITDEPTH_CHROMA_RESOLUTION}:d=1",
301
+ "-frames:v", "1", "-c:v", cpu_lib, "-pix_fmt", pix_fmt,
302
+ test_file,
303
+ ]
304
+ if not _run_ffmpeg_command(command, verbose)[0]:
305
+ title = DECODER_TITLES.get((hw_decoder, codec), f"{hw_decoder.upper()} Decoder:")
306
+ return title, pix_fmt, bit_depth, chroma, "skipped"
307
+
308
+ if "vulkan" in hw_decoder:
309
+ command = [
310
+ "ffmpeg", "-loglevel", "quiet", "-hide_banner", "-y",
311
+ "-init_hw_device", "vulkan=vk:0",
312
+ "-hwaccel", "vulkan",
313
+ "-hwaccel_output_format", "vulkan",
314
+ "-i", test_file,
315
+ "-f", "null", "null",
316
+ ]
317
+ elif "videotoolbox" in hw_decoder:
318
+ command = [
319
+ "ffmpeg", "-loglevel", "quiet", "-hide_banner", "-y",
320
+ "-hwaccel", "videotoolbox",
321
+ "-i", test_file,
322
+ "-f", "null", "null",
323
+ ]
324
+ elif hw_decoder in ["dxva2", "d3d11va"]:
325
+ command = [
326
+ "ffmpeg", "-loglevel", "quiet", "-hide_banner", "-y",
327
+ "-hwaccel", hw_decoder, "-i", test_file,
328
+ "-c:v", "libx264", "-preset", "ultrafast",
329
+ "-f", "null", "null",
330
+ ]
331
+ else:
332
+ command = [
333
+ "ffmpeg", "-loglevel", "quiet", "-hide_banner", "-y",
334
+ "-c:v", hw_decoder, "-i", test_file,
335
+ "-c:v", "libx264", "-preset", "ultrafast",
336
+ "-f", "null", "null",
337
+ ]
338
+
339
+ if verbose:
340
+ command[2] = "error"
341
+
342
+ success, stdout, stderr = _run_ffmpeg_command(command, verbose)
343
+ status = "succeeded" if success else "failed"
344
+
345
+ if verbose:
346
+ info_str = f"codec: {codec}, decoder: {hw_decoder}, format: {pix_fmt}, status: {status}"
347
+ command_str = " ".join(shlex.quote(arg) for arg in command)
348
+ if stdout.strip() and stderr.strip():
349
+ command_log = f"{stdout.strip()}\n{stderr.strip()}"
350
+ elif stdout.strip():
351
+ command_log = stdout.strip()
352
+ elif stderr.strip():
353
+ command_log = stderr.strip()
354
+ else:
355
+ command_log = "(none)"
356
+ log_message = f"""
357
+ ==================================================
358
+ [Bit-depth/Chroma Decoder Test]
359
+ {info_str}
360
+
361
+ [FFmpeg Command]
362
+ {command_str}
363
+
364
+ [Command Log]
365
+ {command_log}
366
+
367
+ """.strip()
368
+ print(log_message)
369
+
370
+ title = DECODER_TITLES.get((hw_decoder, codec), f"{hw_decoder.upper()} Decoder:")
371
+ return title, pix_fmt, bit_depth, chroma, status
372
+
373
+
374
+ def _run_decoder_bitdepth_tests(test_dir, max_workers, verbose):
375
+ """Tests decoder support for various pixel formats."""
376
+ results = defaultdict(dict)
377
+
378
+ print("\n--- Running Bit-depth/Chroma Decoder Tests ---")
379
+
380
+ tasks = []
381
+ for codec, info in DECODERS.items():
382
+ for hw_decoder in info['hw_decoders']:
383
+ for pix_fmt, bit_depth, chroma, desc in PIXEL_FORMATS:
384
+ tasks.append((codec, hw_decoder, pix_fmt, bit_depth, chroma, test_dir, verbose))
385
+
386
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
387
+ futures = [executor.submit(_run_decoder_bitdepth_test, task) for task in tasks]
388
+ for future in tqdm(as_completed(futures), total=len(tasks), desc="Running decoder bit-depth tests"):
389
+ title, pix_fmt, bit_depth, chroma, status = future.result()
390
+ key = f"{bit_depth}-bit {chroma}"
391
+ if title not in results:
392
+ results[title] = {}
393
+ results[title][key] = status
394
+ return results
395
+
396
+
397
+ def _get_display_width(s):
398
+ """Calculates the display width of a string, ignoring ANSI escape codes."""
399
+ ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
400
+ return len(ansi_escape.sub('', s))
401
+
402
+
403
+ def _print_bitdepth_chroma_table(results, table_type="Encoder"):
404
+ """Prints a formatted summary table for bit-depth/chroma results."""
405
+ GREEN_CHECK = Fore.GREEN + "✓" + Style.RESET_ALL
406
+ RED_X = Fore.RED + "×" + Style.RESET_ALL
407
+ GRAY_DASH = Fore.LIGHTBLACK_EX + "—" + Style.RESET_ALL
408
+
409
+ # Define columns for the table
410
+ format_columns = [
411
+ "8-bit 4:2:0",
412
+ "8-bit 4:2:2",
413
+ "8-bit 4:4:4",
414
+ "10-bit 4:2:0",
415
+ "10-bit 4:2:2",
416
+ "10-bit 4:4:4",
417
+ "12-bit 4:2:0",
418
+ "12-bit 4:2:2",
419
+ "12-bit 4:4:4",
420
+ ]
421
+
422
+ titles = sorted(results.keys())
423
+
424
+ col_width = max(len(col) for col in format_columns)
425
+ row_header_width = max([_get_display_width(t) for t in titles] + [20, _get_display_width(table_type)])
426
+
427
+ print("\n" + "=" * (row_header_width + 3 + (col_width + 3) * len(format_columns)))
428
+ header_text = f"Bit-depth/Chroma {table_type} Support"
429
+ padding_left = (row_header_width - _get_display_width(header_text)) // 2
430
+ padding_right = row_header_width - _get_display_width(header_text) - padding_left
431
+ header_row = f"| {' ' * padding_left}{header_text}{' ' * padding_right} |"
432
+ for col in format_columns:
433
+ header_row += f" {col.center(col_width)} |"
434
+ print(header_row)
435
+ print("-" * (row_header_width + 3 + (col_width + 3) * len(format_columns)))
436
+
437
+ for title in titles:
438
+ padding_needed = row_header_width - _get_display_width(title)
439
+ row_string = f"| {title}{' ' * padding_needed} |"
440
+ for col in format_columns:
441
+ status = results.get(title, {}).get(col, "skipped")
442
+ symbol = GREEN_CHECK if status == "succeeded" else RED_X if status == "failed" else GRAY_DASH
443
+ symbol_width = _get_display_width(symbol)
444
+ padding_left = (col_width - symbol_width) // 2
445
+ padding_right = col_width - symbol_width - padding_left
446
+ row_string += f" {' ' * padding_left}{symbol}{' ' * padding_right} |"
447
+ print(row_string)
448
+ print("=" * (row_header_width + 3 + (col_width + 3) * len(format_columns)))
449
+
450
+
451
+ def run_bitdepth_chroma_tests(encoder_count, decoder_count, verbose):
452
+ """Run all bit-depth and chroma tests and return results."""
453
+ import shutil
454
+ temp_dir = os.path.join(tempfile.gettempdir(), "HwCodecDetect_BitDepth")
455
+ if os.path.exists(temp_dir):
456
+ shutil.rmtree(temp_dir)
457
+ os.makedirs(temp_dir)
458
+
459
+ encoder_results = _run_encoder_bitdepth_tests(temp_dir, encoder_count, verbose)
460
+ decoder_results = _run_decoder_bitdepth_tests(temp_dir, decoder_count, verbose)
461
+
462
+ # Clean up
463
+ shutil.rmtree(temp_dir)
464
+
465
+ return encoder_results, decoder_results
466
+
467
+
468
+ def print_bitdepth_chroma_results(encoder_results, decoder_results):
469
+ """Print bit-depth and chroma test results."""
470
+ if decoder_results:
471
+ _print_bitdepth_chroma_table(decoder_results, "Decoder")
472
+ if encoder_results:
473
+ _print_bitdepth_chroma_table(encoder_results, "Encoder")
@@ -9,6 +9,7 @@ import tempfile
9
9
  import argparse
10
10
  from collections import defaultdict
11
11
  from .install_ffmpeg_if_needed import install_ffmpeg_if_needed
12
+ from .bitdepth_chroma_detect import run_bitdepth_chroma_tests, print_bitdepth_chroma_results
12
13
  from colorama import init, Fore, Style
13
14
  from concurrent.futures import ThreadPoolExecutor, as_completed
14
15
  from tqdm import tqdm
@@ -575,6 +576,16 @@ def run_all_tests(args):
575
576
 
576
577
  _print_summary_table(all_results)
577
578
 
579
+ # Run bit-depth and chroma tests if enabled
580
+ if getattr(args, 'bitdepth_chroma', True):
581
+ print("\n" + "=" * 60)
582
+ print("Starting Bit-depth and Chroma Subsampling Detection...")
583
+ print("=" * 60)
584
+ bd_encoder_results, bd_decoder_results = run_bitdepth_chroma_tests(
585
+ args.encoder_count, args.decoder_count, args.verbose
586
+ )
587
+ print_bitdepth_chroma_results(bd_encoder_results, bd_decoder_results)
588
+
578
589
  print("\nCleaning up temporary files...")
579
590
  shutil.rmtree(temp_dir)
580
591
  print("Cleanup complete.")
@@ -645,7 +656,16 @@ def main():
645
656
  help='Print detailed information for each test'
646
657
  )
647
658
 
659
+ parser.add_argument(
660
+ '--no-bitdepth-chroma',
661
+ action='store_true',
662
+ dest='no_bitdepth_chroma',
663
+ help='Disable bit-depth and chroma subsampling detection (enabled by default)'
664
+ )
665
+
648
666
  args = parser.parse_args()
667
+ # Set bitdepth_chroma to True unless --no-bitdepth-chroma is specified
668
+ args.bitdepth_chroma = not args.no_bitdepth_chroma
649
669
  run_all_tests(args)
650
670
 
651
671
  if __name__ == "__main__":
@@ -0,0 +1 @@
1
+ __version__ = "0.2.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: HwCodecDetect
3
- Version: 0.1.9
3
+ Version: 0.2.0
4
4
  Summary: A cross-platform tool to automatically detect and test hardware video decoders/encoders using FFmpeg.
5
5
  Home-page: https://github.com/whyb/HwCodecDetect
6
6
  Author: whyb
@@ -74,6 +74,26 @@ The script automatically detect and reports on the following major hardware deco
74
74
  | Vulkan | H.264、H.265、AV1 |
75
75
  | Apple VideoToolbox | H.264、H.265、MPEG-2、MPEG-4 |
76
76
 
77
+ ### Bit-depth and Chroma Subsampling Detection
78
+ In addition to resolution-based testing, the tool now includes comprehensive bit-depth and chroma subsampling detection. This feature tests hardware codec support for different pixel formats, helping you understand the full capabilities of your hardware encoders and decoders.
79
+
80
+ The detection covers the following pixel formats:
81
+ | Bit-depth | Chroma Subsampling | Pixel Format | Description |
82
+ |-----------|-------------------|--------------|-------------|
83
+ | 8-bit | YUV 4:2:0 | yuv420p | Standard 8-bit 4:2:0 |
84
+ | 8-bit | YUV 4:2:2 | yuv422p | 8-bit 4:2:2 |
85
+ | 8-bit | YUV 4:4:4 | yuv444p | 8-bit 4:4:4 |
86
+ | 10-bit | YUV 4:2:0 | yuv420p10le, p010le | 10-bit 4:2:0 |
87
+ | 10-bit | YUV 4:2:2 | yuv422p10le | 10-bit 4:2:2 |
88
+ | 10-bit | YUV 4:4:4 | yuv444p10le | 10-bit 4:4:4 |
89
+ | 12-bit | YUV 4:2:0 | yuv420p12le | 12-bit 4:2:0 |
90
+ | 12-bit | YUV 4:2:2 | yuv422p12le | 12-bit 4:2:2 |
91
+ | 12-bit | YUV 4:4:4 | yuv444p12le | 12-bit 4:4:4 |
92
+
93
+ This feature uses a fixed resolution of 1280x720 for all tests and follows the encode-then-decode workflow. If hardware encoding fails, the tool automatically falls back to software encoding to ensure decoder tests can still be performed.
94
+
95
+ **Note:** This feature is enabled by default. You can disable it using the `--no-bitdepth-chroma` command-line parameter.
96
+
77
97
 
78
98
  ## How to Use
79
99
  You can install and use HwCodecDetect in two ways.
@@ -6,6 +6,7 @@ pyproject.toml
6
6
  requirements.txt
7
7
  setup.py
8
8
  src/HwCodecDetect/__init__.py
9
+ src/HwCodecDetect/bitdepth_chroma_detect.py
9
10
  src/HwCodecDetect/install_ffmpeg_if_needed.py
10
11
  src/HwCodecDetect/run_tests.py
11
12
  src/HwCodecDetect/version.py
@@ -1 +0,0 @@
1
- 0.1.9
@@ -1 +0,0 @@
1
- __version__ = "0.1.9"
File without changes
File without changes
File without changes
File without changes