plex-generate-previews 2.0.0__py3-none-any.whl
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.
- plex_generate_previews/__init__.py +10 -0
- plex_generate_previews/__main__.py +11 -0
- plex_generate_previews/cli.py +474 -0
- plex_generate_previews/config.py +479 -0
- plex_generate_previews/gpu_detection.py +541 -0
- plex_generate_previews/media_processing.py +439 -0
- plex_generate_previews/plex_client.py +211 -0
- plex_generate_previews/utils.py +135 -0
- plex_generate_previews/version_check.py +178 -0
- plex_generate_previews/worker.py +478 -0
- plex_generate_previews-2.0.0.dist-info/METADATA +728 -0
- plex_generate_previews-2.0.0.dist-info/RECORD +15 -0
- plex_generate_previews-2.0.0.dist-info/WHEEL +5 -0
- plex_generate_previews-2.0.0.dist-info/entry_points.txt +2 -0
- plex_generate_previews-2.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,541 @@
|
|
1
|
+
"""
|
2
|
+
GPU detection for video processing acceleration.
|
3
|
+
|
4
|
+
Detects available GPU hardware and returns appropriate configuration
|
5
|
+
for FFmpeg hardware acceleration. Supports NVIDIA, AMD, Intel, and WSL2 GPUs.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import os
|
9
|
+
import subprocess
|
10
|
+
import platform
|
11
|
+
import re
|
12
|
+
from typing import Tuple, Optional, List
|
13
|
+
from loguru import logger
|
14
|
+
|
15
|
+
# Minimum required FFmpeg version
|
16
|
+
MIN_FFMPEG_VERSION = (7, 0, 0) # FFmpeg 7.0.0+ for better hardware acceleration support
|
17
|
+
|
18
|
+
|
19
|
+
def _get_ffmpeg_version() -> Optional[Tuple[int, int, int]]:
|
20
|
+
"""
|
21
|
+
Get FFmpeg version as a tuple of integers.
|
22
|
+
|
23
|
+
Returns:
|
24
|
+
Optional[Tuple[int, int, int]]: Version tuple (major, minor, patch) or None if failed
|
25
|
+
"""
|
26
|
+
try:
|
27
|
+
result = subprocess.run(['ffmpeg', '-version'], capture_output=True, text=True, timeout=5)
|
28
|
+
if result.returncode != 0:
|
29
|
+
logger.debug(f"Failed to get FFmpeg version: {result.stderr}")
|
30
|
+
return None
|
31
|
+
|
32
|
+
# Extract version from first line: "ffmpeg version 7.1.1-1ubuntu1.2 Copyright..."
|
33
|
+
version_line = result.stdout.split('\n')[0]
|
34
|
+
version_match = re.search(r'ffmpeg version (\d+)\.(\d+)\.(\d+)', version_line)
|
35
|
+
|
36
|
+
if version_match:
|
37
|
+
major, minor, patch = map(int, version_match.groups())
|
38
|
+
logger.debug(f"FFmpeg version: {major}.{minor}.{patch}")
|
39
|
+
return (major, minor, patch)
|
40
|
+
else:
|
41
|
+
logger.debug(f"Could not parse FFmpeg version from: {version_line}")
|
42
|
+
return None
|
43
|
+
|
44
|
+
except Exception as e:
|
45
|
+
logger.debug(f"Error getting FFmpeg version: {e}")
|
46
|
+
return None
|
47
|
+
|
48
|
+
|
49
|
+
def _check_ffmpeg_version() -> bool:
|
50
|
+
"""
|
51
|
+
Check if FFmpeg version meets minimum requirements.
|
52
|
+
|
53
|
+
Returns:
|
54
|
+
bool: True if version is sufficient, False otherwise
|
55
|
+
"""
|
56
|
+
version = _get_ffmpeg_version()
|
57
|
+
if version is None:
|
58
|
+
logger.warning("Could not determine FFmpeg version - proceeding with caution")
|
59
|
+
return True # Don't fail if we can't determine version
|
60
|
+
|
61
|
+
if version >= MIN_FFMPEG_VERSION:
|
62
|
+
logger.debug(f"✓ FFmpeg version {version[0]}.{version[1]}.{version[2]} meets minimum requirement {MIN_FFMPEG_VERSION[0]}.{MIN_FFMPEG_VERSION[1]}.{MIN_FFMPEG_VERSION[2]}")
|
63
|
+
return True
|
64
|
+
else:
|
65
|
+
logger.warning(f"⚠ FFmpeg version {version[0]}.{version[1]}.{version[2]} is below minimum requirement {MIN_FFMPEG_VERSION[0]}.{MIN_FFMPEG_VERSION[1]}.{MIN_FFMPEG_VERSION[2]}")
|
66
|
+
logger.warning("Hardware acceleration may not work properly. Please upgrade FFmpeg.")
|
67
|
+
return False
|
68
|
+
|
69
|
+
|
70
|
+
def _get_ffmpeg_hwaccels() -> List[str]:
|
71
|
+
"""
|
72
|
+
Get list of available FFmpeg hardware accelerators.
|
73
|
+
|
74
|
+
Returns:
|
75
|
+
List[str]: Available hardware accelerators
|
76
|
+
"""
|
77
|
+
try:
|
78
|
+
result = subprocess.run(['ffmpeg', '-hwaccels'], capture_output=True, text=True, timeout=5)
|
79
|
+
if result.returncode != 0:
|
80
|
+
logger.debug(f"Failed to get FFmpeg hardware accelerators: {result.stderr}")
|
81
|
+
return []
|
82
|
+
|
83
|
+
hwaccels = []
|
84
|
+
for line in result.stdout.split('\n'):
|
85
|
+
line = line.strip()
|
86
|
+
if line and not line.startswith('Hardware acceleration methods:'):
|
87
|
+
hwaccels.append(line)
|
88
|
+
|
89
|
+
return hwaccels
|
90
|
+
except Exception as e:
|
91
|
+
logger.debug(f"Error getting FFmpeg hardware accelerators: {e}")
|
92
|
+
return []
|
93
|
+
|
94
|
+
|
95
|
+
def _is_hwaccel_available(hwaccel: str) -> bool:
|
96
|
+
"""
|
97
|
+
Check if a specific hardware acceleration is available.
|
98
|
+
|
99
|
+
Args:
|
100
|
+
hwaccel: Hardware acceleration type to check
|
101
|
+
|
102
|
+
Returns:
|
103
|
+
bool: True if available, False otherwise
|
104
|
+
"""
|
105
|
+
available_hwaccels = _get_ffmpeg_hwaccels()
|
106
|
+
is_available = hwaccel in available_hwaccels
|
107
|
+
|
108
|
+
if is_available:
|
109
|
+
logger.debug(f"✓ {hwaccel} hardware acceleration is available")
|
110
|
+
else:
|
111
|
+
logger.debug(f"✗ {hwaccel} hardware acceleration is not available")
|
112
|
+
|
113
|
+
return is_available
|
114
|
+
|
115
|
+
|
116
|
+
def _test_hwaccel_functionality(hwaccel: str, device_path: Optional[str] = None) -> bool:
|
117
|
+
"""
|
118
|
+
Test if hardware acceleration actually works by running a simple FFmpeg command.
|
119
|
+
|
120
|
+
Args:
|
121
|
+
hwaccel: Hardware acceleration type to test
|
122
|
+
device_path: Optional device path for VAAPI
|
123
|
+
|
124
|
+
Returns:
|
125
|
+
bool: True if hardware acceleration works, False otherwise
|
126
|
+
"""
|
127
|
+
try:
|
128
|
+
# Build FFmpeg command based on acceleration type
|
129
|
+
if hwaccel == 'cuda':
|
130
|
+
cmd = ['ffmpeg', '-f', 'lavfi', '-i', 'testsrc=duration=0.1:size=320x240:rate=1',
|
131
|
+
'-c:v', 'h264_nvenc', '-t', '0.1', '-f', 'null', '/dev/null']
|
132
|
+
elif hwaccel == 'vaapi' and device_path:
|
133
|
+
# For VAAPI, test hardware acceleration initialization rather than encoding
|
134
|
+
# since encoding often fails due to driver issues even when hwaccel works
|
135
|
+
cmd = ['ffmpeg', '-hwaccel', 'vaapi', '-vaapi_device', device_path,
|
136
|
+
'-f', 'lavfi', '-i', 'testsrc=duration=0.1:size=320x240:rate=1',
|
137
|
+
'-t', '0.1', '-f', 'null', '/dev/null']
|
138
|
+
elif hwaccel == 'qsv':
|
139
|
+
cmd = ['ffmpeg', '-f', 'lavfi', '-i', 'testsrc=duration=0.1:size=320x240:rate=1',
|
140
|
+
'-c:v', 'h264_qsv', '-t', '0.1', '-f', 'null', '/dev/null']
|
141
|
+
elif hwaccel == 'd3d11va':
|
142
|
+
cmd = ['ffmpeg', '-f', 'lavfi', '-i', 'testsrc=duration=0.1:size=320x240:rate=1',
|
143
|
+
'-c:v', 'h264_nvenc', '-t', '0.1', '-f', 'null', '/dev/null'] # WSL2 can use NVENC
|
144
|
+
else:
|
145
|
+
# For other types, just test basic hardware acceleration
|
146
|
+
cmd = ['ffmpeg', '-hwaccel', hwaccel, '-f', 'lavfi', '-i', 'testsrc=duration=0.1:size=320x240:rate=1',
|
147
|
+
'-t', '0.1', '-f', 'null', '/dev/null']
|
148
|
+
|
149
|
+
logger.debug(f"Testing {hwaccel} functionality: {' '.join(cmd)}")
|
150
|
+
result = subprocess.run(cmd, capture_output=True, timeout=10)
|
151
|
+
|
152
|
+
# FFmpeg returns 0 for success, 141 for SIGPIPE (which is OK for our test)
|
153
|
+
if result.returncode in [0, 141]:
|
154
|
+
logger.debug(f"✓ {hwaccel} functionality test passed")
|
155
|
+
return True
|
156
|
+
else:
|
157
|
+
logger.debug(f"✗ {hwaccel} functionality test failed (exit code: {result.returncode})")
|
158
|
+
if result.stderr:
|
159
|
+
stderr_lines = result.stderr.decode('utf-8', 'ignore').split('\n')[-3:]
|
160
|
+
logger.debug(f"Error output: {' '.join(stderr_lines)}")
|
161
|
+
return False
|
162
|
+
|
163
|
+
except subprocess.TimeoutExpired:
|
164
|
+
logger.debug(f"✗ {hwaccel} functionality test timed out")
|
165
|
+
return False
|
166
|
+
except Exception as e:
|
167
|
+
logger.debug(f"✗ {hwaccel} functionality test failed with exception: {e}")
|
168
|
+
return False
|
169
|
+
|
170
|
+
|
171
|
+
def _get_gpu_devices() -> List[Tuple[str, str, str]]:
|
172
|
+
"""
|
173
|
+
Get all GPU devices with their render devices and driver information.
|
174
|
+
|
175
|
+
Returns:
|
176
|
+
List[Tuple[str, str, str]]: List of (card_name, render_device, driver) tuples
|
177
|
+
"""
|
178
|
+
devices = []
|
179
|
+
drm_dir = "/sys/class/drm"
|
180
|
+
|
181
|
+
if not os.path.exists(drm_dir):
|
182
|
+
logger.debug(f"DRM directory {drm_dir} does not exist")
|
183
|
+
return devices
|
184
|
+
|
185
|
+
try:
|
186
|
+
entries = os.listdir(drm_dir)
|
187
|
+
logger.debug(f"Scanning DRM devices: {entries}")
|
188
|
+
|
189
|
+
for entry in entries:
|
190
|
+
if not entry.startswith("card") or "-" in entry:
|
191
|
+
continue # Skip card1-HDMI-A-1, card0-DP-2, etc.
|
192
|
+
|
193
|
+
# Extract card number
|
194
|
+
try:
|
195
|
+
card_num = int(entry[4:]) # card0 -> 0, card1 -> 1
|
196
|
+
except ValueError:
|
197
|
+
continue
|
198
|
+
|
199
|
+
# Get render device for this card
|
200
|
+
# The mapping is: card0 -> renderD129, card1 -> renderD128
|
201
|
+
render_device = None
|
202
|
+
for render_entry in entries:
|
203
|
+
if render_entry == f"renderD{129 - card_num}": # card0 -> renderD129, card1 -> renderD128
|
204
|
+
render_device = f"/dev/dri/{render_entry}"
|
205
|
+
break
|
206
|
+
|
207
|
+
if not render_device:
|
208
|
+
logger.debug(f"No render device found for {entry}")
|
209
|
+
continue
|
210
|
+
|
211
|
+
# Get driver information
|
212
|
+
driver_path = os.path.join(drm_dir, entry, "device", "driver")
|
213
|
+
driver = "unknown"
|
214
|
+
if os.path.islink(driver_path):
|
215
|
+
driver = os.path.basename(os.readlink(driver_path))
|
216
|
+
|
217
|
+
devices.append((entry, render_device, driver))
|
218
|
+
logger.debug(f"Found GPU: {entry} -> {render_device} (driver: {driver})")
|
219
|
+
|
220
|
+
except Exception as e:
|
221
|
+
logger.debug(f"Error scanning GPU devices: {e}")
|
222
|
+
|
223
|
+
return devices
|
224
|
+
|
225
|
+
|
226
|
+
def _determine_vaapi_gpu_type(device_path: str) -> str:
|
227
|
+
"""
|
228
|
+
Determine GPU type for VAAPI device by checking driver information.
|
229
|
+
|
230
|
+
Args:
|
231
|
+
device_path: Path to VAAPI device
|
232
|
+
|
233
|
+
Returns:
|
234
|
+
str: GPU type ('AMD', 'INTEL', 'NVIDIA', 'ARM', 'VIDEOCORE', or 'UNKNOWN')
|
235
|
+
"""
|
236
|
+
logger.debug(f"Determining GPU type for VAAPI device: {device_path}")
|
237
|
+
|
238
|
+
try:
|
239
|
+
drm_dir = "/sys/class/drm"
|
240
|
+
if not os.path.exists(drm_dir):
|
241
|
+
logger.debug(f"DRM directory {drm_dir} does not exist")
|
242
|
+
return 'UNKNOWN'
|
243
|
+
|
244
|
+
entries = os.listdir(drm_dir)
|
245
|
+
logger.debug(f"Found DRM entries: {entries}")
|
246
|
+
|
247
|
+
for entry in entries:
|
248
|
+
if not entry.startswith("card"):
|
249
|
+
continue
|
250
|
+
|
251
|
+
driver_path = os.path.join(drm_dir, entry, "device", "driver")
|
252
|
+
if os.path.islink(driver_path):
|
253
|
+
driver_name = os.path.basename(os.readlink(driver_path))
|
254
|
+
logger.debug(f"Driver for {entry}: {driver_name}")
|
255
|
+
|
256
|
+
# Intel drivers
|
257
|
+
if driver_name == "i915":
|
258
|
+
logger.debug("Detected Intel i915 driver - GPU type: INTEL")
|
259
|
+
return 'INTEL'
|
260
|
+
|
261
|
+
# AMD drivers
|
262
|
+
elif driver_name in ("amdgpu", "radeon"):
|
263
|
+
logger.debug(f"Detected AMD driver {driver_name} - GPU type: AMD")
|
264
|
+
return 'AMD'
|
265
|
+
|
266
|
+
# ARM Mali drivers
|
267
|
+
elif driver_name == "panfrost":
|
268
|
+
logger.debug("Detected ARM Mali panfrost driver - GPU type: ARM")
|
269
|
+
return 'ARM'
|
270
|
+
|
271
|
+
# VideoCore (Raspberry Pi)
|
272
|
+
elif driver_name == "vc4":
|
273
|
+
logger.debug("Detected VideoCore vc4 driver - GPU type: VIDEOCORE")
|
274
|
+
return 'VIDEOCORE'
|
275
|
+
|
276
|
+
# Other drivers - try to detect from lspci
|
277
|
+
else:
|
278
|
+
logger.debug(f"Unknown driver {driver_name}, attempting lspci detection")
|
279
|
+
gpu_type = _detect_gpu_type_from_lspci()
|
280
|
+
if gpu_type != 'UNKNOWN':
|
281
|
+
return gpu_type
|
282
|
+
|
283
|
+
logger.debug("No suitable driver found, defaulting to UNKNOWN")
|
284
|
+
return 'UNKNOWN'
|
285
|
+
except Exception as e:
|
286
|
+
logger.debug(f"Error determining VAAPI GPU type: {e}")
|
287
|
+
return 'UNKNOWN'
|
288
|
+
|
289
|
+
|
290
|
+
def _detect_gpu_type_from_lspci() -> str:
|
291
|
+
"""
|
292
|
+
Detect GPU type using lspci as fallback when driver detection fails.
|
293
|
+
|
294
|
+
Returns:
|
295
|
+
str: GPU type ('AMD', 'INTEL', 'NVIDIA', 'ARM', or 'UNKNOWN')
|
296
|
+
"""
|
297
|
+
try:
|
298
|
+
result = subprocess.run(['lspci'], capture_output=True, text=True, timeout=5)
|
299
|
+
if result.returncode != 0:
|
300
|
+
logger.debug("lspci command failed")
|
301
|
+
return 'UNKNOWN'
|
302
|
+
|
303
|
+
for line in result.stdout.split('\n'):
|
304
|
+
if 'VGA' in line or 'Display' in line:
|
305
|
+
line_lower = line.lower()
|
306
|
+
if 'amd' in line_lower or 'radeon' in line_lower:
|
307
|
+
logger.debug("lspci detected AMD GPU")
|
308
|
+
return 'AMD'
|
309
|
+
elif 'intel' in line_lower:
|
310
|
+
logger.debug("lspci detected Intel GPU")
|
311
|
+
return 'INTEL'
|
312
|
+
elif 'nvidia' in line_lower or 'geforce' in line_lower:
|
313
|
+
logger.debug("lspci detected NVIDIA GPU")
|
314
|
+
return 'NVIDIA'
|
315
|
+
elif 'mali' in line_lower or 'arm' in line_lower:
|
316
|
+
logger.debug("lspci detected ARM GPU")
|
317
|
+
return 'ARM'
|
318
|
+
|
319
|
+
logger.debug("lspci did not identify GPU type")
|
320
|
+
return 'UNKNOWN'
|
321
|
+
except Exception as e:
|
322
|
+
logger.debug(f"Error running lspci: {e}")
|
323
|
+
return 'UNKNOWN'
|
324
|
+
|
325
|
+
|
326
|
+
def _log_system_info() -> None:
|
327
|
+
"""Log system information for debugging GPU detection issues."""
|
328
|
+
logger.debug("=== System Information ===")
|
329
|
+
logger.debug(f"Platform: {platform.platform()}")
|
330
|
+
logger.debug(f"Python version: {platform.python_version()}")
|
331
|
+
logger.debug(f"FFmpeg path: {os.environ.get('FFMPEG_PATH', 'ffmpeg')}")
|
332
|
+
|
333
|
+
# Check FFmpeg version
|
334
|
+
_check_ffmpeg_version()
|
335
|
+
|
336
|
+
# Log available hardware accelerators
|
337
|
+
hwaccels = _get_ffmpeg_hwaccels()
|
338
|
+
if hwaccels:
|
339
|
+
logger.debug(f"Available FFmpeg hardware accelerators: {hwaccels}")
|
340
|
+
|
341
|
+
# Log GPU device mapping
|
342
|
+
gpu_devices = _get_gpu_devices()
|
343
|
+
if gpu_devices:
|
344
|
+
logger.debug("GPU device mapping:")
|
345
|
+
for card_name, render_device, driver in gpu_devices:
|
346
|
+
logger.debug(f" {card_name} -> {render_device} (driver: {driver})")
|
347
|
+
else:
|
348
|
+
logger.debug("No GPU devices found")
|
349
|
+
|
350
|
+
logger.debug("=== End System Information ===")
|
351
|
+
|
352
|
+
|
353
|
+
def _parse_lspci_gpu_name(gpu_type: str) -> str:
|
354
|
+
"""
|
355
|
+
Parse GPU name from lspci output.
|
356
|
+
|
357
|
+
Args:
|
358
|
+
gpu_type: Type of GPU ('AMD', 'INTEL')
|
359
|
+
|
360
|
+
Returns:
|
361
|
+
str: GPU name or fallback description
|
362
|
+
"""
|
363
|
+
try:
|
364
|
+
result = subprocess.run(['lspci'], capture_output=True, text=True, timeout=5)
|
365
|
+
if result.returncode == 0:
|
366
|
+
for line in result.stdout.split('\n'):
|
367
|
+
if 'VGA' in line and (gpu_type == 'AMD' and 'AMD' in line or gpu_type == 'INTEL' and 'Intel' in line):
|
368
|
+
parts = line.split(':')
|
369
|
+
if len(parts) > 2:
|
370
|
+
return parts[2].strip()
|
371
|
+
except Exception as e:
|
372
|
+
logger.debug(f"Error parsing lspci for {gpu_type}: {e}")
|
373
|
+
|
374
|
+
return f"{gpu_type} GPU"
|
375
|
+
|
376
|
+
|
377
|
+
def get_gpu_name(gpu_type: str, gpu_device: str) -> str:
|
378
|
+
"""
|
379
|
+
Extract GPU model name from system.
|
380
|
+
|
381
|
+
Args:
|
382
|
+
gpu_type: Type of GPU ('NVIDIA', 'AMD', 'INTEL', 'WSL2')
|
383
|
+
gpu_device: GPU device path or info string
|
384
|
+
|
385
|
+
Returns:
|
386
|
+
str: GPU model name or fallback description
|
387
|
+
"""
|
388
|
+
try:
|
389
|
+
if gpu_type == 'NVIDIA':
|
390
|
+
# Use nvidia-smi to get GPU name
|
391
|
+
result = subprocess.run(['nvidia-smi', '--query-gpu=name', '--format=csv,noheader,nounits'],
|
392
|
+
capture_output=True, text=True, timeout=5)
|
393
|
+
if result.returncode == 0:
|
394
|
+
gpu_names = [line.strip() for line in result.stdout.strip().split('\n') if line.strip()]
|
395
|
+
if gpu_names:
|
396
|
+
return gpu_names[0] # Return first GPU name
|
397
|
+
return "NVIDIA GPU (CUDA)"
|
398
|
+
|
399
|
+
elif gpu_type == 'WSL2':
|
400
|
+
return "WSL2 GPU (D3D11VA)"
|
401
|
+
|
402
|
+
elif gpu_type == 'INTEL' and gpu_device == 'qsv':
|
403
|
+
# Try to get Intel GPU info
|
404
|
+
gpu_name = _parse_lspci_gpu_name('INTEL')
|
405
|
+
return f"{gpu_name} (QSV)"
|
406
|
+
|
407
|
+
elif gpu_type in ('AMD', 'INTEL') and gpu_device.startswith('/dev/dri/'):
|
408
|
+
# Try to get GPU info from lspci
|
409
|
+
gpu_name = _parse_lspci_gpu_name(gpu_type)
|
410
|
+
return f"{gpu_name} (VAAPI)"
|
411
|
+
|
412
|
+
except Exception as e:
|
413
|
+
logger.debug(f"Error getting GPU name for {gpu_type}: {e}")
|
414
|
+
|
415
|
+
# Fallback
|
416
|
+
return f"{gpu_type} GPU"
|
417
|
+
|
418
|
+
|
419
|
+
def format_gpu_info(gpu_type: str, gpu_device: str, gpu_name: str) -> str:
|
420
|
+
"""
|
421
|
+
Format GPU information for display.
|
422
|
+
|
423
|
+
Args:
|
424
|
+
gpu_type: Type of GPU
|
425
|
+
gpu_device: GPU device path or info
|
426
|
+
gpu_name: GPU model name
|
427
|
+
|
428
|
+
Returns:
|
429
|
+
str: Formatted GPU description
|
430
|
+
"""
|
431
|
+
if gpu_type == 'NVIDIA':
|
432
|
+
return f"{gpu_name} (CUDA)"
|
433
|
+
elif gpu_type == 'WSL2':
|
434
|
+
return f"{gpu_name} (D3D11VA)"
|
435
|
+
elif gpu_type == 'INTEL' and gpu_device == 'qsv':
|
436
|
+
return f"{gpu_name} (QSV)"
|
437
|
+
elif gpu_type in ('AMD', 'INTEL', 'ARM', 'VIDEOCORE') and gpu_device.startswith('/dev/dri/'):
|
438
|
+
return f"{gpu_name} (VAAPI - {gpu_device})"
|
439
|
+
elif gpu_type == 'UNKNOWN':
|
440
|
+
return f"{gpu_name} (Unknown GPU)"
|
441
|
+
else:
|
442
|
+
return f"{gpu_name} ({gpu_type})"
|
443
|
+
|
444
|
+
|
445
|
+
def detect_all_gpus() -> List[Tuple[str, str, dict]]:
|
446
|
+
"""
|
447
|
+
Detect all available GPU hardware using FFmpeg capability detection.
|
448
|
+
|
449
|
+
Checks FFmpeg's available hardware acceleration capabilities and returns
|
450
|
+
all working GPUs instead of just the first one.
|
451
|
+
|
452
|
+
Returns:
|
453
|
+
List[Tuple[str, str, dict]]: List of (gpu_type, gpu_device, gpu_info_dict)
|
454
|
+
- gpu_type: 'NVIDIA', 'AMD', 'INTEL', 'WSL2'
|
455
|
+
- gpu_device: Device path or info string
|
456
|
+
- gpu_info_dict: Dictionary with GPU details (name, vram, etc.)
|
457
|
+
"""
|
458
|
+
logger.debug("=== Starting Multi-GPU Detection ===")
|
459
|
+
_log_system_info()
|
460
|
+
logger.debug("Checking FFmpeg hardware acceleration capabilities for all GPUs")
|
461
|
+
|
462
|
+
detected_gpus = []
|
463
|
+
|
464
|
+
# Check NVIDIA CUDA (can have multiple GPUs)
|
465
|
+
logger.debug("1. Checking NVIDIA CUDA acceleration...")
|
466
|
+
if _is_hwaccel_available('cuda') and _test_hwaccel_functionality('cuda'):
|
467
|
+
logger.debug("✓ NVIDIA CUDA hardware acceleration is available and working")
|
468
|
+
gpu_name = get_gpu_name('NVIDIA', 'cuda')
|
469
|
+
gpu_info = {
|
470
|
+
'name': gpu_name,
|
471
|
+
'acceleration': 'CUDA',
|
472
|
+
'device_path': 'cuda'
|
473
|
+
}
|
474
|
+
detected_gpus.append(('NVIDIA', 'cuda', gpu_info))
|
475
|
+
|
476
|
+
# Check WSL2 D3D11VA (usually single GPU)
|
477
|
+
logger.debug("2. Checking WSL2 D3D11VA acceleration...")
|
478
|
+
if _is_hwaccel_available('d3d11va') and _test_hwaccel_functionality('d3d11va'):
|
479
|
+
logger.debug("✓ WSL2 D3D11VA hardware acceleration is available and working")
|
480
|
+
gpu_name = get_gpu_name('WSL2', 'd3d11va')
|
481
|
+
gpu_info = {
|
482
|
+
'name': gpu_name,
|
483
|
+
'acceleration': 'D3D11VA',
|
484
|
+
'device_path': 'd3d11va'
|
485
|
+
}
|
486
|
+
detected_gpus.append(('WSL2', 'd3d11va', gpu_info))
|
487
|
+
|
488
|
+
# Check Intel QSV (usually single GPU)
|
489
|
+
logger.debug("3. Checking Intel QSV acceleration...")
|
490
|
+
if _is_hwaccel_available('qsv') and _test_hwaccel_functionality('qsv'):
|
491
|
+
logger.debug("✓ Intel QSV hardware acceleration is available and working")
|
492
|
+
gpu_name = get_gpu_name('INTEL', 'qsv')
|
493
|
+
gpu_info = {
|
494
|
+
'name': gpu_name,
|
495
|
+
'acceleration': 'QSV',
|
496
|
+
'device_path': 'qsv'
|
497
|
+
}
|
498
|
+
detected_gpus.append(('INTEL', 'qsv', gpu_info))
|
499
|
+
|
500
|
+
# Check VAAPI (can have multiple devices)
|
501
|
+
logger.debug("4. Checking VAAPI acceleration...")
|
502
|
+
if _is_hwaccel_available('vaapi'):
|
503
|
+
logger.debug("VAAPI acceleration is available, searching for devices...")
|
504
|
+
vaapi_devices = _find_all_vaapi_devices()
|
505
|
+
for device_path in vaapi_devices:
|
506
|
+
if _test_hwaccel_functionality('vaapi', device_path):
|
507
|
+
gpu_type = _determine_vaapi_gpu_type(device_path)
|
508
|
+
gpu_name = get_gpu_name(gpu_type, device_path)
|
509
|
+
gpu_info = {
|
510
|
+
'name': gpu_name,
|
511
|
+
'acceleration': 'VAAPI',
|
512
|
+
'device_path': device_path
|
513
|
+
}
|
514
|
+
detected_gpus.append((gpu_type, device_path, gpu_info))
|
515
|
+
logger.debug(f"✓ {gpu_type} VAAPI hardware acceleration is available and working with device {device_path}")
|
516
|
+
|
517
|
+
logger.debug(f"=== Multi-GPU Detection Complete: Found {len(detected_gpus)} GPUs ===")
|
518
|
+
return detected_gpus
|
519
|
+
|
520
|
+
|
521
|
+
def _find_all_vaapi_devices() -> List[str]:
|
522
|
+
"""
|
523
|
+
Find all available VAAPI devices.
|
524
|
+
|
525
|
+
Returns:
|
526
|
+
List[str]: List of VAAPI device paths
|
527
|
+
"""
|
528
|
+
devices = []
|
529
|
+
gpu_devices = _get_gpu_devices()
|
530
|
+
|
531
|
+
if not gpu_devices:
|
532
|
+
logger.debug("No GPU devices found for VAAPI")
|
533
|
+
return devices
|
534
|
+
|
535
|
+
# Add all GPU devices as potential VAAPI devices
|
536
|
+
for card_name, render_device, driver in gpu_devices:
|
537
|
+
devices.append(render_device)
|
538
|
+
logger.debug(f"Found potential VAAPI device: {render_device} (card: {card_name}, driver: {driver})")
|
539
|
+
|
540
|
+
return devices
|
541
|
+
|