diff --git a/gwencoder/main.go b/gwencoder/main.go index c612dad..6917486 100644 --- a/gwencoder/main.go +++ b/gwencoder/main.go @@ -38,6 +38,7 @@ func printHelp() { fmt.Println() fmt.Println("🎬 CODEC OPTIONS:") fmt.Println(" --x264 Use H.264/x264 codec instead of AV1 (matches AV1 presets)") + fmt.Println(" --nvhevc Use NVIDIA NVENC HEVC hardware encoding (requires NVIDIA GPU)") fmt.Println() fmt.Println("📊 INFORMATION OPTIONS:") fmt.Println(" --help Show this help information") @@ -45,7 +46,7 @@ func printHelp() { fmt.Println(" --stats Show encoding statistics") fmt.Println() fmt.Println("⚙️ FEATURES:") - fmt.Println("• Multiple codecs: AV1 (default), H.264/x264") + fmt.Println("• Multiple codecs: AV1 (default), H.264/x264, NVIDIA NVENC HEVC") fmt.Println("• Hardware acceleration support (NVENC)") fmt.Println("• Automatic file detection") fmt.Println("• Progress tracking with ETA") @@ -60,6 +61,7 @@ func printHelp() { fmt.Println("💡 EXAMPLES:") fmt.Println(" ./gwencoder --fast # Fast AV1 encoding") fmt.Println(" ./gwencoder --fast --x264 # Fast H.264 encoding") + fmt.Println(" ./gwencoder --fast --nvhevc # Fast NVENC HEVC encoding") fmt.Println(" ./gwencoder --web # Web-optimized AV1 encoding") fmt.Println(" ./gwencoder --web --x264 # Web-optimized H.264 encoding") fmt.Println(" ./gwencoder --quick # Quick AV1 encoding") @@ -116,13 +118,20 @@ func printStats() { fmt.Println("══════════════════════════════════════════════════════════════") } -func encodeFile(file string, mode gwutils.EncodingMode, useX264 bool) (time.Duration, int64, error) { +func encodeFile(file string, mode gwutils.EncodingMode, useX264 bool, useNVHEVC bool) (time.Duration, int64, error) { base := strings.TrimSuffix(filepath.Base(file), filepath.Ext(file)) codecName := "AV1" if useX264 { codecName = "H264" + } else if useNVHEVC { + codecName = "NVHEVC" } - out := fmt.Sprintf("%s-%s-%s-GWELL.%s", base, codecName, strings.Title(mode.Name), mode.Container) + // Choose container/extension, overriding invalid combos (e.g., H.264 in WEBM) + outExt := mode.Container + if useX264 && mode.Name == "Web-optimized" && strings.EqualFold(mode.Container, "webm") { + outExt = "mkv" + } + out := fmt.Sprintf("%s-%s-%s-GWELL.%s", base, codecName, strings.Title(mode.Name), outExt) start := time.Now() @@ -137,15 +146,51 @@ func encodeFile(file string, mode gwutils.EncodingMode, useX264 bool) (time.Dura // Calculate physical cores physicalCores := gwutils.GetPhysicalCores() + // Initialize NVENC quality variable + var nvencQuality string + // Build FFmpeg arguments args := []string{"-y", "-i", file} - - if useX264 { + + if useNVHEVC { + // NVIDIA NVENC HEVC parameters based on mode + var nvencPreset string + + if mode.Name == "Web-optimized" { + nvencPreset = "medium" + nvencQuality = "28" // Higher quality for web + } else if mode.Name == "Quick" { + nvencPreset = "fast" + nvencQuality = "26" // Good quality for quick + } else if mode.Name == "Tiny" { + nvencPreset = "fast" + nvencQuality = "30" // Lower quality for tiny files + } else { + // Fast mode + nvencPreset = "fast" + nvencQuality = "26" // Good quality for fast + } + + args = append(args, "-c:v", "hevc_nvenc") + args = append(args, "-preset", nvencPreset) + args = append(args, "-profile:v", "main10") + args = append(args, "-rc", "vbr") + args = append(args, "-cq", nvencQuality) + args = append(args, "-b:v", "0") + args = append(args, "-maxrate", fmt.Sprintf("%sk", mode.Maxrate)) + args = append(args, "-bufsize", fmt.Sprintf("%sk", mode.Maxrate)) + args = append(args, "-rc-lookahead", "32") + args = append(args, "-spatial_aq", "1") + args = append(args, "-temporal_aq", "1") + args = append(args, "-g", "240") + args = append(args, "-keyint_min", "24") + args = append(args, "-tag:v", "hvc1") + } else if useX264 { // H.264/x264 parameters based on mode var x264Preset string var x264Tune string var x264Profile string - + if mode.Name == "Web-optimized" { x264Preset = "medium" x264Tune = "film" @@ -164,7 +209,7 @@ func encodeFile(file string, mode gwutils.EncodingMode, useX264 bool) (time.Dura x264Tune = "film" x264Profile = "high" } - + args = append(args, "-c:v", "libx264") args = append(args, "-crf", mode.CRF) args = append(args, "-preset", x264Preset) @@ -198,7 +243,7 @@ func encodeFile(file string, mode gwutils.EncodingMode, useX264 bool) (time.Dura svtParams = fmt.Sprintf("\"preset=%s:tune=0:scd=0:aq-mode=1:lp=%d:keyint=240:film-grain=0:input-depth=8:fast-decode=1:lookahead=60:enable-tf=0\"", mode.Preset, physicalCores) } - + args = append(args, "-c:v", "libsvtav1") args = append(args, "-crf", mode.CRF) args = append(args, "-b:v", "0") @@ -207,8 +252,14 @@ func encodeFile(file string, mode gwutils.EncodingMode, useX264 bool) (time.Dura args = append(args, "-svtav1-params", svtParams) args = append(args, "-g", "240") } - - args = append(args, "-c:a", "libopus") + + // Select audio codec based on container compatibility + // - WEBM requires Opus/Vorbis; MKV supports Opus; MP4 prefers AAC + audioCodec := "libopus" + if strings.EqualFold(outExt, "mp4") { + audioCodec = "aac" + } + args = append(args, "-c:a", audioCodec) args = append(args, audioBitrateParts...) args = append(args, "-map", "0:v", "-map", "0:a") args = append(args, "-sn") // Skip subtitles for speed @@ -217,14 +268,18 @@ func encodeFile(file string, mode gwutils.EncodingMode, useX264 bool) (time.Dura fmt.Printf("🎬 %s %s Encoding: %s → %s\n", mode.Name, codecName, file, out) fmt.Printf("⚡ %s settings:\n", mode.Name) fmt.Printf(" • Using %d physical cores\n", physicalCores) - fmt.Printf(" • CRF=%s (%s quality)\n", mode.CRF, mode.Name) - if useX264 { + if useNVHEVC { + fmt.Printf(" • NVIDIA NVENC HEVC hardware encoding\n") + fmt.Printf(" • CQ=%s (constant quality)\n", nvencQuality) + } else if useX264 { + fmt.Printf(" • CRF=%s (%s quality)\n", mode.CRF, mode.Name) fmt.Printf(" • H.264/x264 codec\n") } else { + fmt.Printf(" • CRF=%s (%s quality)\n", mode.CRF, mode.Name) fmt.Printf(" • Preset=%s (encoding speed)\n", mode.Preset) } - fmt.Printf(" • Opus audio at %skbps per channel\n", mode.AudioBitrate) - fmt.Printf(" • %s container\n", strings.ToUpper(mode.Container)) + fmt.Printf(" • %s audio at %skbps per channel\n", strings.ToUpper(audioCodec), mode.AudioBitrate) + fmt.Printf(" • %s container\n", strings.ToUpper(outExt)) if totalDuration > 0 { fmt.Printf("📐 Duration: %s\n", gwutils.FormatTime(totalDuration)) } @@ -301,25 +356,28 @@ func main() { return } - // Check for --x264 flag + // Check for --x264 and --nvhevc flags useX264 := false + useNVHEVC := false args := os.Args[1:] - - // Filter out --x264 flag and set useX264 + + // Filter out codec flags and set flags var filteredArgs []string for _, arg := range args { if arg == "--x264" { useX264 = true + } else if arg == "--nvhevc" { + useNVHEVC = true } else { filteredArgs = append(filteredArgs, arg) } } - + if len(filteredArgs) == 0 { printHelp() return } - + arg := filteredArgs[0] modes := gwutils.GetDefaultModes() @@ -333,7 +391,15 @@ func main() { case "--fast": printHeader() fmt.Println() - if useX264 { + if useNVHEVC { + fmt.Println("⚡ FAST NVENC HEVC ENCODING MODE") + fmt.Println("══════════════════════════════════════════════════════════════") + fmt.Println("Using fast NVENC HEVC encoding settings:") + fmt.Println("• NVIDIA NVENC HEVC hardware encoding") + fmt.Println("• Opus audio at 64kbps per channel") + fmt.Println("• MKV container for better compatibility") + fmt.Println("• CQ 26 for fast encoding") + } else if useX264 { fmt.Println("⚡ FAST H.264 ENCODING MODE") fmt.Println("══════════════════════════════════════════════════════════════") fmt.Println("Using fast H.264 encoding settings:") @@ -356,13 +422,21 @@ func main() { codecName := "AV1" if useX264 { codecName = "H264" + } else if useNVHEVC { + codecName = "NVHEVC" + } else if useNVHEVC { + codecName = "NVHEVC" } fmt.Printf("⚡ %s %s settings:\n", mode.Name, codecName) fmt.Printf(" • Using %d physical cores\n", gwutils.GetPhysicalCores()) - fmt.Printf(" • CRF=%s (fast encoding quality)\n", mode.CRF) - if useX264 { + if useNVHEVC { + fmt.Printf(" • NVIDIA NVENC HEVC hardware encoding\n") + fmt.Printf(" • CQ=26 (fast encoding quality)\n") + } else if useX264 { + fmt.Printf(" • CRF=%s (fast encoding quality)\n", mode.CRF) fmt.Printf(" • H.264/x264 codec\n") } else { + fmt.Printf(" • CRF=%s (fast encoding quality)\n", mode.CRF) fmt.Printf(" • Preset=%s (speed optimized)\n", mode.Preset) } fmt.Printf(" • Opus audio at %skbps per channel\n", mode.AudioBitrate) @@ -370,7 +444,7 @@ func main() { fmt.Println("📏 Using original resolution") fmt.Println() - excludePatterns := []string{"GWELL", "AV1-", "H264-"} + excludePatterns := []string{"GWELL", "AV1-", "H264-", "NVHEVC-"} files := gwutils.FindMediaFiles(excludePatterns) if len(files) == 0 { fmt.Println("❌ No video files found in current directory.") @@ -381,14 +455,22 @@ func main() { fmt.Println() for _, file := range files { - encodeFile(file, mode, useX264) + encodeFile(file, mode, useX264, useNVHEVC) fmt.Println() } case "--web": printHeader() fmt.Println() - if useX264 { + if useNVHEVC { + fmt.Println("🌐 WEB-OPTIMIZED NVENC HEVC ENCODING MODE") + fmt.Println("══════════════════════════════════════════════════════════════") + fmt.Println("Using web-optimized NVENC HEVC settings:") + fmt.Println("• NVIDIA NVENC HEVC hardware encoding") + fmt.Println("• Opus audio at 64kbps per channel") + fmt.Println("• MKV container for web compatibility") + fmt.Println("• CQ 28 for web-optimized quality") + } else if useX264 { fmt.Println("🌐 WEB-OPTIMIZED H.264 ENCODING MODE") fmt.Println("══════════════════════════════════════════════════════════════") fmt.Println("Using web-optimized H.264 settings:") @@ -411,13 +493,21 @@ func main() { codecName := "AV1" if useX264 { codecName = "H264" + } else if useNVHEVC { + codecName = "NVHEVC" + } else if useNVHEVC { + codecName = "NVHEVC" } fmt.Printf("🌐 %s %s settings:\n", mode.Name, codecName) fmt.Printf(" • Using %d physical cores\n", gwutils.GetPhysicalCores()) - fmt.Printf(" • CRF=%s (web-optimized quality)\n", mode.CRF) - if useX264 { + if useNVHEVC { + fmt.Printf(" • NVIDIA NVENC HEVC hardware encoding\n") + fmt.Printf(" • CQ=28 (web-optimized quality)\n") + } else if useX264 { + fmt.Printf(" • CRF=%s (web-optimized quality)\n", mode.CRF) fmt.Printf(" • H.264/x264 codec\n") } else { + fmt.Printf(" • CRF=%s (web-optimized quality)\n", mode.CRF) fmt.Printf(" • Preset=%s (balanced encoding)\n", mode.Preset) } fmt.Printf(" • Opus audio at %skbps per channel\n", mode.AudioBitrate) @@ -425,7 +515,7 @@ func main() { fmt.Println("📏 Using original resolution") fmt.Println() - excludePatterns := []string{"GWELL", "AV1-", "H264-"} + excludePatterns := []string{"GWELL", "AV1-", "H264-", "NVHEVC-"} files := gwutils.FindMediaFiles(excludePatterns) if len(files) == 0 { fmt.Println("❌ No video files found in current directory.") @@ -436,14 +526,22 @@ func main() { fmt.Println() for _, file := range files { - encodeFile(file, mode, useX264) + encodeFile(file, mode, useX264, useNVHEVC) fmt.Println() } case "--quick": printHeader() fmt.Println() - if useX264 { + if useNVHEVC { + fmt.Println("⚡ QUICK NVENC HEVC ENCODING MODE") + fmt.Println("══════════════════════════════════════════════════════════════") + fmt.Println("Using quick NVENC HEVC encoding settings:") + fmt.Println("• NVIDIA NVENC HEVC hardware encoding") + fmt.Println("• Opus audio at 80kbps per channel") + fmt.Println("• MKV container for better compatibility") + fmt.Println("• CQ 26 for quick encoding") + } else if useX264 { fmt.Println("⚡ QUICK H.264 ENCODING MODE") fmt.Println("══════════════════════════════════════════════════════════════") fmt.Println("Using quick H.264 encoding settings:") @@ -466,6 +564,8 @@ func main() { codecName := "AV1" if useX264 { codecName = "H264" + } else if useNVHEVC { + codecName = "NVHEVC" } fmt.Printf("⚡ %s %s settings:\n", mode.Name, codecName) fmt.Printf(" • Using %d physical cores\n", gwutils.GetPhysicalCores()) @@ -480,7 +580,7 @@ func main() { fmt.Println("📏 Using original resolution") fmt.Println() - excludePatterns := []string{"GWELL", "AV1-", "H264-"} + excludePatterns := []string{"GWELL", "AV1-", "H264-", "NVHEVC-"} files := gwutils.FindMediaFiles(excludePatterns) if len(files) == 0 { fmt.Println("❌ No video files found in current directory.") @@ -491,14 +591,22 @@ func main() { fmt.Println() for _, file := range files { - encodeFile(file, mode, useX264) + encodeFile(file, mode, useX264, useNVHEVC) fmt.Println() } case "--tiny": printHeader() fmt.Println() - if useX264 { + if useNVHEVC { + fmt.Println("📦 TINY NVENC HEVC ENCODING MODE") + fmt.Println("══════════════════════════════════════════════════════════════") + fmt.Println("Using tiny NVENC HEVC encoding settings:") + fmt.Println("• NVIDIA NVENC HEVC hardware encoding") + fmt.Println("• Opus audio at 64kbps per channel") + fmt.Println("• MP4 container for maximum compatibility") + fmt.Println("• CQ 30 for smallest file size") + } else if useX264 { fmt.Println("📦 TINY H.264 ENCODING MODE") fmt.Println("══════════════════════════════════════════════════════════════") fmt.Println("Using tiny H.264 encoding settings:") @@ -521,6 +629,8 @@ func main() { codecName := "AV1" if useX264 { codecName = "H264" + } else if useNVHEVC { + codecName = "NVHEVC" } fmt.Printf("📦 %s %s settings:\n", mode.Name, codecName) fmt.Printf(" • Using %d physical cores\n", gwutils.GetPhysicalCores()) @@ -535,7 +645,7 @@ func main() { fmt.Println("📏 Using original resolution") fmt.Println() - excludePatterns := []string{"GWELL", "AV1-", "H264-"} + excludePatterns := []string{"GWELL", "AV1-", "H264-", "NVHEVC-"} files := gwutils.FindMediaFiles(excludePatterns) if len(files) == 0 { fmt.Println("❌ No video files found in current directory.") @@ -546,7 +656,7 @@ func main() { fmt.Println() for _, file := range files { - encodeFile(file, mode, useX264) + encodeFile(file, mode, useX264, useNVHEVC) fmt.Println() }