124 lines
3.5 KiB
Go
124 lines
3.5 KiB
Go
package gpu
|
|
|
|
import (
|
|
"ffmpeg-gui/internal/ffmpeg"
|
|
"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"`
|
|
Label string `json:"label"`
|
|
Available bool `json:"available"`
|
|
}
|
|
|
|
// knownEncoders defines all known hardware encoders by vendor.
|
|
type knownEnc struct {
|
|
Name, Vendor, Codec, Label string
|
|
}
|
|
|
|
var allEncoders = []knownEnc{
|
|
{"h264_nvenc", "nvidia", "h264", "H.264 NVENC"},
|
|
{"hevc_nvenc", "nvidia", "hevc", "HEVC NVENC"},
|
|
{"av1_nvenc", "nvidia", "av1", "AV1 NVENC"},
|
|
{"h264_qsv", "intel", "h264", "H.264 QSV"},
|
|
{"hevc_qsv", "intel", "hevc", "HEVC QSV"},
|
|
{"av1_qsv", "intel", "av1", "AV1 QSV"},
|
|
{"h264_amf", "amd", "h264", "H.264 AMF"},
|
|
{"hevc_amf", "amd", "hevc", "HEVC AMF"},
|
|
{"av1_amf", "amd", "av1", "AV1 AMF"},
|
|
}
|
|
|
|
// DetectGPUs detects GPUs and their encoder capabilities.
|
|
// Uses a single ffmpeg -encoders call for speed.
|
|
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
|
|
gpuNames := detectGPUNames()
|
|
log.Printf("[gpu] detected GPU names: %v", gpuNames)
|
|
|
|
if len(gpuNames) == 0 {
|
|
gpuNames = []gpuName{{Name: "Unknown GPU", Vendor: "unknown"}}
|
|
}
|
|
|
|
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,
|
|
})
|
|
}
|
|
|
|
// Add CPU entry
|
|
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")},
|
|
},
|
|
})
|
|
|
|
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 containsLine(text, word string) bool {
|
|
for i := 0; i < len(text); i++ {
|
|
if i+len(word) > len(text) {
|
|
break
|
|
}
|
|
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 {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
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},
|
|
}},
|
|
}
|
|
}
|