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