1. 增加配置持久化

2. 完善细节
This commit is contained in:
sansen
2026-07-29 19:33:46 +08:00
parent 86532cc2e9
commit a53e205bfb
12 changed files with 721 additions and 215 deletions
+84 -55
View File
@@ -5,14 +5,12 @@ import (
"log"
)
// GPUInfo holds detected GPU information.
type GPUInfo struct {
Name string `json:"name"`
Vendor string `json:"vendor"`
Encoders []EncoderCap `json:"encoders"`
}
// EncoderCap describes an encoder capability.
type EncoderCap struct {
Name string `json:"name"`
Codec string `json:"codec"`
@@ -20,10 +18,7 @@ type EncoderCap struct {
Available bool `json:"available"`
}
// knownEncoders defines all known hardware encoders by vendor.
type knownEnc struct {
Name, Vendor, Codec, Label string
}
type knownEnc struct{ Name, Vendor, Codec, Label string }
var allEncoders = []knownEnc{
{"h264_nvenc", "nvidia", "h264", "H.264 NVENC"},
@@ -37,72 +32,111 @@ var allEncoders = []knownEnc{
{"av1_amf", "amd", "av1", "AV1 AMF"},
}
// DetectGPUs detects GPUs and their encoder capabilities.
// Uses a single ffmpeg -encoders call for speed.
var cpuEncoders = []knownEnc{
{"libx264", "cpu", "h264", "H.264 (libx264)"},
{"libx265", "cpu", "hevc", "H.265/HEVC (libx265)"},
{"libsvtav1", "cpu", "av1", "AV1 (libsvtav1)"},
}
func DetectGPUs(exec *ffmpeg.Executor) []GPUInfo {
encList, err := exec.RunSync("-encoders")
if err != nil {
log.Printf("[gpu] ffmpeg -encoders failed: %v", err)
return fallbackGPUs()
}
// Group encoders by vendor
vendorEncs := map[string][]EncoderCap{}
for _, ke := range allEncoders {
available := containsWord(encList, ke.Name)
vendorEncs[ke.Vendor] = append(vendorEncs[ke.Vendor], EncoderCap{
Name: ke.Name, Codec: ke.Codec, Label: ke.Label, Available: available,
})
}
// Get GPU names
// Step 1: Get GPU names (DXGI/Registry/PS — no ffmpeg needed)
gpuNames := detectGPUNames()
log.Printf("[gpu] detected GPU names: %v", gpuNames)
log.Printf("[gpu] names: %v", gpuNames)
// Step 2: Detect encoder availability via ffmpeg (best effort)
var encList string
var ffmpegOK bool
if exec != nil {
out, err := exec.RunSync("-encoders")
if err == nil {
encList = out
ffmpegOK = true
} else {
log.Printf("[gpu] ffmpeg -encoders failed: %v (showing GPUs without encoder info)", err)
}
}
// Fall back to ffmpeg if no GPU names were found
if len(gpuNames) == 0 && ffmpegOK {
gpuNames = ffmpegFallback(exec)
}
if len(gpuNames) == 0 {
gpuNames = []gpuName{{Name: "Unknown GPU", Vendor: "unknown"}}
}
// Build result
var result []GPUInfo
for _, gn := range gpuNames {
encs := vendorEncs[gn.Vendor]
if encs == nil {
encs = []EncoderCap{}
}
result = append(result, GPUInfo{
Name: gn.Name,
Vendor: gn.Vendor,
Encoders: encs,
Encoders: capsForVendor(encList, gn.Vendor, ffmpegOK),
})
}
// Add CPU entry
// CPU
cpuName := detectCPUName()
log.Printf("[gpu] CPU: %s", cpuName)
result = append(result, GPUInfo{
Name: "CPU",
Vendor: "cpu",
Encoders: []EncoderCap{
{Name: "libx264", Codec: "h264", Label: "H.264 (libx264)", Available: containsWord(encList, "libx264")},
{Name: "libx265", Codec: "hevc", Label: "H.265/HEVC (libx265)", Available: containsWord(encList, "libx265")},
{Name: "libsvtav1", Codec: "av1", Label: "AV1 (libsvtav1)", Available: containsWord(encList, "libsvtav1")},
},
Name: cpuName,
Vendor: "cpu",
Encoders: capsForVendor(encList, "cpu", ffmpegOK),
})
log.Printf("[gpu] returning %d GPU entries", len(result))
return result
}
func containsWord(text, word string) bool {
// Simple substring match — the encoder list has one encoder per line
return len(text) > 0 && len(word) > 0 && containsLine(text, word)
func capsForVendor(encList, vendor string, ffmpegOK bool) []EncoderCap {
var src []knownEnc
switch vendor {
case "cpu":
src = cpuEncoders
default:
src = allEncoders
}
var caps []EncoderCap
for _, ke := range src {
if ke.Vendor == vendor || vendor == "cpu" {
avail := ffmpegOK && containsWord(encList, ke.Name)
if !ffmpegOK && vendor == "cpu" && ke.Name == "libx264" {
avail = true // always assume x264 is available
}
caps = append(caps, EncoderCap{
Name: ke.Name, Codec: ke.Codec, Label: ke.Label, Available: avail,
})
}
}
return caps
}
func containsLine(text, word string) bool {
for i := 0; i < len(text); i++ {
if i+len(word) > len(text) {
break
}
func ffmpegFallback(exec ffmpegExecutor) []gpuName {
out, err := exec.RunSync("-encoders")
if err != nil {
return nil
}
var gpus []gpuName
if wordIn(out, "h264_nvenc") || wordIn(out, "hevc_nvenc") {
gpus = append(gpus, gpuName{Name: "NVIDIA GPU", Vendor: "nvidia"})
}
if wordIn(out, "h264_qsv") || wordIn(out, "hevc_qsv") {
gpus = append(gpus, gpuName{Name: "Intel GPU", Vendor: "intel"})
}
if wordIn(out, "h264_amf") || wordIn(out, "hevc_amf") {
gpus = append(gpus, gpuName{Name: "AMD GPU", Vendor: "amd"})
}
return gpus
}
type ffmpegExecutor interface {
RunSync(args ...string) (string, error)
}
func containsWord(text, word string) bool {
if len(word) == 0 || len(text) < len(word) {
return false
}
for i := 0; i <= len(text)-len(word); i++ {
if text[i:i+len(word)] == word {
// Check word boundaries: should be preceded by space/newline and followed by space/newline
before := i == 0 || text[i-1] == ' ' || text[i-1] == '\n' || text[i-1] == '\r'
after := i+len(word) >= len(text) || text[i+len(word)] == ' ' || text[i+len(word)] == '\n' || text[i+len(word)] == '\r'
if before && after {
@@ -113,11 +147,6 @@ func containsLine(text, word string) bool {
return false
}
func fallbackGPUs() []GPUInfo {
return []GPUInfo{
{Name: "CPU", Vendor: "cpu", Encoders: []EncoderCap{
{Name: "libx264", Codec: "h264", Label: "H.264 (libx264)", Available: true},
{Name: "libx265", Codec: "hevc", Label: "H.265/HEVC (libx265)", Available: true},
}},
}
func wordIn(text, word string) bool {
return containsWord(text, word)
}