HwCodecDetect 0.2.2__tar.gz → 0.2.4__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.
- {hwcodecdetect-0.2.2/src/HwCodecDetect.egg-info → hwcodecdetect-0.2.4}/PKG-INFO +4 -4
- {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4}/README.md +3 -3
- hwcodecdetect-0.2.4/VERSION +1 -0
- {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4}/src/HwCodecDetect/bitdepth_chroma_detect.py +54 -15
- {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4}/src/HwCodecDetect/run_tests.py +56 -15
- hwcodecdetect-0.2.4/src/HwCodecDetect/utils.py +166 -0
- hwcodecdetect-0.2.4/src/HwCodecDetect/version.py +1 -0
- {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4/src/HwCodecDetect.egg-info}/PKG-INFO +4 -4
- {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4}/src/HwCodecDetect.egg-info/SOURCES.txt +1 -0
- hwcodecdetect-0.2.2/VERSION +0 -1
- hwcodecdetect-0.2.2/src/HwCodecDetect/version.py +0 -1
- {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4}/LICENSE +0 -0
- {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4}/MANIFEST.in +0 -0
- {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4}/pyproject.toml +0 -0
- {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4}/requirements.txt +0 -0
- {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4}/setup.cfg +0 -0
- {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4}/setup.py +0 -0
- {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4}/src/HwCodecDetect/__init__.py +0 -0
- {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4}/src/HwCodecDetect/install_ffmpeg_if_needed.py +0 -0
- {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4}/src/HwCodecDetect.egg-info/dependency_links.txt +0 -0
- {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4}/src/HwCodecDetect.egg-info/entry_points.txt +0 -0
- {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4}/src/HwCodecDetect.egg-info/requires.txt +0 -0
- {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4}/src/HwCodecDetect.egg-info/top_level.txt +0 -0
- {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4}/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.2.
|
|
3
|
+
Version: 0.2.4
|
|
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
|
|
@@ -59,8 +59,8 @@ The script automatically detect and reports on the following major hardware enco
|
|
|
59
59
|
| Media Foundation | H.264、H.265、AV1 |
|
|
60
60
|
| D3D12VA (Direct3D 12 Video Acceleration) | H.264、H.265、AV1 |
|
|
61
61
|
| VAAPI (Video Acceleration API) | H.264、H.265、AV1、MJPEG、MPEG-2、VP8、VP9 |
|
|
62
|
-
| Vulkan | H.264、H.265
|
|
63
|
-
| Apple VideoToolbox | H.264、H.265
|
|
62
|
+
| Vulkan | H.264、H.265、AV1 |
|
|
63
|
+
| Apple VideoToolbox | H.264、H.265、ProRes |
|
|
64
64
|
|
|
65
65
|
### Decoders
|
|
66
66
|
The script automatically detect and reports on the following major hardware decoders and their supported formats:
|
|
@@ -72,7 +72,7 @@ The script automatically detect and reports on the following major hardware deco
|
|
|
72
72
|
| DXVA2 (DirectX Video Acceleration) | H.264、H.265、MJPEG、MPEG-1、MPEG-2、MPEG-4、VP8 |
|
|
73
73
|
| D3D11VA (Direct3D 11 Video Acceleration) | H.264、H.265、AV1、MJPEG、MPEG-1、MPEG-2、MPEG-4、VP8、VP9 |
|
|
74
74
|
| Vulkan | H.264、H.265、AV1 |
|
|
75
|
-
| Apple VideoToolbox | H.264、H.265、MPEG-2、MPEG-4
|
|
75
|
+
| Apple VideoToolbox | H.264、H.265、MPEG-2、MPEG-4、ProRes |
|
|
76
76
|
|
|
77
77
|
### Bit-depth and Chroma Subsampling Detection
|
|
78
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.
|
|
@@ -33,8 +33,8 @@ The script automatically detect and reports on the following major hardware enco
|
|
|
33
33
|
| Media Foundation | H.264、H.265、AV1 |
|
|
34
34
|
| D3D12VA (Direct3D 12 Video Acceleration) | H.264、H.265、AV1 |
|
|
35
35
|
| VAAPI (Video Acceleration API) | H.264、H.265、AV1、MJPEG、MPEG-2、VP8、VP9 |
|
|
36
|
-
| Vulkan | H.264、H.265
|
|
37
|
-
| Apple VideoToolbox | H.264、H.265
|
|
36
|
+
| Vulkan | H.264、H.265、AV1 |
|
|
37
|
+
| Apple VideoToolbox | H.264、H.265、ProRes |
|
|
38
38
|
|
|
39
39
|
### Decoders
|
|
40
40
|
The script automatically detect and reports on the following major hardware decoders and their supported formats:
|
|
@@ -46,7 +46,7 @@ The script automatically detect and reports on the following major hardware deco
|
|
|
46
46
|
| DXVA2 (DirectX Video Acceleration) | H.264、H.265、MJPEG、MPEG-1、MPEG-2、MPEG-4、VP8 |
|
|
47
47
|
| D3D11VA (Direct3D 11 Video Acceleration) | H.264、H.265、AV1、MJPEG、MPEG-1、MPEG-2、MPEG-4、VP8、VP9 |
|
|
48
48
|
| Vulkan | H.264、H.265、AV1 |
|
|
49
|
-
| Apple VideoToolbox | H.264、H.265、MPEG-2、MPEG-4
|
|
49
|
+
| Apple VideoToolbox | H.264、H.265、MPEG-2、MPEG-4、ProRes |
|
|
50
50
|
|
|
51
51
|
### Bit-depth and Chroma Subsampling Detection
|
|
52
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.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.2.4
|
|
@@ -12,6 +12,7 @@ from collections import defaultdict
|
|
|
12
12
|
from colorama import init, Fore, Style
|
|
13
13
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
14
14
|
from tqdm import tqdm
|
|
15
|
+
from .utils import check_codec_support
|
|
15
16
|
|
|
16
17
|
init(autoreset=True)
|
|
17
18
|
|
|
@@ -58,8 +59,10 @@ ENCODER_TITLES = {
|
|
|
58
59
|
("vp9_vaapi", "vp9"): "Video Acceleration VP9 Encoder(VAAPI)",
|
|
59
60
|
("h264_vulkan", "h264"): "Vulkan Hardware H264 Encoder(Vulkan)",
|
|
60
61
|
("hevc_vulkan", "h265"): "Vulkan Hardware H265 Encoder(Vulkan)",
|
|
62
|
+
("av1_vulkan", "av1"): "Vulkan Hardware AV1 Encoder(Vulkan)",
|
|
61
63
|
("h264_videotoolbox", "h264"): "MacOS Hardware H264 Encoder(VideoToolbox)",
|
|
62
64
|
("hevc_videotoolbox", "h265"): "MacOS Hardware H265 Encoder(VideoToolbox)",
|
|
65
|
+
("prores_videotoolbox", "prores"): "MacOS Hardware ProRes Encoder(VideoToolbox)",
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
# Decoder titles (same as main module)
|
|
@@ -101,16 +104,18 @@ DECODER_TITLES = {
|
|
|
101
104
|
("videotoolbox", "h265"): "MacOS Hardware H265 Decoder(VideoToolbox)",
|
|
102
105
|
("videotoolbox", "mpeg2"): "MacOS Hardware MPEG-2 Decoder(VideoToolbox)",
|
|
103
106
|
("videotoolbox", "mpeg4"): "MacOS Hardware MPEG-4 Decoder(VideoToolbox)",
|
|
107
|
+
("videotoolbox", "prores"): "MacOS Hardware ProRes Decoder(VideoToolbox)",
|
|
104
108
|
}
|
|
105
109
|
|
|
106
110
|
# Encoder definitions (same as main module)
|
|
107
111
|
ENCODERS = {
|
|
108
112
|
"h264": {"lib": "libx264", "hw_encoders": ["h264_nvenc", "h264_qsv", "h264_amf", "h264_mf", "h264_d3d12va", "h264_vaapi", "h264_vulkan", "h264_videotoolbox"]},
|
|
109
113
|
"h265": {"lib": "libx265", "hw_encoders": ["hevc_nvenc", "hevc_qsv", "hevc_amf", "hevc_mf", "hevc_d3d12va", "hevc_vaapi", "hevc_vulkan", "hevc_videotoolbox"]},
|
|
110
|
-
"av1": {"lib": "librav1e", "hw_encoders": ["av1_nvenc", "av1_qsv", "av1_amf", "av1_mf", "av1_d3d12va", "av1_vaapi"]},
|
|
114
|
+
"av1": {"lib": "librav1e", "hw_encoders": ["av1_nvenc", "av1_qsv", "av1_amf", "av1_mf", "av1_d3d12va", "av1_vaapi", "av1_vulkan"]},
|
|
111
115
|
"mpeg2": {"lib": "mpeg2video", "hw_encoders": ["mpeg2_qsv", "mpeg2_vaapi"]},
|
|
112
116
|
"vp8": {"lib": "libvpx", "hw_encoders": ["vp8_vaapi"]},
|
|
113
117
|
"vp9": {"lib": "libvpx-vp9", "hw_encoders": ["vp9_qsv", "vp9_vaapi"]},
|
|
118
|
+
"prores": {"lib": "prores", "hw_encoders": ["prores_videotoolbox"]},
|
|
114
119
|
}
|
|
115
120
|
|
|
116
121
|
# Decoder definitions (same as main module)
|
|
@@ -123,6 +128,7 @@ DECODERS = {
|
|
|
123
128
|
"mpeg4": {"lib": "mpeg4", "hw_decoders": ["mpeg4_cuvid", "dxva2", "d3d11va", "videotoolbox"]},
|
|
124
129
|
"vp8": {"lib": "libvpx", "hw_decoders": ["vp8_cuvid", "vp8_qsv", "dxva2", "d3d11va"]},
|
|
125
130
|
"vp9": {"lib": "libvpx-vp9", "hw_decoders": ["vp9_cuvid", "vp9_qsv", "dxva2", "d3d11va"]},
|
|
131
|
+
"prores": {"lib": "prores", "hw_decoders": ["videotoolbox"]},
|
|
126
132
|
}
|
|
127
133
|
|
|
128
134
|
|
|
@@ -147,11 +153,19 @@ def _run_ffmpeg_command(command, verbose):
|
|
|
147
153
|
|
|
148
154
|
def _run_encoder_bitdepth_test(test_data):
|
|
149
155
|
"""Tests encoder support for a specific pixel format."""
|
|
150
|
-
codec, encoder, pix_fmt, bit_depth, chroma, test_dir, verbose = test_data
|
|
151
|
-
|
|
152
|
-
|
|
156
|
+
codec, encoder, pix_fmt, bit_depth, chroma, test_dir, verbose, unsupported_encoders = test_data
|
|
157
|
+
|
|
158
|
+
# Skip unsupported encoders
|
|
159
|
+
if encoder in unsupported_encoders:
|
|
160
|
+
title = ENCODER_TITLES.get((encoder, codec), f"{encoder.upper()} Encoder:")
|
|
161
|
+
return title, pix_fmt, bit_depth, chroma, "skipped"
|
|
162
|
+
|
|
163
|
+
if codec == "prores":
|
|
164
|
+
file_ext = ".mov"
|
|
165
|
+
else:
|
|
166
|
+
file_ext = ".webm" if codec in ["vp8", "vp9"] else ".mp4"
|
|
153
167
|
output_file = os.path.join(test_dir, f"{encoder}_{pix_fmt}{file_ext}")
|
|
154
|
-
|
|
168
|
+
|
|
155
169
|
# Determine pixel format for output based on input format
|
|
156
170
|
if bit_depth == 8:
|
|
157
171
|
if chroma == "4:2:0":
|
|
@@ -263,17 +277,20 @@ def _run_encoder_bitdepth_test(test_data):
|
|
|
263
277
|
return title, pix_fmt, bit_depth, chroma, status
|
|
264
278
|
|
|
265
279
|
|
|
266
|
-
def _run_encoder_bitdepth_tests(test_dir, max_workers, verbose):
|
|
280
|
+
def _run_encoder_bitdepth_tests(test_dir, max_workers, verbose, unsupported_encoders=None):
|
|
267
281
|
"""Tests encoder support for various pixel formats."""
|
|
268
282
|
results = defaultdict(dict)
|
|
269
283
|
|
|
284
|
+
if unsupported_encoders is None:
|
|
285
|
+
unsupported_encoders = set()
|
|
286
|
+
|
|
270
287
|
print("\n--- Running Bit-depth/Chroma Encoder Tests ---")
|
|
271
288
|
|
|
272
289
|
tasks = []
|
|
273
290
|
for codec, info in ENCODERS.items():
|
|
274
291
|
for encoder in info['hw_encoders']:
|
|
275
292
|
for pix_fmt, bit_depth, chroma, desc in PIXEL_FORMATS:
|
|
276
|
-
tasks.append((codec, encoder, pix_fmt, bit_depth, chroma, test_dir, verbose))
|
|
293
|
+
tasks.append((codec, encoder, pix_fmt, bit_depth, chroma, test_dir, verbose, unsupported_encoders))
|
|
277
294
|
|
|
278
295
|
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
279
296
|
futures = [executor.submit(_run_encoder_bitdepth_test, task) for task in tasks]
|
|
@@ -290,9 +307,17 @@ def _run_encoder_bitdepth_tests(test_dir, max_workers, verbose):
|
|
|
290
307
|
|
|
291
308
|
def _run_decoder_bitdepth_test(test_data):
|
|
292
309
|
"""Tests decoder support for a specific pixel format."""
|
|
293
|
-
codec, hw_decoder, pix_fmt, bit_depth, chroma, test_dir, verbose = test_data
|
|
310
|
+
codec, hw_decoder, pix_fmt, bit_depth, chroma, test_dir, verbose, unsupported_decoders = test_data
|
|
294
311
|
|
|
295
|
-
|
|
312
|
+
# Skip unsupported decoders
|
|
313
|
+
if hw_decoder in unsupported_decoders:
|
|
314
|
+
title = DECODER_TITLES.get((hw_decoder, codec), f"{hw_decoder.upper()} Decoder:")
|
|
315
|
+
return title, pix_fmt, bit_depth, chroma, "skipped"
|
|
316
|
+
|
|
317
|
+
if codec == "prores":
|
|
318
|
+
file_ext = ".mov"
|
|
319
|
+
else:
|
|
320
|
+
file_ext = ".webm" if codec in ["vp8", "vp9"] else ".mp4"
|
|
296
321
|
test_file = os.path.join(test_dir, f"{codec}_{pix_fmt}{file_ext}")
|
|
297
322
|
|
|
298
323
|
# Create test file with specific pixel format if it doesn't exist
|
|
@@ -374,17 +399,20 @@ def _run_decoder_bitdepth_test(test_data):
|
|
|
374
399
|
return title, pix_fmt, bit_depth, chroma, status
|
|
375
400
|
|
|
376
401
|
|
|
377
|
-
def _run_decoder_bitdepth_tests(test_dir, max_workers, verbose):
|
|
402
|
+
def _run_decoder_bitdepth_tests(test_dir, max_workers, verbose, unsupported_decoders=None):
|
|
378
403
|
"""Tests decoder support for various pixel formats."""
|
|
379
404
|
results = defaultdict(dict)
|
|
380
405
|
|
|
406
|
+
if unsupported_decoders is None:
|
|
407
|
+
unsupported_decoders = set()
|
|
408
|
+
|
|
381
409
|
print("\n--- Running Bit-depth/Chroma Decoder Tests ---")
|
|
382
410
|
|
|
383
411
|
tasks = []
|
|
384
412
|
for codec, info in DECODERS.items():
|
|
385
413
|
for hw_decoder in info['hw_decoders']:
|
|
386
414
|
for pix_fmt, bit_depth, chroma, desc in PIXEL_FORMATS:
|
|
387
|
-
tasks.append((codec, hw_decoder, pix_fmt, bit_depth, chroma, test_dir, verbose))
|
|
415
|
+
tasks.append((codec, hw_decoder, pix_fmt, bit_depth, chroma, test_dir, verbose, unsupported_decoders))
|
|
388
416
|
|
|
389
417
|
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
390
418
|
futures = [executor.submit(_run_decoder_bitdepth_test, task) for task in tasks]
|
|
@@ -454,13 +482,24 @@ def _print_bitdepth_chroma_table(results, table_type="Encoder"):
|
|
|
454
482
|
def run_bitdepth_chroma_tests(encoder_count, decoder_count, verbose):
|
|
455
483
|
"""Run all bit-depth and chroma tests and return results."""
|
|
456
484
|
import shutil
|
|
457
|
-
temp_dir = os.path.join(tempfile.gettempdir(), "HwCodecDetect_BitDepth")
|
|
485
|
+
#temp_dir = os.path.join(tempfile.gettempdir(), "HwCodecDetect_BitDepth")
|
|
486
|
+
from .utils import get_temp_path
|
|
487
|
+
temp_dir = os.path.join(get_temp_path(), "HwCodecDetect_BitDepth")
|
|
458
488
|
if os.path.exists(temp_dir):
|
|
459
489
|
shutil.rmtree(temp_dir)
|
|
460
|
-
os.makedirs(temp_dir)
|
|
490
|
+
os.makedirs(temp_dir, exist_ok=True)
|
|
491
|
+
|
|
492
|
+
# Check codec support before running tests
|
|
493
|
+
print("\nChecking FFmpeg codec support for bit-depth/chroma tests...")
|
|
494
|
+
unsupported_encoders, unsupported_decoders = check_codec_support(ENCODERS, DECODERS)
|
|
495
|
+
if unsupported_encoders or unsupported_decoders:
|
|
496
|
+
print(f"\nFound {len(unsupported_encoders)} unsupported encoder(s) and {len(unsupported_decoders)} unsupported decoder(s).")
|
|
497
|
+
print("These codecs will be marked as unavailable '-' in the results.\n")
|
|
498
|
+
else:
|
|
499
|
+
print("All defined hardware codecs are supported.\n")
|
|
461
500
|
|
|
462
|
-
encoder_results = _run_encoder_bitdepth_tests(temp_dir, encoder_count, verbose)
|
|
463
|
-
decoder_results = _run_decoder_bitdepth_tests(temp_dir, decoder_count, verbose)
|
|
501
|
+
encoder_results = _run_encoder_bitdepth_tests(temp_dir, encoder_count, verbose, unsupported_encoders)
|
|
502
|
+
decoder_results = _run_decoder_bitdepth_tests(temp_dir, decoder_count, verbose, unsupported_decoders)
|
|
464
503
|
|
|
465
504
|
# Clean up
|
|
466
505
|
shutil.rmtree(temp_dir)
|
|
@@ -10,6 +10,7 @@ import argparse
|
|
|
10
10
|
from collections import defaultdict
|
|
11
11
|
from .install_ffmpeg_if_needed import install_ffmpeg_if_needed
|
|
12
12
|
from .bitdepth_chroma_detect import run_bitdepth_chroma_tests, print_bitdepth_chroma_results
|
|
13
|
+
from .utils import check_codec_support
|
|
13
14
|
from colorama import init, Fore, Style
|
|
14
15
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
15
16
|
from tqdm import tqdm
|
|
@@ -78,6 +79,7 @@ DECODER_TITLES = {
|
|
|
78
79
|
("videotoolbox", "h265"): "MacOS Hardware H265 Decoder(VideoToolbox)",
|
|
79
80
|
("videotoolbox", "mpeg2"): "MacOS Hardware MPEG-2 Decoder(VideoToolbox)",
|
|
80
81
|
("videotoolbox", "mpeg4"): "MacOS Hardware MPEG-4 Decoder(VideoToolbox)",
|
|
82
|
+
("videotoolbox", "prores"): "MacOS Hardware ProRes Decoder(VideoToolbox)",
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
DECODERS = {
|
|
@@ -90,6 +92,7 @@ DECODERS = {
|
|
|
90
92
|
"mpeg4": {"lib": "mpeg4", "hw_decoders": ["mpeg4_cuvid", "dxva2", "d3d11va", "videotoolbox"]},
|
|
91
93
|
"vp8": {"lib": "libvpx", "hw_decoders": ["vp8_cuvid", "vp8_qsv", "dxva2", "d3d11va"]},
|
|
92
94
|
"vp9": {"lib": "libvpx-vp9", "hw_decoders": ["vp9_cuvid", "vp9_qsv", "dxva2", "d3d11va"]},
|
|
95
|
+
"prores": {"lib": "prores", "hw_decoders": ["videotoolbox"]},
|
|
93
96
|
}
|
|
94
97
|
|
|
95
98
|
# --- Encoder Definitions ---
|
|
@@ -121,18 +124,21 @@ ENCODER_TITLES = {
|
|
|
121
124
|
("vp9_vaapi", "vp9"): "Video Acceleration VP9 Encoder(VAAPI)",
|
|
122
125
|
("h264_vulkan", "h264"): "Vulkan Hardware H264 Encoder(Vulkan)",
|
|
123
126
|
("hevc_vulkan", "h265"): "Vulkan Hardware H265 Encoder(Vulkan)",
|
|
127
|
+
("av1_vulkan", "av1"): "Vulkan Hardware AV1 Encoder(Vulkan)",
|
|
124
128
|
("h264_videotoolbox", "h264"): "MacOS Hardware H264 Encoder(VideoToolbox)",
|
|
125
129
|
("hevc_videotoolbox", "h265"): "MacOS Hardware H265 Encoder(VideoToolbox)",
|
|
130
|
+
("prores_videotoolbox", "prores"): "MacOS Hardware ProRes Encoder(VideoToolbox)",
|
|
126
131
|
}
|
|
127
132
|
|
|
128
133
|
ENCODERS = {
|
|
129
134
|
"h264": {"lib": "libx264", "hw_encoders": ["h264_nvenc", "h264_qsv", "h264_amf", "h264_mf", "h264_d3d12va", "h264_vaapi", "h264_vulkan", "h264_videotoolbox"]},
|
|
130
135
|
"h265": {"lib": "libx265", "hw_encoders": ["hevc_nvenc", "hevc_qsv", "hevc_amf", "hevc_mf", "hevc_d3d12va", "hevc_vaapi", "hevc_vulkan", "hevc_videotoolbox"]},
|
|
131
|
-
"av1": {"lib": "librav1e", "hw_encoders": ["av1_nvenc", "av1_qsv", "av1_amf", "av1_mf", "av1_d3d12va", "av1_vaapi"]},
|
|
136
|
+
"av1": {"lib": "librav1e", "hw_encoders": ["av1_nvenc", "av1_qsv", "av1_amf", "av1_mf", "av1_d3d12va", "av1_vaapi", "av1_vulkan"]},
|
|
132
137
|
"mjpeg": {"lib": "mjpeg", "hw_encoders": ["mjpeg_qsv", "mjpeg_vaapi"]},
|
|
133
138
|
"mpeg2": {"lib": "mpeg2video", "hw_encoders": ["mpeg2_qsv", "mpeg2_vaapi"]},
|
|
134
139
|
"vp8": {"lib": "libvpx", "hw_encoders": ["vp8_vaapi"]},
|
|
135
140
|
"vp9": {"lib": "libvpx-vp9", "hw_encoders": ["vp9_qsv", "vp9_vaapi"]},
|
|
141
|
+
"prores": {"lib": "prores", "hw_encoders": ["prores_videotoolbox"]},
|
|
136
142
|
}
|
|
137
143
|
|
|
138
144
|
# Combine both decoder and encoder data into a single structure
|
|
@@ -258,8 +264,17 @@ def _run_ffmpeg_command(command, verbose):
|
|
|
258
264
|
|
|
259
265
|
def _run_encoder_test_single(test_data):
|
|
260
266
|
"""Runs a single encoder test and returns the result."""
|
|
261
|
-
codec, encoder, res_name, res_size, test_dir, verbose = test_data
|
|
262
|
-
|
|
267
|
+
codec, encoder, res_name, res_size, test_dir, verbose, unsupported_encoders = test_data
|
|
268
|
+
|
|
269
|
+
# Skip unsupported encoders
|
|
270
|
+
if encoder in unsupported_encoders:
|
|
271
|
+
title = ENCODER_TITLES.get((encoder, codec), f"{encoder.upper()} Encoder:")
|
|
272
|
+
return title, res_name, "skipped"
|
|
273
|
+
|
|
274
|
+
if codec == "prores":
|
|
275
|
+
file_ext = ".mov"
|
|
276
|
+
else:
|
|
277
|
+
file_ext = ".webm" if codec in ["vp8", "vp9"] else ".mp4"
|
|
263
278
|
output_file = os.path.join(test_dir, f"{encoder}_{res_name}{file_ext}")
|
|
264
279
|
if "vulkan" in encoder:
|
|
265
280
|
command = [
|
|
@@ -350,17 +365,20 @@ def _run_encoder_test_single(test_data):
|
|
|
350
365
|
return title, res_name, status
|
|
351
366
|
|
|
352
367
|
|
|
353
|
-
def _run_encoder_tests(test_dir, max_workers, verbose):
|
|
368
|
+
def _run_encoder_tests(test_dir, max_workers, verbose, unsupported_encoders=None):
|
|
354
369
|
"""Runs hardware encoder tests using a thread pool."""
|
|
355
370
|
results = defaultdict(dict)
|
|
356
|
-
|
|
371
|
+
|
|
372
|
+
if unsupported_encoders is None:
|
|
373
|
+
unsupported_encoders = set()
|
|
374
|
+
|
|
357
375
|
print("\n--- Running Encoder Tests ---")
|
|
358
|
-
|
|
376
|
+
|
|
359
377
|
tasks = []
|
|
360
378
|
for codec, info in ENCODERS.items():
|
|
361
379
|
for encoder in info['hw_encoders']:
|
|
362
380
|
for res_name, res_size in RESOLUTIONS.items():
|
|
363
|
-
tasks.append((codec, encoder, res_name, res_size, test_dir, verbose))
|
|
381
|
+
tasks.append((codec, encoder, res_name, res_size, test_dir, verbose, unsupported_encoders))
|
|
364
382
|
|
|
365
383
|
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
366
384
|
futures = [executor.submit(_run_encoder_test_single, task) for task in tasks]
|
|
@@ -373,8 +391,17 @@ def _run_encoder_tests(test_dir, max_workers, verbose):
|
|
|
373
391
|
|
|
374
392
|
def _run_decoder_test_single(test_data):
|
|
375
393
|
"""Runs a single decoder test and returns the result."""
|
|
376
|
-
codec, hw_decoder, res_name, res_size, test_dir, verbose = test_data
|
|
377
|
-
|
|
394
|
+
codec, hw_decoder, res_name, res_size, test_dir, verbose, unsupported_decoders = test_data
|
|
395
|
+
|
|
396
|
+
# Skip unsupported decoders
|
|
397
|
+
if hw_decoder in unsupported_decoders:
|
|
398
|
+
title = DECODER_TITLES.get((hw_decoder, codec), f"{hw_decoder.upper()} Decoder:")
|
|
399
|
+
return title, res_name, "skipped"
|
|
400
|
+
|
|
401
|
+
if codec == "prores":
|
|
402
|
+
file_ext = ".mov"
|
|
403
|
+
else:
|
|
404
|
+
file_ext = ".webm" if codec in ["vp8", "vp9"] else ".mp4"
|
|
378
405
|
test_file_path = os.path.join(test_dir, f"{codec}_{res_name}{file_ext}")
|
|
379
406
|
|
|
380
407
|
found_file = False
|
|
@@ -465,17 +492,20 @@ def _run_decoder_test_single(test_data):
|
|
|
465
492
|
return title, res_name, status
|
|
466
493
|
|
|
467
494
|
|
|
468
|
-
def _run_decoder_tests(test_dir, max_workers, verbose):
|
|
495
|
+
def _run_decoder_tests(test_dir, max_workers, verbose, unsupported_decoders=None):
|
|
469
496
|
"""Runs hardware decoder tests using a thread pool."""
|
|
470
497
|
results = defaultdict(dict)
|
|
471
498
|
|
|
499
|
+
if unsupported_decoders is None:
|
|
500
|
+
unsupported_decoders = set()
|
|
501
|
+
|
|
472
502
|
print("\n--- Running Decoder Tests ---")
|
|
473
503
|
|
|
474
504
|
tasks = []
|
|
475
505
|
for codec, info in DECODERS.items():
|
|
476
506
|
for hw_decoder in info['hw_decoders']:
|
|
477
507
|
for res_name, res_size in RESOLUTIONS.items():
|
|
478
|
-
tasks.append((codec, hw_decoder, res_name, res_size, test_dir, verbose))
|
|
508
|
+
tasks.append((codec, hw_decoder, res_name, res_size, test_dir, verbose, unsupported_decoders))
|
|
479
509
|
|
|
480
510
|
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
481
511
|
futures = [executor.submit(_run_decoder_test_single, task) for task in tasks]
|
|
@@ -564,14 +594,25 @@ def run_all_tests(args):
|
|
|
564
594
|
print("Error: FFmpeg dependency not met. Please check installation.", file=sys.stderr)
|
|
565
595
|
return -1
|
|
566
596
|
|
|
567
|
-
|
|
597
|
+
# Check codec support before running tests
|
|
598
|
+
print("\nChecking FFmpeg codec support...")
|
|
599
|
+
unsupported_encoders, unsupported_decoders = check_codec_support(ENCODERS, DECODERS)
|
|
600
|
+
if unsupported_encoders or unsupported_decoders:
|
|
601
|
+
print(f"\nFound {len(unsupported_encoders)} unsupported encoder(s) and {len(unsupported_decoders)} unsupported decoder(s).")
|
|
602
|
+
print("These codecs will be marked as unavailable '-' in the results.\n")
|
|
603
|
+
else:
|
|
604
|
+
print("All defined hardware codecs are supported.\n")
|
|
605
|
+
|
|
606
|
+
#temp_dir = os.path.join(tempfile.gettempdir(), "HwCodecDetect")
|
|
607
|
+
from .utils import get_temp_path
|
|
608
|
+
temp_dir = os.path.join(get_temp_path(), "HwCodecDetect_cli")
|
|
568
609
|
if os.path.exists(temp_dir):
|
|
569
610
|
# Clear previous run data to ensure a fresh test
|
|
570
611
|
shutil.rmtree(temp_dir)
|
|
571
|
-
os.makedirs(temp_dir)
|
|
612
|
+
os.makedirs(temp_dir, exist_ok=True)
|
|
572
613
|
|
|
573
|
-
encoder_results = _run_encoder_tests(temp_dir, args.encoder_count, args.verbose)
|
|
574
|
-
decoder_results = _run_decoder_tests(temp_dir, args.decoder_count, args.verbose)
|
|
614
|
+
encoder_results = _run_encoder_tests(temp_dir, args.encoder_count, args.verbose, unsupported_encoders)
|
|
615
|
+
decoder_results = _run_decoder_tests(temp_dir, args.decoder_count, args.verbose, unsupported_decoders)
|
|
575
616
|
|
|
576
617
|
all_results = {}
|
|
577
618
|
all_results.update(encoder_results)
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import os.path
|
|
3
|
+
import tempfile
|
|
4
|
+
import sys
|
|
5
|
+
import re
|
|
6
|
+
import subprocess
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
def get_temp_path():
|
|
10
|
+
app_id = "HwCodecDetect"
|
|
11
|
+
|
|
12
|
+
candidates = []
|
|
13
|
+
|
|
14
|
+
candidates.append(os.path.join(tempfile.gettempdir(), app_id))
|
|
15
|
+
|
|
16
|
+
if sys.platform == "win32":
|
|
17
|
+
local_appdata = os.getenv("LOCALAPPDATA")
|
|
18
|
+
if local_appdata:
|
|
19
|
+
candidates.append(os.path.join(local_appdata, app_id))
|
|
20
|
+
elif sys.platform == "darwin":
|
|
21
|
+
candidates.append(os.path.expanduser(f"~/Library/Caches/{app_id}"))
|
|
22
|
+
elif sys.platform.startswith("linux"):
|
|
23
|
+
xdg_cache = os.getenv("XDG_CACHE_HOME", os.path.expanduser("~/.cache"))
|
|
24
|
+
candidates.append(os.path.join(xdg_cache, app_id))
|
|
25
|
+
|
|
26
|
+
candidates.append(os.path.expanduser(f"~/.{app_id}"))
|
|
27
|
+
|
|
28
|
+
for path in candidates:
|
|
29
|
+
if not path:
|
|
30
|
+
continue
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
os.makedirs(path, mode=0o755, exist_ok=True)
|
|
34
|
+
test_file = os.path.join(path, ".perm_test")
|
|
35
|
+
with open(test_file, 'w', encoding='utf-8') as f:
|
|
36
|
+
f.write("test")
|
|
37
|
+
|
|
38
|
+
if os.path.exists(test_file):
|
|
39
|
+
os.remove(test_file)
|
|
40
|
+
return path
|
|
41
|
+
except Exception as e:
|
|
42
|
+
print(f"Warning: Attempting path {path} failed: {e}")
|
|
43
|
+
continue
|
|
44
|
+
|
|
45
|
+
return os.getcwd()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_local_version():
|
|
49
|
+
if getattr(sys, 'frozen', False):
|
|
50
|
+
base_path = sys._MEIPASS
|
|
51
|
+
else:
|
|
52
|
+
base_path = os.path.dirname(os.path.abspath(__file__))
|
|
53
|
+
|
|
54
|
+
v_path = os.path.join(base_path, "VERSION")
|
|
55
|
+
|
|
56
|
+
if not os.path.exists(v_path):
|
|
57
|
+
v_path = os.path.join(base_path, "..", "..", "VERSION")
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
with open(v_path, "r", encoding="utf-8") as f:
|
|
61
|
+
return f.read().strip()
|
|
62
|
+
except FileNotFoundError:
|
|
63
|
+
return "Unknown Version"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def get_ffmpeg_supported_codecs():
|
|
67
|
+
"""Get supported encoders, decoders and hwaccels from ffmpeg.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
tuple: (supported_encoders, supported_decoders) - two sets containing:
|
|
71
|
+
- supported_encoders: set of supported encoder names and hwaccel methods
|
|
72
|
+
- supported_decoders: set of supported decoder names and hwaccel methods
|
|
73
|
+
"""
|
|
74
|
+
supported_encoders = set()
|
|
75
|
+
supported_decoders = set()
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
# Get encoders
|
|
79
|
+
result = subprocess.run(
|
|
80
|
+
["ffmpeg", "-hide_banner", "-encoders"],
|
|
81
|
+
capture_output=True,
|
|
82
|
+
text=True,
|
|
83
|
+
encoding='utf-8',
|
|
84
|
+
errors='ignore'
|
|
85
|
+
)
|
|
86
|
+
if result.returncode == 0:
|
|
87
|
+
for line in result.stdout.split('\n'):
|
|
88
|
+
# Parse lines like: V....D av1_nvenc NVIDIA NVENC av1 encoder (codec av1)
|
|
89
|
+
# Format: [VSA][6 chars of flags] [codec name] [description]
|
|
90
|
+
match = re.search(r'^\s*[VSA].{6}\s*(\S+)', line)
|
|
91
|
+
if match:
|
|
92
|
+
supported_encoders.add(match.group(1))
|
|
93
|
+
|
|
94
|
+
# Get decoders
|
|
95
|
+
result = subprocess.run(
|
|
96
|
+
["ffmpeg", "-hide_banner", "-decoders"],
|
|
97
|
+
capture_output=True,
|
|
98
|
+
text=True,
|
|
99
|
+
encoding='utf-8',
|
|
100
|
+
errors='ignore'
|
|
101
|
+
)
|
|
102
|
+
if result.returncode == 0:
|
|
103
|
+
for line in result.stdout.split('\n'):
|
|
104
|
+
# Parse lines like: V..... av1_cuvid Nvidia CUVID AV1 decoder (codec av1)
|
|
105
|
+
match = re.search(r'^\s*[VSA].{6}\s*(\S+)', line)
|
|
106
|
+
if match:
|
|
107
|
+
supported_decoders.add(match.group(1))
|
|
108
|
+
|
|
109
|
+
# Get hardware acceleration methods
|
|
110
|
+
result = subprocess.run(
|
|
111
|
+
["ffmpeg", "-hide_banner", "-hwaccels"],
|
|
112
|
+
capture_output=True,
|
|
113
|
+
text=True,
|
|
114
|
+
encoding='utf-8',
|
|
115
|
+
errors='ignore'
|
|
116
|
+
)
|
|
117
|
+
if result.returncode == 0:
|
|
118
|
+
lines = result.stdout.split('\n')
|
|
119
|
+
for line in lines:
|
|
120
|
+
line = line.strip()
|
|
121
|
+
# Skip empty lines and header
|
|
122
|
+
if not line or line.startswith('Hardware acceleration'):
|
|
123
|
+
continue
|
|
124
|
+
# Add hwaccel methods to both encoders and decoders
|
|
125
|
+
supported_encoders.add(line)
|
|
126
|
+
supported_decoders.add(line)
|
|
127
|
+
|
|
128
|
+
except Exception as e:
|
|
129
|
+
print(f"Warning: Failed to get ffmpeg codecs: {e}", file=sys.stderr)
|
|
130
|
+
|
|
131
|
+
return supported_encoders, supported_decoders
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def check_codec_support(encoders_dict, decoders_dict):
|
|
135
|
+
"""Check which hardware codecs are supported by current ffmpeg version.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
encoders_dict: Dictionary of encoder definitions (like ENCODERS)
|
|
139
|
+
decoders_dict: Dictionary of decoder definitions (like DECODERS)
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
tuple: (unsupported_encoders, unsupported_decoders) - two sets containing
|
|
143
|
+
the names of unsupported encoders and decoders
|
|
144
|
+
"""
|
|
145
|
+
from colorama import Fore, Style
|
|
146
|
+
|
|
147
|
+
supported_encoders, supported_decoders = get_ffmpeg_supported_codecs()
|
|
148
|
+
|
|
149
|
+
unsupported_encoders = set()
|
|
150
|
+
unsupported_decoders = set()
|
|
151
|
+
|
|
152
|
+
# Check encoders
|
|
153
|
+
for codec, info in encoders_dict.items():
|
|
154
|
+
for encoder in info.get('hw_encoders', []):
|
|
155
|
+
if encoder not in supported_encoders:
|
|
156
|
+
unsupported_encoders.add(encoder)
|
|
157
|
+
print(f"{Fore.YELLOW}Warning: Encoder '{encoder}' is not supported by current FFmpeg version{Style.RESET_ALL}")
|
|
158
|
+
|
|
159
|
+
# Check decoders
|
|
160
|
+
for codec, info in decoders_dict.items():
|
|
161
|
+
for decoder in info.get('hw_decoders', []):
|
|
162
|
+
if decoder not in supported_decoders:
|
|
163
|
+
unsupported_decoders.add(decoder)
|
|
164
|
+
print(f"{Fore.YELLOW}Warning: Decoder '{decoder}' is not supported by current FFmpeg version{Style.RESET_ALL}")
|
|
165
|
+
|
|
166
|
+
return unsupported_encoders, unsupported_decoders
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.4"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: HwCodecDetect
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
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
|
|
@@ -59,8 +59,8 @@ The script automatically detect and reports on the following major hardware enco
|
|
|
59
59
|
| Media Foundation | H.264、H.265、AV1 |
|
|
60
60
|
| D3D12VA (Direct3D 12 Video Acceleration) | H.264、H.265、AV1 |
|
|
61
61
|
| VAAPI (Video Acceleration API) | H.264、H.265、AV1、MJPEG、MPEG-2、VP8、VP9 |
|
|
62
|
-
| Vulkan | H.264、H.265
|
|
63
|
-
| Apple VideoToolbox | H.264、H.265
|
|
62
|
+
| Vulkan | H.264、H.265、AV1 |
|
|
63
|
+
| Apple VideoToolbox | H.264、H.265、ProRes |
|
|
64
64
|
|
|
65
65
|
### Decoders
|
|
66
66
|
The script automatically detect and reports on the following major hardware decoders and their supported formats:
|
|
@@ -72,7 +72,7 @@ The script automatically detect and reports on the following major hardware deco
|
|
|
72
72
|
| DXVA2 (DirectX Video Acceleration) | H.264、H.265、MJPEG、MPEG-1、MPEG-2、MPEG-4、VP8 |
|
|
73
73
|
| D3D11VA (Direct3D 11 Video Acceleration) | H.264、H.265、AV1、MJPEG、MPEG-1、MPEG-2、MPEG-4、VP8、VP9 |
|
|
74
74
|
| Vulkan | H.264、H.265、AV1 |
|
|
75
|
-
| Apple VideoToolbox | H.264、H.265、MPEG-2、MPEG-4
|
|
75
|
+
| Apple VideoToolbox | H.264、H.265、MPEG-2、MPEG-4、ProRes |
|
|
76
76
|
|
|
77
77
|
### Bit-depth and Chroma Subsampling Detection
|
|
78
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.
|
|
@@ -9,6 +9,7 @@ src/HwCodecDetect/__init__.py
|
|
|
9
9
|
src/HwCodecDetect/bitdepth_chroma_detect.py
|
|
10
10
|
src/HwCodecDetect/install_ffmpeg_if_needed.py
|
|
11
11
|
src/HwCodecDetect/run_tests.py
|
|
12
|
+
src/HwCodecDetect/utils.py
|
|
12
13
|
src/HwCodecDetect/version.py
|
|
13
14
|
src/HwCodecDetect.egg-info/PKG-INFO
|
|
14
15
|
src/HwCodecDetect.egg-info/SOURCES.txt
|
hwcodecdetect-0.2.2/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.2.2
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.2.2"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|