Skip to content

Commit

Permalink
Added initial hardware acceleration support, fix #13
Browse files Browse the repository at this point in the history
  • Loading branch information
Niek committed Jul 30, 2020
1 parent f61cadb commit e693c27
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 28 deletions.
59 changes: 49 additions & 10 deletions common.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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)

Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -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=
Expand Down
21 changes: 14 additions & 7 deletions superview-cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down
42 changes: 33 additions & 9 deletions superview-gui.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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() {
Expand All @@ -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()
}

0 comments on commit e693c27

Please sign in to comment.