From 887547473cee26dc9e0b4e87f5d4c4366093db7c Mon Sep 17 00:00:00 2001 From: pacnpal <183241239+pacnpal@users.noreply.github.com> Date: Fri, 15 Nov 2024 05:26:10 +0000 Subject: [PATCH] Added proper physical GPU detection on Linux/Windows/macOS Separated physical GPU detection from FFmpeg encoder support Only enable GPU encoding when both physical GPU and FFmpeg support exist Added multiple detection methods for better reliability --- videoarchiver/ffmpeg/gpu_detector.py | 171 +++++++++++++++------------ 1 file changed, 93 insertions(+), 78 deletions(-) diff --git a/videoarchiver/ffmpeg/gpu_detector.py b/videoarchiver/ffmpeg/gpu_detector.py index 2957925..9dc32f2 100644 --- a/videoarchiver/ffmpeg/gpu_detector.py +++ b/videoarchiver/ffmpeg/gpu_detector.py @@ -5,7 +5,7 @@ import subprocess import logging import platform import re -from typing import Dict, List +from typing import Dict, List, Tuple from pathlib import Path logger = logging.getLogger("VideoArchiver") @@ -34,24 +34,23 @@ class GPUDetector: } try: - # Check system-specific GPU detection first - system = platform.system().lower() - if system == "windows": - gpu_info.update(self._detect_windows_gpu()) - elif system == "linux": - gpu_info.update(self._detect_linux_gpu()) - elif system == "darwin": - gpu_info.update(self._detect_macos_gpu()) - - # Verify GPU support in FFmpeg - gpu_info.update(self._verify_ffmpeg_gpu_support()) + # First detect physical GPUs + physical_gpus = self._detect_physical_gpus() + + # Then check FFmpeg support + ffmpeg_support = self._verify_ffmpeg_gpu_support() + + # Only enable GPU if both physical GPU exists and FFmpeg supports it + gpu_info["nvidia"] = physical_gpus["nvidia"] and ffmpeg_support["nvidia"] + gpu_info["amd"] = physical_gpus["amd"] and ffmpeg_support["amd"] + gpu_info["intel"] = physical_gpus["intel"] and ffmpeg_support["intel"] # Log detection results detected_gpus = [name for name, detected in gpu_info.items() if detected] if detected_gpus: - logger.info(f"Detected GPUs: {', '.join(detected_gpus)}") + logger.info(f"Detected GPUs with FFmpeg support: {', '.join(detected_gpus)}") else: - logger.info("No GPU acceleration support detected") + logger.info("No GPU acceleration available") return gpu_info @@ -59,6 +58,17 @@ class GPUDetector: logger.error(f"Error during GPU detection: {str(e)}") return {"nvidia": False, "amd": False, "intel": False} + def _detect_physical_gpus(self) -> Dict[str, bool]: + """Detect physical GPUs in the system""" + system = platform.system().lower() + if system == "windows": + return self._detect_windows_gpu() + elif system == "linux": + return self._detect_linux_gpu() + elif system == "darwin": + return self._detect_macos_gpu() + return {"nvidia": False, "amd": False, "intel": False} + def _detect_windows_gpu(self) -> Dict[str, bool]: """Detect GPUs on Windows using PowerShell""" gpu_info = {"nvidia": False, "amd": False, "intel": False} @@ -80,60 +90,56 @@ class GPUDetector: return gpu_info def _detect_linux_gpu(self) -> Dict[str, bool]: - """Detect GPUs on Linux using lspci and other tools""" + """Detect GPUs on Linux using multiple methods""" gpu_info = {"nvidia": False, "amd": False, "intel": False} try: - # Try lspci first + # Check for NVIDIA GPU try: result = subprocess.run( - ["lspci", "-v"], + ["nvidia-smi"], capture_output=True, - text=True, timeout=10 ) - if result.returncode == 0: - output = result.stdout.lower() - gpu_info["nvidia"] = "nvidia" in output - gpu_info["amd"] = any(x in output for x in ["amd", "radeon"]) - gpu_info["intel"] = "intel" in output + gpu_info["nvidia"] = result.returncode == 0 except FileNotFoundError: pass - # Check for NVIDIA using nvidia-smi - if not gpu_info["nvidia"]: + # Check for AMD GPU using DRI + if os.path.exists("/dev/dri"): try: result = subprocess.run( - ["nvidia-smi"], + ["ls", "/dev/dri/render*"], capture_output=True, + text=True, timeout=10 ) - gpu_info["nvidia"] = result.returncode == 0 - except FileNotFoundError: + if result.returncode == 0: + # Check device info using lspci + lspci_result = subprocess.run( + ["lspci", "-v"], + capture_output=True, + text=True, + timeout=10 + ) + if lspci_result.returncode == 0: + output = lspci_result.stdout.lower() + gpu_info["amd"] = any(x in output for x in ["amd", "radeon", "advanced micro devices"]) + gpu_info["intel"] = "intel" in output and "graphics" in output + except (FileNotFoundError, subprocess.SubprocessError): pass - # Check for AMD using rocm-smi - if not gpu_info["amd"]: + # Additional check for Intel GPU + if not gpu_info["intel"] and os.path.exists("/sys/class/drm"): try: result = subprocess.run( - ["rocm-smi"], + ["find", "/sys/class/drm", "-name", "*i915*"], capture_output=True, + text=True, timeout=10 ) - gpu_info["amd"] = result.returncode == 0 - except FileNotFoundError: - pass - - # Check for Intel using intel_gpu_top - if not gpu_info["intel"]: - try: - result = subprocess.run( - ["intel_gpu_top", "-L"], - capture_output=True, - timeout=10 - ) - gpu_info["intel"] = result.returncode == 0 - except FileNotFoundError: + gpu_info["intel"] = bool(result.stdout.strip()) + except subprocess.SubprocessError: pass except Exception as e: @@ -187,7 +193,7 @@ class GPUDetector: encoders.append("QSV") if encoders: - logger.info(f"FFmpeg supports GPU encoders: {', '.join(encoders)}") + logger.info(f"FFmpeg compiled with GPU encoders: {', '.join(encoders)}") else: logger.info("No GPU encoders available in FFmpeg") @@ -197,11 +203,7 @@ class GPUDetector: return gpu_support def get_gpu_info(self) -> Dict[str, List[str]]: - """Get detailed GPU information - - Returns: - Dict containing lists of GPU names by type - """ + """Get detailed GPU information""" gpu_info = { "nvidia": [], "amd": [], @@ -211,23 +213,9 @@ class GPUDetector: try: system = platform.system().lower() - if system == "windows": - cmd = ["powershell", "-Command", "Get-WmiObject Win32_VideoController | Select-Object Name"] - result = subprocess.run(cmd, capture_output=True, text=True, timeout=10) - - if result.returncode == 0: - for line in result.stdout.splitlines(): - line = line.strip().lower() - if line: - if "nvidia" in line: - gpu_info["nvidia"].append(line) - elif any(x in line for x in ["amd", "radeon"]): - gpu_info["amd"].append(line) - elif "intel" in line: - gpu_info["intel"].append(line) - - elif system == "linux": + if system == "linux": try: + # Try lspci first result = subprocess.run( ["lspci", "-v"], capture_output=True, @@ -246,6 +234,35 @@ class GPUDetector: except FileNotFoundError: pass + # Try nvidia-smi for NVIDIA GPUs + if not gpu_info["nvidia"]: + try: + result = subprocess.run( + ["nvidia-smi", "-L"], + capture_output=True, + text=True, + timeout=10 + ) + if result.returncode == 0: + gpu_info["nvidia"].extend(line.strip() for line in result.stdout.splitlines() if line.strip()) + except FileNotFoundError: + pass + + elif system == "windows": + cmd = ["powershell", "-Command", "Get-WmiObject Win32_VideoController | Select-Object Name"] + result = subprocess.run(cmd, capture_output=True, text=True, timeout=10) + + if result.returncode == 0: + for line in result.stdout.splitlines(): + line = line.strip() + if line and not line.startswith("Name"): + if "nvidia" in line.lower(): + gpu_info["nvidia"].append(line) + elif any(x in line.lower() for x in ["amd", "radeon"]): + gpu_info["amd"].append(line) + elif "intel" in line.lower(): + gpu_info["intel"].append(line) + elif system == "darwin": cmd = ["system_profiler", "SPDisplaysDataType"] result = subprocess.run(cmd, capture_output=True, text=True, timeout=10) @@ -253,17 +270,15 @@ class GPUDetector: if result.returncode == 0: current_gpu = None for line in result.stdout.splitlines(): - line = line.strip().lower() - if "chipset model" in line: - if "nvidia" in line: - current_gpu = "nvidia" - gpu_info["nvidia"].append(line.split(":")[1].strip()) - elif any(x in line for x in ["amd", "radeon"]): - current_gpu = "amd" - gpu_info["amd"].append(line.split(":")[1].strip()) - elif "intel" in line: - current_gpu = "intel" - gpu_info["intel"].append(line.split(":")[1].strip()) + line = line.strip() + if "Chipset Model:" in line: + model = line.split(":", 1)[1].strip() + if "nvidia" in model.lower(): + gpu_info["nvidia"].append(model) + elif any(x in model.lower() for x in ["amd", "radeon"]): + gpu_info["amd"].append(model) + elif "intel" in model.lower(): + gpu_info["intel"].append(model) except Exception as e: logger.error(f"Error getting detailed GPU info: {str(e)}")