diff --git a/common.go b/common.go index a393c95..078a5e6 100644 --- a/common.go +++ b/common.go @@ -30,19 +30,44 @@ type VideoSpecs struct { } } -func checkCodecs() (map[string]string, error) { - // Check for available codecs - codecs, err := exec.Command("ffmpeg", "-codecs").CombinedOutput() - codecsString := string(codecs) +// Check for available codecs and hardware accelerators +func checkFfmpeg() (map[string]string, error) { + ret := make(map[string]string) + + version, err := exec.Command("ffmpeg", "-version").CombinedOutput() if err != nil { return nil, errors.New("Cannot find ffmpeg/ffprobe on your system.\nMake sure to install it first: https://github.com/Niek/superview/#requirements") } - ret := make(map[string]string) - ret["version"] = strings.Split(codecsString[strings.Index(codecsString, "ffmpeg version ")+15:], " ")[0] - ret["h264"] = strconv.FormatBool(strings.Contains(codecsString, "H.264")) - ret["h265"] = strconv.FormatBool(strings.Contains(codecsString, "H.265")) + ret["version"] = strings.Split(string(version), " ")[2] + + // split on newline, skip first line + accels, err := exec.Command("ffmpeg", "-hwaccels", "-hide_banner").CombinedOutput() + accelsArr := strings.Split(string(accels), "\n") + for i := 1; i < len(accelsArr); i++ { + if len(accelsArr[i]) != 0 { + ret["accels"] += accelsArr[i] + "," + } + } + + // split on newline, skip first 10 lines + encoders, err := exec.Command("ffmpeg", "-encoders", "-hide_banner").CombinedOutput() + encodersArr := strings.Split(string(encoders), "\n") + for i := 10; i < len(encodersArr); i++ { + if strings.Index(encodersArr[i], " V") == 0 { + enc := strings.Split(encodersArr[i], " ") + if strings.Index(enc[2], "h264") == 0 || strings.Index(enc[2], "libx264") == 0 { + ret["h264"] += enc[2] + "," + } else if strings.Index(enc[2], "h264") == 0 || strings.Index(enc[2], "libx265") == 0 { + ret["h265"] += enc[2] + "," + } + } + } + + ret["accels"] = strings.Trim(ret["accels"], ",") + ret["h264"] = strings.Trim(ret["h264"], ",") + ret["h265"] = strings.Trim(ret["h265"], ",") return ret, nil } @@ -144,9 +169,23 @@ func generatePGM(video *VideoSpecs, squeeze bool) error { return nil } -func encodeVideo(video *VideoSpecs, bitrate int, output string, callback func(float64)) error { +func findEncoder(codec string, ffmpeg map[string]string) string { + codec = strings.ToLower(codec) + encoder := strings.Split(ffmpeg[codec], ",")[0] + for _, acc := range strings.Split(ffmpeg["accels"], ",") { + for _, enc := range strings.Split(ffmpeg[codec], ",") { + if strings.Index(enc, acc) != -1 { + encoder = enc + } + } + } + + return encoder +} + +func encodeVideo(video *VideoSpecs, encoder string, bitrate int, output string, callback func(float64)) error { // Starting encoder, write progress to stdout pipe - cmd := exec.Command("ffmpeg", "-hide_banner", "-progress", "pipe:1", "-loglevel", "panic", "-y", "-re", "-i", video.File, "-i", "x.pgm", "-i", "y.pgm", "-filter_complex", "remap,format=yuv444p,format=yuv420p", "-c:v", video.Streams[0].Codec, "-b:v", strconv.Itoa(bitrate), "-c:a", "aac", "-x265-params", "log-level=error", output) + cmd := exec.Command("ffmpeg", "-hide_banner", "-progress", "pipe:1", "-loglevel", "panic", "-y", "-re", "-i", video.File, "-i", "x.pgm", "-i", "y.pgm", "-filter_complex", "remap,format=yuv444p,format=yuv420p", "-c:v", encoder, "-b:v", strconv.Itoa(bitrate), "-c:a", "aac", "-x265-params", "log-level=error", output) stdout, err := cmd.StdoutPipe() rd := bufio.NewReader(stdout) diff --git a/go.sum b/go.sum index b2bf1e1..d6fdf64 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ fyne.io/fyne v1.3.1-0.20200616154823-a82c32879cba h1:IBNJ2W3+ntf4wxYoN9QHK5lw0C/yrG7R6n5hS0mN6mE= fyne.io/fyne v1.3.1-0.20200616154823-a82c32879cba/go.mod h1:AcBUeR8hetITnnfaLvuVqioWM/lT18WPeMVAobhMbg8= -fyne.io/fyne v1.3.1 h1:s08z/QJAfSnEiuy+s5ATmeEeMQnOsdqfp8NMLQHCvzA= -fyne.io/fyne v1.3.1/go.mod h1:osD/JXxGf8AC7aB+Ek0YuFF2QXzdTFFzMRM8cdqrwvQ= github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9 h1:1ltqoej5GtaWF8jaiA49HwsZD459jqm9YFz9ZtMFpQA= github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I= github.com/akavel/rsrc v0.8.0 h1:zjWn7ukO9Kc5Q62DOJCcxGpXC18RawVtYAGdz2aLlfw= diff --git a/superview-cli.go b/superview-cli.go index b12a08e..7ec06da 100644 --- a/superview-cli.go +++ b/superview-cli.go @@ -11,8 +11,9 @@ import ( var opts struct { Input string `short:"i" long:"input" description:"The input video filename" value-name:"FILE" required:"true"` Output string `short:"o" long:"output" description:"The output video filename" value-name:"FILE" required:"false" default:"output.mp4"` + Codec string `short:"c" long:"codec" description:"The codec to encode in, either h264 or h265. If not specified, it takes the same codec of the input file" value-name:"[h264|h265]" required:"false"` Bitrate int `short:"b" long:"bitrate" description:"The bitrate in bytes/second to encode in. If not specified, take the same bitrate as the input file" value-name:"BITRATE" required:"false"` - Squeeze bool `short:"s" long:"squeeze" description:"Squeeze 4:3 video stretched to 16:9 (e.g. Caddx Tarsier 2.7k60)"` + Squeeze bool `short:"s" long:"squeeze" description:"Squeeze 4:3 video stretched to 16:9 (e.g. Caddx Tarsier 2.7k60)" required:"false"` } func main() { @@ -25,14 +26,15 @@ func main() { log.Fatal(err) } - codecs, err := checkCodecs() + ffmpeg, err := checkFfmpeg() if err != nil { log.Fatal(err) } - fmt.Printf("ffmpeg version: %s\n", codecs["version"]) - fmt.Printf("H.264 support: %s\n", codecs["h264"]) - fmt.Printf("H.265/HEVC support: %s\n", codecs["h265"]) + fmt.Printf("ffmpeg version: %s\n", ffmpeg["version"]) + fmt.Printf("Hardware accelerators: %s\n", ffmpeg["accels"]) + fmt.Printf("H.264 encoders: %s\n", ffmpeg["h264"]) + fmt.Printf("H.265/HEVC encoders: %s\n", ffmpeg["h265"]) video, err := checkVideo(opts.Input) if err != nil { @@ -44,11 +46,16 @@ func main() { opts.Bitrate = video.Streams[0].BitrateInt } + if opts.Codec == "" { + opts.Codec = video.Streams[0].Codec + } + encoder := findEncoder(opts.Codec, ffmpeg) + generatePGM(video, opts.Squeeze) - fmt.Printf("Re-encoding video at bitrate %d MB/s\n", opts.Bitrate/1024/1024) + fmt.Printf("Re-encoding video with %s encoder at %d MB/s bitrate\n", encoder, opts.Bitrate/1024/1024) - err = encodeVideo(video, opts.Bitrate, opts.Output, func(v float64) { + err = encodeVideo(video, encoder, opts.Bitrate, opts.Output, func(v float64) { fmt.Printf("\rEncoding progress: %.2f%%", v) }) if err != nil { diff --git a/superview-gui.go b/superview-gui.go index c3a0047..3192b43 100644 --- a/superview-gui.go +++ b/superview-gui.go @@ -17,6 +17,8 @@ import ( func main() { var video *VideoSpecs + var ffmpeg map[string]string + var encoder *widget.Select app := app.New() app.Settings().SetTheme(theme.LightTheme()) @@ -69,9 +71,18 @@ func main() { br = video.Streams[0].BitrateInt } - err = encodeVideo(video, br, uri, func(v float64) { prog.SetValue(v / 100) }) - if err != nil { + enc := video.Streams[0].Codec + switch encoder.Selected { + case "Force H264 encoder": + enc = "h264" + break + case "Force H265 encoder": + enc = "h265" + break + } + err = encodeVideo(video, findEncoder(enc, ffmpeg), br, uri, func(v float64) { prog.SetValue(v / 100) }) + if err != nil { dialog.ShowError(err, window) return } @@ -112,12 +123,32 @@ func main() { fd.Show() }) + ffmpeg, err := checkFfmpeg() + if err != nil { + dialog.ShowError(err, window) + open.Disable() + } + info.SetText(fmt.Sprintf("ffmpeg version: %s\nHardware accellerators: %s\nH.264 encoders: %s\nH.265/HEVC encoders: %s", ffmpeg["version"], ffmpeg["accels"], ffmpeg["h264"], ffmpeg["h265"])) + + encoderOptions := []string{"Use same encoder as input file"} + if len(ffmpeg["h264"]) > 0 { + encoderOptions = append(encoderOptions, "Force H264 encoder") + } + if len(ffmpeg["h265"]) > 0 { + encoderOptions = append(encoderOptions, "Force H265 encoder") + } + encoder = widget.NewSelect(encoderOptions, func(s string) { + + }) + encoder.SetSelected(encoderOptions[0]) + window.SetContent(widget.NewVBox( title, info, layout.NewSpacer(), open, squeeze, + encoder, bitrate, start, widget.NewButton("Quit", func() { @@ -127,12 +158,5 @@ func main() { window.Resize(fyne.NewSize(640, 330)) - codecs, err := checkCodecs() - if err != nil { - dialog.ShowError(err, window) - open.Disable() - } - info.SetText(fmt.Sprintf("ffmpeg version: %s\nH.264 support: %s\nH.265/HEVC support: %s", codecs["version"], codecs["h264"], codecs["h265"])) - window.ShowAndRun() }