TUISTREAM/internal/system/gpu.go
28allday 96c908f324 Auto-install GPU hardware-transcoding packages on Jellyfin install
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>
2026-07-18 13:58:22 +01:00

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)))
}