FIXES: - Fixed --tiny --nvhevc to use MP4 container instead of WEBM - Added conflict detection for --x264 and --nvhevc flags - Fixed stats system to accumulate data properly (total_files, total_time, average_time) - Improved error handling for missing flag values IMPROVEMENTS: - Implemented actual progress tracking using FFmpeg stderr parsing - Added real-time progress bar with ETA calculation - Progress shows percentage complete and estimated time remaining - Stats now properly accumulate over time instead of overwriting TECHNICAL: - Added bufio import for progress parsing - Enhanced container compatibility logic - Better error messages for conflicting flags - Improved stats JSON structure with proper data types
822 lines
31 KiB
Go
822 lines
31 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"gwutils"
|
|
)
|
|
|
|
func printHeader() {
|
|
fmt.Println("╔══════════════════════════════════════════════════════════════╗")
|
|
fmt.Println("║ GWEncoder v3.0 ║")
|
|
fmt.Println("║ Unified Video Encoding Tool ║")
|
|
fmt.Println("╚══════════════════════════════════════════════════════════════╝")
|
|
}
|
|
|
|
func printHelp() {
|
|
printHeader()
|
|
fmt.Println()
|
|
fmt.Println("❓ GWENCODER HELP & USAGE")
|
|
fmt.Println("══════════════════════════════════════════════════════════════")
|
|
fmt.Println("GWEncoder is a unified video encoding tool with multiple modes:")
|
|
fmt.Println()
|
|
fmt.Println("🚀 ENCODING MODES:")
|
|
fmt.Println(" --fast Fast AV1 encoding (MKV, CRF 32, Opus 64kbps, preset 10)")
|
|
fmt.Println(" --web Web-optimized encoding (WEBM/MP4, CRF 40, Opus/AAC 64kbps, preset 10)")
|
|
fmt.Println(" --quick Quick encoding (MKV, CRF 32, Opus 80kbps, preset 10)")
|
|
fmt.Println(" --tiny Tiny encoding (MP4, CRF 45, AAC 64kbps, preset 8)")
|
|
fmt.Println(" --full Full interactive mode with all codecs and options")
|
|
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("🔊 AUDIO OPTIONS:")
|
|
fmt.Println(" --aac Force AAC audio codec (overrides container default)")
|
|
fmt.Println(" --opus Force Opus audio codec (overrides container default)")
|
|
fmt.Println(" --abr 64 Set audio bitrate in kbps per channel (e.g., --abr 128)")
|
|
fmt.Println()
|
|
fmt.Println("📊 INFORMATION OPTIONS:")
|
|
fmt.Println(" --help Show this help information")
|
|
fmt.Println(" --info Show system information")
|
|
fmt.Println(" --stats Show encoding statistics")
|
|
fmt.Println()
|
|
fmt.Println("⚙️ FEATURES:")
|
|
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")
|
|
fmt.Println("• Subtitle extraction")
|
|
fmt.Println("• Configuration saving/loading")
|
|
fmt.Println("• Preset system for quick encoding")
|
|
fmt.Println()
|
|
fmt.Println("📁 SUPPORTED FORMATS:")
|
|
fmt.Println("• Input: WMV, AVI, MP4, MKV, MPG, TS, WEBM")
|
|
fmt.Println("• Output: MKV, MP4, WEBM")
|
|
fmt.Println()
|
|
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 --web --nvhevc # Web-optimized NVENC HEVC encoding")
|
|
fmt.Println(" ./gwencoder --web --aac --abr 128 # Web mode with AAC 128kbps")
|
|
fmt.Println(" ./gwencoder --quick # Quick AV1 encoding")
|
|
fmt.Println(" ./gwencoder --tiny # Tiny file AV1 encoding")
|
|
fmt.Println(" ./gwencoder --full # Full interactive mode")
|
|
fmt.Println(" ./gwencoder --help # Show this help")
|
|
fmt.Println(" ./gwencoder --info # Show system info")
|
|
fmt.Println("══════════════════════════════════════════════════════════════")
|
|
}
|
|
|
|
func printSystemInfo() {
|
|
printHeader()
|
|
fmt.Println()
|
|
fmt.Println("💻 SYSTEM INFORMATION")
|
|
fmt.Println("══════════════════════════════════════════════════════════════")
|
|
fmt.Printf("OS: %s\n", runtime.GOOS)
|
|
fmt.Printf("Architecture: %s\n", runtime.GOARCH)
|
|
fmt.Printf("CPU Cores: %d\n", runtime.NumCPU())
|
|
fmt.Printf("Physical Cores: %d\n", gwutils.GetPhysicalCores())
|
|
|
|
if gwutils.CheckFFmpeg() {
|
|
fmt.Println("✅ FFmpeg: Available")
|
|
} else {
|
|
fmt.Println("❌ FFmpeg: Not available")
|
|
}
|
|
|
|
if gwutils.CheckFFprobe() {
|
|
fmt.Println("✅ FFprobe: Available")
|
|
} else {
|
|
fmt.Println("❌ FFprobe: Not available")
|
|
}
|
|
fmt.Println("══════════════════════════════════════════════════════════════")
|
|
}
|
|
|
|
func printStats() {
|
|
printHeader()
|
|
fmt.Println()
|
|
fmt.Println("📊 ENCODING STATISTICS")
|
|
fmt.Println("══════════════════════════════════════════════════════════════")
|
|
|
|
// Try to read stats from file
|
|
if data, err := os.ReadFile("gwencoder_stats.json"); err == nil {
|
|
var stats map[string]interface{}
|
|
if json.Unmarshal(data, &stats) == nil {
|
|
fmt.Printf("Total files encoded: %v\n", stats["total_files"])
|
|
fmt.Printf("Last encoding: %v\n", stats["last_encoding"])
|
|
fmt.Printf("Average time: %v\n", stats["average_time"])
|
|
} else {
|
|
fmt.Println("No encoding history found.")
|
|
}
|
|
} else {
|
|
fmt.Println("No encoding history found.")
|
|
}
|
|
fmt.Println("══════════════════════════════════════════════════════════════")
|
|
}
|
|
|
|
func encodeFile(file string, mode gwutils.EncodingMode, useX264 bool, useNVHEVC bool, useAAC bool, useOpus bool, audioBitrate string) (time.Duration, int64, error) {
|
|
base := strings.TrimSuffix(filepath.Base(file), filepath.Ext(file))
|
|
codecName := "AV1"
|
|
if useX264 {
|
|
codecName = "H264"
|
|
} else if useNVHEVC {
|
|
codecName = "NVHEVC"
|
|
}
|
|
// Choose container/extension, overriding invalid combos
|
|
outExt := mode.Container
|
|
if useX264 && mode.Name == "Web-optimized" && strings.EqualFold(mode.Container, "webm") {
|
|
outExt = "mkv"
|
|
}
|
|
if useNVHEVC && strings.EqualFold(mode.Container, "webm") {
|
|
// NVENC HEVC doesn't work well with WEBM, use MP4 for web-optimized, MKV for others
|
|
if mode.Name == "Web-optimized" {
|
|
outExt = "mp4"
|
|
} else {
|
|
outExt = "mkv"
|
|
}
|
|
}
|
|
// If user forces AAC, prefer MP4 container to maintain compatibility
|
|
if useAAC && !strings.EqualFold(outExt, "mp4") {
|
|
outExt = "mp4"
|
|
}
|
|
out := fmt.Sprintf("%s-%s-%s-GWELL.%s", base, codecName, strings.Title(mode.Name), outExt)
|
|
|
|
start := time.Now()
|
|
|
|
// Get video duration for progress calculation
|
|
totalDuration := gwutils.GetVideoDuration(file)
|
|
|
|
// Get dynamic audio bitrate based on channel count
|
|
audioBitratePerChannel, _ := strconv.Atoi(mode.AudioBitrate)
|
|
if audioBitrate != "" {
|
|
// Use custom bitrate if provided
|
|
if customBitrate, err := strconv.Atoi(audioBitrate); err == nil {
|
|
audioBitratePerChannel = customBitrate
|
|
}
|
|
}
|
|
audioBitrateStr := gwutils.GetAudioBitrate(file, audioBitratePerChannel)
|
|
audioBitrateParts := strings.Fields(audioBitrateStr)
|
|
|
|
// Calculate physical cores
|
|
physicalCores := gwutils.GetPhysicalCores()
|
|
|
|
// Initialize NVENC quality variable
|
|
var nvencQuality string
|
|
|
|
// Build FFmpeg arguments
|
|
args := []string{"-y", "-i", file}
|
|
|
|
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"
|
|
x264Profile = "high"
|
|
} else if mode.Name == "Quick" {
|
|
x264Preset = "medium"
|
|
x264Tune = "film"
|
|
x264Profile = "high"
|
|
} else if mode.Name == "Tiny" {
|
|
x264Preset = "fast"
|
|
x264Tune = "film"
|
|
x264Profile = "main"
|
|
} else {
|
|
// Fast mode
|
|
x264Preset = "fast"
|
|
x264Tune = "film"
|
|
x264Profile = "high"
|
|
}
|
|
|
|
args = append(args, "-c:v", "libx264")
|
|
args = append(args, "-crf", mode.CRF)
|
|
args = append(args, "-preset", x264Preset)
|
|
args = append(args, "-tune", x264Tune)
|
|
args = append(args, "-profile:v", x264Profile)
|
|
args = append(args, "-level", "4.1")
|
|
args = append(args, "-maxrate", fmt.Sprintf("%sk", mode.Maxrate))
|
|
args = append(args, "-bufsize", fmt.Sprintf("%sk", mode.Maxrate))
|
|
args = append(args, "-threads", strconv.Itoa(physicalCores))
|
|
args = append(args, "-g", "240")
|
|
args = append(args, "-keyint_min", "24")
|
|
args = append(args, "-sc_threshold", "40")
|
|
args = append(args, "-bf", "2")
|
|
args = append(args, "-b_strategy", "1")
|
|
args = append(args, "-aq-mode", "1")
|
|
args = append(args, "-aq-strength", "1.0")
|
|
} else {
|
|
// AV1 parameters based on mode
|
|
var svtParams string
|
|
if mode.Name == "Web-optimized" {
|
|
svtParams = fmt.Sprintf("\"preset=%s:tune=0:scd=1:aq-mode=1:lp=%d:keyint=240:film-grain=0:input-depth=8:fast-decode=1:lookahead=90:enable-tf=0\"",
|
|
mode.Preset, physicalCores)
|
|
} else if mode.Name == "Quick" {
|
|
svtParams = fmt.Sprintf("\"preset=%s:tune=0:scd=1:aq-mode=1:lp=%d:keyint=240:film-grain=0:input-depth=8:fast-decode=1:lookahead=90:enable-tf=0\"",
|
|
mode.Preset, physicalCores)
|
|
} else if mode.Name == "Tiny" {
|
|
svtParams = fmt.Sprintf("\"preset=%s:tune=1:scd=1:aq-mode=2:lp=%d:keyint=240:film-grain=0:input-depth=8:fast-decode=1:lookahead=120:enable-tf=0\"",
|
|
mode.Preset, physicalCores)
|
|
} else {
|
|
// Fast mode
|
|
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")
|
|
args = append(args, "-maxrate", fmt.Sprintf("%sk", mode.Maxrate))
|
|
args = append(args, "-bufsize", fmt.Sprintf("%sk", mode.Maxrate))
|
|
args = append(args, "-svtav1-params", svtParams)
|
|
args = append(args, "-g", "240")
|
|
}
|
|
|
|
// Select audio codec based on user preference or container compatibility
|
|
// - User can force AAC or Opus with --aac or --opus flags
|
|
// - Default: WEBM requires Opus/Vorbis; MKV supports Opus; MP4 prefers AAC
|
|
var audioCodec string
|
|
if useAAC {
|
|
audioCodec = "aac"
|
|
} else if useOpus {
|
|
audioCodec = "libopus"
|
|
} else {
|
|
// Default based on container
|
|
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
|
|
args = append(args, out)
|
|
|
|
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)
|
|
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(" • %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))
|
|
}
|
|
|
|
cmd := exec.Command("ffmpeg", args...)
|
|
|
|
// Capture stderr for progress tracking and error messages
|
|
var stderr bytes.Buffer
|
|
cmd.Stderr = &stderr
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
fmt.Printf("❌ Failed to start encoding %s: %v\n", file, err)
|
|
return 0, 0, err
|
|
}
|
|
|
|
// Start progress tracking goroutine
|
|
progressDone := make(chan bool)
|
|
go func() {
|
|
scanner := bufio.NewScanner(&stderr)
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
// Parse FFmpeg progress output (format: "frame=1234 fps=25.0 q=23.0 size=1024kB time=00:01:23.45 bitrate=1000.0kbits/s")
|
|
if strings.Contains(line, "time=") && strings.Contains(line, "bitrate=") {
|
|
// Extract time from the line
|
|
timeStart := strings.Index(line, "time=")
|
|
if timeStart != -1 {
|
|
timeEnd := strings.Index(line[timeStart:], " ")
|
|
if timeEnd == -1 {
|
|
timeEnd = len(line)
|
|
} else {
|
|
timeEnd += timeStart
|
|
}
|
|
timeStr := line[timeStart+5:timeEnd]
|
|
|
|
// Parse time (format: HH:MM:SS.mmm)
|
|
timeParts := strings.Split(timeStr, ":")
|
|
if len(timeParts) == 3 {
|
|
hours, _ := strconv.Atoi(timeParts[0])
|
|
minutes, _ := strconv.Atoi(timeParts[1])
|
|
secondsParts := strings.Split(timeParts[2], ".")
|
|
seconds, _ := strconv.Atoi(secondsParts[0])
|
|
|
|
currentSeconds := float64(hours*3600 + minutes*60 + seconds)
|
|
if totalDuration > 0 {
|
|
progress := (currentSeconds / totalDuration) * 100
|
|
if progress > 100 {
|
|
progress = 100
|
|
}
|
|
|
|
// Calculate ETA
|
|
elapsed := time.Since(start)
|
|
if currentSeconds > 0 {
|
|
eta := time.Duration((totalDuration-currentSeconds)/currentSeconds) * elapsed
|
|
fmt.Printf("\r⏳ Progress: %.1f%% (ETA: %s)", progress, eta.Round(time.Second))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
progressDone <- true
|
|
}()
|
|
|
|
// Wait for completion
|
|
if err := cmd.Wait(); err != nil {
|
|
<-progressDone // Wait for progress goroutine to finish
|
|
fmt.Printf("\n❌ Failed to encode %s: %v\n", file, err)
|
|
fmt.Printf("🔧 FFmpeg stderr: %s\n", stderr.String())
|
|
gwutils.AppendToFile("gwencoder_errors.txt", fmt.Sprintf("Failed: %s - %v\nStderr: %s\n", file, err, stderr.String()))
|
|
return 0, 0, err
|
|
}
|
|
|
|
<-progressDone // Wait for progress goroutine to finish
|
|
fmt.Printf("\r✅ Progress: 100.0%% (Complete) \n")
|
|
|
|
duration := time.Since(start)
|
|
|
|
// Get output file size
|
|
var fileSize int64
|
|
if stat, err := os.Stat(out); err == nil {
|
|
fileSize = stat.Size()
|
|
}
|
|
|
|
fmt.Printf("✅ %s encoding complete: %s in %s\n", mode.Name, file, duration)
|
|
fmt.Printf("📊 Output file size: %.2f MB\n", float64(fileSize)/(1024*1024))
|
|
|
|
gwutils.AppendToFile("gwencoder_log.txt", fmt.Sprintf("Encoded %s in %d seconds using %s mode (%s)\n", file, int(duration.Seconds()), mode.Name, codecName))
|
|
updateStats(file, int(duration.Seconds()), mode.Name)
|
|
|
|
return duration, fileSize, nil
|
|
}
|
|
|
|
func updateStats(file string, duration int, mode string) {
|
|
// Load existing stats
|
|
stats := map[string]interface{}{
|
|
"total_files": 0,
|
|
"total_time": 0,
|
|
"last_encoding": fmt.Sprintf("%s (%s)", file, mode),
|
|
"last_update": time.Now().Format("2006-01-02 15:04:05"),
|
|
}
|
|
|
|
// Try to load existing stats
|
|
if data, err := os.ReadFile("gwencoder_stats.json"); err == nil {
|
|
var existingStats map[string]interface{}
|
|
if json.Unmarshal(data, &existingStats) == nil {
|
|
if totalFiles, ok := existingStats["total_files"].(float64); ok {
|
|
stats["total_files"] = int(totalFiles) + 1
|
|
} else {
|
|
stats["total_files"] = 1
|
|
}
|
|
if totalTime, ok := existingStats["total_time"].(float64); ok {
|
|
stats["total_time"] = int(totalTime) + duration
|
|
} else {
|
|
stats["total_time"] = duration
|
|
}
|
|
} else {
|
|
stats["total_files"] = 1
|
|
stats["total_time"] = duration
|
|
}
|
|
} else {
|
|
stats["total_files"] = 1
|
|
stats["total_time"] = duration
|
|
}
|
|
|
|
// Calculate average time
|
|
if totalFiles, ok := stats["total_files"].(int); ok && totalFiles > 0 {
|
|
if totalTime, ok := stats["total_time"].(int); ok {
|
|
avgTime := float64(totalTime) / float64(totalFiles)
|
|
stats["average_time"] = fmt.Sprintf("%.1f seconds", avgTime)
|
|
}
|
|
}
|
|
|
|
if data, err := json.MarshalIndent(stats, "", " "); err == nil {
|
|
os.WriteFile("gwencoder_stats.json", data, 0644)
|
|
}
|
|
}
|
|
|
|
func runFullMode() {
|
|
fmt.Println("🎬 FULL INTERACTIVE MODE")
|
|
fmt.Println("══════════════════════════════════════════════════════════════")
|
|
fmt.Println("This mode provides full access to all encoding options.")
|
|
fmt.Println("For now, this is a placeholder for the full GWEncoder.go functionality.")
|
|
fmt.Println("Use specific modes (--fast, --web, --quick, --tiny) for immediate encoding.")
|
|
fmt.Println()
|
|
|
|
// This would integrate the full GWEncoder.go functionality
|
|
// For now, just show available options
|
|
fmt.Println("Available quick modes:")
|
|
fmt.Println(" --fast Fast AV1 encoding")
|
|
fmt.Println(" --web Web-optimized encoding")
|
|
fmt.Println(" --quick Quick encoding")
|
|
fmt.Println(" --tiny Tiny file encoding")
|
|
}
|
|
|
|
func main() {
|
|
if len(os.Args) < 2 {
|
|
printHelp()
|
|
return
|
|
}
|
|
|
|
// Check for codec and audio flags
|
|
useX264 := false
|
|
useNVHEVC := false
|
|
useAAC := false
|
|
useOpus := false
|
|
audioBitrate := ""
|
|
args := os.Args[1:]
|
|
|
|
// Filter out codec and audio flags and set flags
|
|
var filteredArgs []string
|
|
for i, arg := range args {
|
|
if arg == "--x264" {
|
|
useX264 = true
|
|
} else if arg == "--nvhevc" {
|
|
useNVHEVC = true
|
|
} else if arg == "--aac" {
|
|
useAAC = true
|
|
} else if arg == "--opus" {
|
|
useOpus = true
|
|
} else if arg == "--abr" && i+1 < len(args) {
|
|
audioBitrate = args[i+1]
|
|
// Skip the next argument since we consumed it
|
|
continue
|
|
} else {
|
|
filteredArgs = append(filteredArgs, arg)
|
|
}
|
|
}
|
|
|
|
// Check for conflicting codec flags
|
|
if useX264 && useNVHEVC {
|
|
fmt.Println("❌ Error: Cannot use both --x264 and --nvhevc flags together")
|
|
fmt.Println("Please choose one codec option:")
|
|
fmt.Println(" --x264 Use H.264/x264 codec")
|
|
fmt.Println(" --nvhevc Use NVIDIA NVENC HEVC codec")
|
|
fmt.Println(" (omit both for default AV1 encoding)")
|
|
return
|
|
}
|
|
|
|
if len(filteredArgs) == 0 {
|
|
printHelp()
|
|
return
|
|
}
|
|
|
|
arg := filteredArgs[0]
|
|
modes := gwutils.GetDefaultModes()
|
|
|
|
switch arg {
|
|
case "--help", "-h":
|
|
printHelp()
|
|
case "--info":
|
|
printSystemInfo()
|
|
case "--stats":
|
|
printStats()
|
|
case "--fast":
|
|
printHeader()
|
|
fmt.Println()
|
|
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:")
|
|
fmt.Println("• H.264/x264 codec with fast preset")
|
|
fmt.Println("• Opus audio at 64kbps per channel")
|
|
fmt.Println("• MKV container for better compatibility")
|
|
fmt.Println("• CRF 32 for fast encoding")
|
|
} else {
|
|
fmt.Println("⚡ FAST ENCODING MODE")
|
|
fmt.Println("══════════════════════════════════════════════════════════════")
|
|
fmt.Println("Using fast encoding settings:")
|
|
fmt.Println("• AV1 codec with speed preset (10)")
|
|
fmt.Println("• Opus audio at 64kbps per channel")
|
|
fmt.Println("• MKV container for better compatibility")
|
|
fmt.Println("• CRF 32 for fast encoding")
|
|
}
|
|
fmt.Println()
|
|
|
|
mode := modes["fast"]
|
|
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())
|
|
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)
|
|
fmt.Printf(" • %s container\n", strings.ToUpper(mode.Container))
|
|
fmt.Println("📏 Using original resolution")
|
|
fmt.Println()
|
|
|
|
excludePatterns := []string{"GWELL", "AV1-", "H264-", "NVHEVC-"}
|
|
files := gwutils.FindMediaFiles(excludePatterns)
|
|
if len(files) == 0 {
|
|
fmt.Println("❌ No video files found in current directory.")
|
|
return
|
|
}
|
|
|
|
fmt.Printf("🚀 Starting %s encoding of %d files...\n", mode.Name, len(files))
|
|
fmt.Println()
|
|
|
|
for _, file := range files {
|
|
encodeFile(file, mode, useX264, useNVHEVC, useAAC, useOpus, audioBitrate)
|
|
fmt.Println()
|
|
}
|
|
|
|
case "--web":
|
|
printHeader()
|
|
fmt.Println()
|
|
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("• AAC audio at 64kbps per channel")
|
|
fmt.Println("• MP4 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:")
|
|
fmt.Println("• H.264/x264 codec with medium preset")
|
|
fmt.Println("• Opus audio at 64kbps per channel")
|
|
fmt.Println("• MKV container for web compatibility")
|
|
fmt.Println("• CRF 40 for web-optimized quality")
|
|
} else {
|
|
fmt.Println("🌐 WEB-OPTIMIZED ENCODING MODE")
|
|
fmt.Println("══════════════════════════════════════════════════════════════")
|
|
fmt.Println("Using web-optimized settings:")
|
|
fmt.Println("• AV1 codec with balanced preset (10)")
|
|
fmt.Println("• Opus audio at 64kbps per channel")
|
|
fmt.Println("• WEBM container for web compatibility")
|
|
fmt.Println("• CRF 40 for web-optimized quality")
|
|
}
|
|
fmt.Println()
|
|
|
|
mode := modes["web"]
|
|
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())
|
|
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)
|
|
}
|
|
if useNVHEVC {
|
|
fmt.Printf(" • AAC audio at %skbps per channel\n", mode.AudioBitrate)
|
|
fmt.Printf(" • MP4 container for web compatibility\n")
|
|
} else {
|
|
// H.264 in web mode uses MKV (audio Opus); AV1 uses WEBM (audio Opus)
|
|
fmt.Printf(" • Opus audio at %skbps per channel\n", mode.AudioBitrate)
|
|
containerForWeb := mode.Container
|
|
if useX264 && strings.EqualFold(mode.Container, "webm") {
|
|
containerForWeb = "mkv"
|
|
}
|
|
fmt.Printf(" • %s container for web compatibility\n", strings.ToUpper(containerForWeb))
|
|
}
|
|
fmt.Println("📏 Using original resolution")
|
|
fmt.Println()
|
|
|
|
excludePatterns := []string{"GWELL", "AV1-", "H264-", "NVHEVC-"}
|
|
files := gwutils.FindMediaFiles(excludePatterns)
|
|
if len(files) == 0 {
|
|
fmt.Println("❌ No video files found in current directory.")
|
|
return
|
|
}
|
|
|
|
fmt.Printf("🚀 Starting %s encoding of %d files...\n", mode.Name, len(files))
|
|
fmt.Println()
|
|
|
|
for _, file := range files {
|
|
encodeFile(file, mode, useX264, useNVHEVC, useAAC, useOpus, audioBitrate)
|
|
fmt.Println()
|
|
}
|
|
|
|
case "--quick":
|
|
printHeader()
|
|
fmt.Println()
|
|
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:")
|
|
fmt.Println("• H.264/x264 codec with medium preset")
|
|
fmt.Println("• Opus audio at 80kbps per channel")
|
|
fmt.Println("• MKV container for better compatibility")
|
|
fmt.Println("• CRF 32 for quick encoding")
|
|
} else {
|
|
fmt.Println("⚡ QUICK ENCODING MODE")
|
|
fmt.Println("══════════════════════════════════════════════════════════════")
|
|
fmt.Println("Using quick encoding settings:")
|
|
fmt.Println("• AV1 codec with balanced preset (10)")
|
|
fmt.Println("• Opus audio at 80kbps per channel")
|
|
fmt.Println("• MKV container for better compatibility")
|
|
fmt.Println("• CRF 32 for quick encoding")
|
|
}
|
|
fmt.Println()
|
|
|
|
mode := modes["quick"]
|
|
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())
|
|
fmt.Printf(" • CRF=%s (quick encoding quality)\n", mode.CRF)
|
|
if useX264 {
|
|
fmt.Printf(" • H.264/x264 codec\n")
|
|
} else {
|
|
fmt.Printf(" • Preset=%s (balanced encoding)\n", mode.Preset)
|
|
}
|
|
fmt.Printf(" • Opus audio at %skbps per channel\n", mode.AudioBitrate)
|
|
fmt.Printf(" • %s container\n", strings.ToUpper(mode.Container))
|
|
fmt.Println("📏 Using original resolution")
|
|
fmt.Println()
|
|
|
|
excludePatterns := []string{"GWELL", "AV1-", "H264-", "NVHEVC-"}
|
|
files := gwutils.FindMediaFiles(excludePatterns)
|
|
if len(files) == 0 {
|
|
fmt.Println("❌ No video files found in current directory.")
|
|
return
|
|
}
|
|
|
|
fmt.Printf("🚀 Starting %s encoding of %d files...\n", mode.Name, len(files))
|
|
fmt.Println()
|
|
|
|
for _, file := range files {
|
|
encodeFile(file, mode, useX264, useNVHEVC, useAAC, useOpus, audioBitrate)
|
|
fmt.Println()
|
|
}
|
|
|
|
case "--tiny":
|
|
printHeader()
|
|
fmt.Println()
|
|
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("• AAC 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:")
|
|
fmt.Println("• H.264/x264 codec with fast preset")
|
|
fmt.Println("• AAC audio at 64kbps per channel")
|
|
fmt.Println("• MP4 container for maximum compatibility")
|
|
fmt.Println("• CRF 45 for smallest file size")
|
|
} else {
|
|
fmt.Println("📦 TINY ENCODING MODE")
|
|
fmt.Println("══════════════════════════════════════════════════════════════")
|
|
fmt.Println("Using tiny encoding settings:")
|
|
fmt.Println("• AV1 codec with quality preset (8)")
|
|
fmt.Println("• AAC audio at 64kbps per channel")
|
|
fmt.Println("• MP4 container for maximum compatibility")
|
|
fmt.Println("• CRF 45 for smallest file size")
|
|
}
|
|
fmt.Println()
|
|
|
|
mode := modes["tiny"]
|
|
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())
|
|
fmt.Printf(" • CRF=%s (tiny file quality)\n", mode.CRF)
|
|
if useX264 {
|
|
fmt.Printf(" • H.264/x264 codec\n")
|
|
} else {
|
|
fmt.Printf(" • Preset=%s (quality encoding)\n", mode.Preset)
|
|
}
|
|
if useNVHEVC || useX264 || strings.EqualFold(mode.Container, "mp4") {
|
|
fmt.Printf(" • AAC audio at %skbps per channel\n", mode.AudioBitrate)
|
|
} else {
|
|
fmt.Printf(" • Opus audio at %skbps per channel\n", mode.AudioBitrate)
|
|
}
|
|
fmt.Printf(" • %s container for maximum compatibility\n", strings.ToUpper(mode.Container))
|
|
fmt.Println("📏 Using original resolution")
|
|
fmt.Println()
|
|
|
|
excludePatterns := []string{"GWELL", "AV1-", "H264-", "NVHEVC-"}
|
|
files := gwutils.FindMediaFiles(excludePatterns)
|
|
if len(files) == 0 {
|
|
fmt.Println("❌ No video files found in current directory.")
|
|
return
|
|
}
|
|
|
|
fmt.Printf("🚀 Starting %s encoding of %d files...\n", mode.Name, len(files))
|
|
fmt.Println()
|
|
|
|
for _, file := range files {
|
|
encodeFile(file, mode, useX264, useNVHEVC, useAAC, useOpus, audioBitrate)
|
|
fmt.Println()
|
|
}
|
|
|
|
case "--full":
|
|
printHeader()
|
|
runFullMode()
|
|
|
|
default:
|
|
fmt.Println("❌ Unknown option:", arg)
|
|
fmt.Println("Use --help for usage information")
|
|
}
|
|
}
|