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.
Files changed (24) hide show
  1. {hwcodecdetect-0.2.2/src/HwCodecDetect.egg-info → hwcodecdetect-0.2.4}/PKG-INFO +4 -4
  2. {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4}/README.md +3 -3
  3. hwcodecdetect-0.2.4/VERSION +1 -0
  4. {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4}/src/HwCodecDetect/bitdepth_chroma_detect.py +54 -15
  5. {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4}/src/HwCodecDetect/run_tests.py +56 -15
  6. hwcodecdetect-0.2.4/src/HwCodecDetect/utils.py +166 -0
  7. hwcodecdetect-0.2.4/src/HwCodecDetect/version.py +1 -0
  8. {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4/src/HwCodecDetect.egg-info}/PKG-INFO +4 -4
  9. {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4}/src/HwCodecDetect.egg-info/SOURCES.txt +1 -0
  10. hwcodecdetect-0.2.2/VERSION +0 -1
  11. hwcodecdetect-0.2.2/src/HwCodecDetect/version.py +0 -1
  12. {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4}/LICENSE +0 -0
  13. {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4}/MANIFEST.in +0 -0
  14. {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4}/pyproject.toml +0 -0
  15. {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4}/requirements.txt +0 -0
  16. {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4}/setup.cfg +0 -0
  17. {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4}/setup.py +0 -0
  18. {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4}/src/HwCodecDetect/__init__.py +0 -0
  19. {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4}/src/HwCodecDetect/install_ffmpeg_if_needed.py +0 -0
  20. {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4}/src/HwCodecDetect.egg-info/dependency_links.txt +0 -0
  21. {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4}/src/HwCodecDetect.egg-info/entry_points.txt +0 -0
  22. {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4}/src/HwCodecDetect.egg-info/requires.txt +0 -0
  23. {hwcodecdetect-0.2.2 → hwcodecdetect-0.2.4}/src/HwCodecDetect.egg-info/top_level.txt +0 -0
  24. {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.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
- file_ext = ".webm" if codec in ["vp8", "vp9"] else ".mp4"
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
- file_ext = ".webm" if codec in ["vp8", "vp9"] else ".mp4"
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
- file_ext = ".webm" if codec in ["vp8", "vp9"] else ".mp4"
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
- file_ext = ".webm" if codec in ["vp8", "vp9"] else ".mp4"
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
- temp_dir = os.path.join(tempfile.gettempdir(), "HwCodecDetect")
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.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
@@ -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