Detect the host's GPU vendor(s) via PCI sysfs (no lspci dependency) and append vendor-matched encoding-package steps to the install plan: Intel -> intel-media-driver + vpl-gpu-rt + intel-compute-runtime, AMD -> mesa + libva-mesa-driver. NVIDIA NVENC ships with the driver's own userspace, so nothing is added when a driver is loaded, and a warning step (never an automatic kernel-driver install) when it isn't. Without these packages Jellyfin installs fine and direct-plays fine, but any client that needs a transcode fails instantly with the opaque "FFmpeg exited with code 251" / "fatal playback error" pair. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
74 lines
1.9 KiB
Go
74 lines
1.9 KiB
Go
package system
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// GPU vendor detection for hardware-accelerated transcoding. We walk
|
|
// /sys/bus/pci/devices directly instead of shelling out to lspci so the
|
|
// pre-flight needs no extra package and works before any GPU driver is
|
|
// loaded (an unbound card still has its PCI class/vendor files).
|
|
|
|
type GPUVendor string
|
|
|
|
const (
|
|
VendorIntel GPUVendor = "intel"
|
|
VendorAMD GPUVendor = "amd"
|
|
VendorNVIDIA GPUVendor = "nvidia"
|
|
)
|
|
|
|
// DetectGPUs returns the distinct GPU vendors present on the host, in a
|
|
// stable intel/amd/nvidia order. Vendors jellyfin-ffmpeg has no encoder
|
|
// for are ignored.
|
|
func DetectGPUs() []GPUVendor {
|
|
return detectGPUs("/sys/bus/pci/devices")
|
|
}
|
|
|
|
func detectGPUs(sysfsRoot string) []GPUVendor {
|
|
entries, err := os.ReadDir(sysfsRoot)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
seen := map[GPUVendor]bool{}
|
|
for _, e := range entries {
|
|
dir := filepath.Join(sysfsRoot, e.Name())
|
|
// PCI class 0x03xxxx = display controller (VGA, 3D, other).
|
|
class := readSysfsHex(filepath.Join(dir, "class"))
|
|
if !strings.HasPrefix(class, "0x03") {
|
|
continue
|
|
}
|
|
switch readSysfsHex(filepath.Join(dir, "vendor")) {
|
|
case "0x8086":
|
|
seen[VendorIntel] = true
|
|
case "0x1002":
|
|
seen[VendorAMD] = true
|
|
case "0x10de":
|
|
seen[VendorNVIDIA] = true
|
|
}
|
|
}
|
|
var out []GPUVendor
|
|
for _, v := range []GPUVendor{VendorIntel, VendorAMD, VendorNVIDIA} {
|
|
if seen[v] {
|
|
out = append(out, v)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// NvidiaDriverLoaded reports whether the proprietary/open NVIDIA kernel
|
|
// driver is active. When it is, its userspace (nvidia-utils) is already
|
|
// installed as a dependency, which is all NVENC needs — no extra packages.
|
|
func NvidiaDriverLoaded() bool {
|
|
_, err := os.Stat("/proc/driver/nvidia")
|
|
return err == nil
|
|
}
|
|
|
|
func readSysfsHex(path string) string {
|
|
b, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return strings.ToLower(strings.TrimSpace(string(b)))
|
|
}
|