Enhanced FFmpeg Integration:

Added robust error handling and logging
Improved binary verification and initialization
Added proper GPU detection and hardware acceleration
Optimized encoding parameters for different content types
Improved File Operations:

Added retry mechanisms for file operations
Enhanced temporary directory management
Improved cleanup of failed downloads
Added proper permission handling
Enhanced Queue Management:

Fixed queue manager initialization
Added better error recovery
Improved status tracking and logging
Enhanced cleanup of failed items
Better Error Handling:

Added comprehensive exception hierarchy
Improved error logging and reporting
Added fallback mechanisms for failures
Enhanced error recovery strategies
This commit is contained in:
pacnpal
2024-11-15 03:21:25 +00:00
parent a04c576e0a
commit 8503fc6fdd
13 changed files with 1336 additions and 376 deletions

View File

@@ -1,130 +1,271 @@
"""GPU detection functionality for FFmpeg"""
import os
import json
import subprocess
import logging
import platform
import re
from typing import Dict, List
from pathlib import Path
from typing import Dict
logger = logging.getLogger("VideoArchiver")
class GPUDetector:
def __init__(self, ffmpeg_path: Path):
self.ffmpeg_path = ffmpeg_path
"""Initialize GPU detector
Args:
ffmpeg_path: Path to FFmpeg binary
"""
self.ffmpeg_path = Path(ffmpeg_path)
if not self.ffmpeg_path.exists():
raise FileNotFoundError(f"FFmpeg not found at {self.ffmpeg_path}")
def detect_gpu(self) -> Dict[str, bool]:
"""Detect available GPU and its capabilities"""
gpu_info = {"nvidia": False, "amd": False, "intel": False, "arm": False}
"""Detect available GPU acceleration support
Returns:
Dict containing boolean flags for each GPU type
"""
gpu_info = {
"nvidia": False,
"amd": False,
"intel": False
}
try:
if os.name == "posix": # Linux/Unix
gpu_info.update(self._detect_linux_gpu())
elif os.name == "nt": # Windows
# 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())
# 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)}")
else:
logger.info("No GPU acceleration support detected")
return gpu_info
except Exception as e:
logger.warning(f"GPU detection failed: {str(e)}")
return gpu_info
def _test_encoder(self, encoder: str) -> bool:
"""Test if a specific encoder works"""
try:
test_cmd = [
str(self.ffmpeg_path),
"-f", "lavfi",
"-i", "testsrc=duration=1:size=1280x720:rate=30",
"-c:v", encoder,
"-f", "null",
"-"
]
result = subprocess.run(
test_cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=5
)
return result.returncode == 0
except Exception:
return False
def _detect_linux_gpu(self) -> Dict[str, bool]:
"""Detect GPUs on Linux systems"""
gpu_info = {"nvidia": False, "amd": False, "intel": False, "arm": False}
# Check for NVIDIA GPU
try:
nvidia_smi = subprocess.run(
["nvidia-smi", "-q", "-d", "ENCODER"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=5
)
if nvidia_smi.returncode == 0 and b"Encoder" in nvidia_smi.stdout:
gpu_info["nvidia"] = self._test_encoder("h264_nvenc")
except (subprocess.TimeoutExpired, FileNotFoundError):
pass
# Check for AMD GPU
try:
if os.path.exists("/dev/dri/renderD128"):
with open("/sys/class/drm/renderD128/device/vendor", "r") as f:
vendor = f.read().strip()
if vendor == "0x1002": # AMD vendor ID
gpu_info["amd"] = self._test_encoder("h264_amf")
except (IOError, OSError):
pass
# Check for Intel GPU
try:
lspci = subprocess.run(
["lspci"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=5
)
output = lspci.stdout.decode().lower()
if "intel" in output and ("vga" in output or "display" in output):
gpu_info["intel"] = self._test_encoder("h264_qsv")
except (subprocess.TimeoutExpired, FileNotFoundError):
pass
# Check for ARM GPU
if os.uname().machine.startswith(("aarch64", "armv7")):
gpu_info["arm"] = True
return gpu_info
logger.error(f"Error during GPU detection: {str(e)}")
return {"nvidia": False, "amd": False, "intel": False}
def _detect_windows_gpu(self) -> Dict[str, bool]:
"""Detect GPUs on Windows systems"""
gpu_info = {"nvidia": False, "amd": False, "intel": False, "arm": False}
"""Detect GPUs on Windows using PowerShell"""
gpu_info = {"nvidia": False, "amd": False, "intel": False}
try:
# Use PowerShell to get GPU info
ps_command = "Get-WmiObject Win32_VideoController | ConvertTo-Json"
result = subprocess.run(
["powershell", "-Command", ps_command],
capture_output=True,
text=True,
timeout=10
)
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:
gpu_data = json.loads(result.stdout)
if not isinstance(gpu_data, list):
gpu_data = [gpu_data]
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
except Exception as e:
logger.error(f"Windows GPU detection failed: {str(e)}")
return gpu_info
for gpu in gpu_data:
name = gpu.get("Name", "").lower()
if "nvidia" in name:
gpu_info["nvidia"] = self._test_encoder("h264_nvenc")
if "amd" in name or "radeon" in name:
gpu_info["amd"] = self._test_encoder("h264_amf")
if "intel" in name:
gpu_info["intel"] = self._test_encoder("h264_qsv")
def _detect_linux_gpu(self) -> Dict[str, bool]:
"""Detect GPUs on Linux using lspci and other tools"""
gpu_info = {"nvidia": False, "amd": False, "intel": False}
try:
# Try lspci first
try:
result = subprocess.run(
["lspci", "-v"],
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
except FileNotFoundError:
pass
# Check for NVIDIA using nvidia-smi
if not gpu_info["nvidia"]:
try:
result = subprocess.run(
["nvidia-smi"],
capture_output=True,
timeout=10
)
gpu_info["nvidia"] = result.returncode == 0
except FileNotFoundError:
pass
# Check for AMD using rocm-smi
if not gpu_info["amd"]:
try:
result = subprocess.run(
["rocm-smi"],
capture_output=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:
pass
except Exception as e:
logger.error(f"Error during Windows GPU detection: {str(e)}")
logger.error(f"Linux GPU detection failed: {str(e)}")
return gpu_info
def _detect_macos_gpu(self) -> Dict[str, bool]:
"""Detect GPUs on macOS using system_profiler"""
gpu_info = {"nvidia": False, "amd": False, "intel": False}
try:
cmd = ["system_profiler", "SPDisplaysDataType"]
result = subprocess.run(cmd, 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
except Exception as e:
logger.error(f"macOS GPU detection failed: {str(e)}")
return gpu_info
def _verify_ffmpeg_gpu_support(self) -> Dict[str, bool]:
"""Verify GPU support in FFmpeg installation"""
gpu_support = {"nvidia": False, "amd": False, "intel": False}
try:
# Check FFmpeg encoders
cmd = [str(self.ffmpeg_path), "-hide_banner", "-encoders"]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
if result.returncode == 0:
output = result.stdout.lower()
# Check for specific GPU encoders
gpu_support["nvidia"] = "h264_nvenc" in output
gpu_support["amd"] = "h264_amf" in output
gpu_support["intel"] = "h264_qsv" in output
# Log available encoders
encoders = []
if gpu_support["nvidia"]:
encoders.append("NVENC")
if gpu_support["amd"]:
encoders.append("AMF")
if gpu_support["intel"]:
encoders.append("QSV")
if encoders:
logger.info(f"FFmpeg supports GPU encoders: {', '.join(encoders)}")
else:
logger.info("No GPU encoders available in FFmpeg")
except Exception as e:
logger.error(f"FFmpeg GPU support verification failed: {str(e)}")
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
"""
gpu_info = {
"nvidia": [],
"amd": [],
"intel": []
}
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":
try:
result = subprocess.run(
["lspci", "-v"],
capture_output=True,
text=True,
timeout=10
)
if result.returncode == 0:
for line in result.stdout.splitlines():
if "vga" in line.lower() or "3d" in line.lower():
if "nvidia" in line.lower():
gpu_info["nvidia"].append(line.strip())
elif any(x in line.lower() for x in ["amd", "radeon"]):
gpu_info["amd"].append(line.strip())
elif "intel" in line.lower():
gpu_info["intel"].append(line.strip())
except FileNotFoundError:
pass
elif system == "darwin":
cmd = ["system_profiler", "SPDisplaysDataType"]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
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())
except Exception as e:
logger.error(f"Error getting detailed GPU info: {str(e)}")
return gpu_info