Enable hardware accel but nothing improves the performance over libx264, and in some cases--like vulkan--it was slower. Tryed some of the amdgpu-install and h264_amf support but it relies on Ubuntu 24.

master
Zed A. Shaw 1 month ago
parent be44bcec22
commit e86695d903
  1. 4
      config.json
  2. 1
      config/settings.go
  3. 393
      main.go

@ -31,8 +31,8 @@
],
"CRF": 30,
"FPS": 30,
"Input": "test*.mp4",
"OutDir": "dash_test",
"Input": "LGoTHW/*.mp4",
"OutDir": "LGoTHW_DASH",
"Passes": 1,
"Dash": true,
"Extras": [

@ -26,6 +26,7 @@ type EncodeOpts struct {
Dash bool
CRF int
FPS int
HardwareAccel string
Tune string
Input string
OutDir string

@ -1,133 +1,140 @@
package main
import (
"fmt"
"path/filepath"
"runtime"
"log"
"os"
"math/rand"
"net/http"
"strings"
"lcthw.dev/go/vidcrunch/config"
"github.com/modfy/fluent-ffmpeg"
"strconv"
"os/exec"
"slices"
"iter"
"fmt"
"path/filepath"
"runtime"
"log"
"os"
"math/rand"
"net/http"
"strings"
"lcthw.dev/go/vidcrunch/config"
"github.com/modfy/fluent-ffmpeg"
"strconv"
"os/exec"
"slices"
"iter"
)
func AudioOnly(codec config.CodecOpts) bool {
return codec.Scale == "" && codec.VideoCodec == "none"
return codec.Scale == "" && codec.VideoCodec == "none"
}
func VideoOnly(codec config.CodecOpts) bool {
return codec.AudioCodec == "none"
return codec.AudioCodec == "none"
}
func ExtractBase(source_path string, outdir string) (string, string) {
base := filepath.Base(source_path)
target := filepath.Join(outdir, base)
base := filepath.Base(source_path)
target := filepath.Join(outdir, base)
cleaned := filepath.Clean(target)
dir, file := filepath.Split(cleaned)
ext := filepath.Ext(file)
cleaned := filepath.Clean(target)
dir, file := filepath.Split(cleaned)
ext := filepath.Ext(file)
base, found := strings.CutSuffix(file, ext)
if !found { panic("no extension found?!") }
base, found := strings.CutSuffix(file, ext)
if !found { panic("no extension found?!") }
return dir, base
return dir, base
}
func SetCodecTarget(codec *config.CodecOpts, source_path string, encoding config.EncodeOpts) {
dir, base := ExtractBase(source_path, encoding.OutDir)
dir, base := ExtractBase(source_path, encoding.OutDir)
dot_or_dash := "."; if encoding.Dash { dot_or_dash = "/" }
dot_or_dash := "."; if encoding.Dash { dot_or_dash = "/" }
if AudioOnly(*codec) {
renamed := fmt.Sprint(base, dot_or_dash, "audio.", encoding.Format)
codec.Target = filepath.Join(dir, renamed)
} else {
dim := strings.Replace(codec.Scale, ":", ".", 1)
renamed := fmt.Sprint(base, dot_or_dash, dim, ".", encoding.Format)
codec.Target = filepath.Join(dir, renamed)
}
if AudioOnly(*codec) {
renamed := fmt.Sprint(base, dot_or_dash, "audio.", encoding.Format)
codec.Target = filepath.Join(dir, renamed)
} else {
dim := strings.Replace(codec.Scale, ":", ".", 1)
renamed := fmt.Sprint(base, dot_or_dash, dim, ".", encoding.Format)
codec.Target = filepath.Join(dir, renamed)
}
codec.Target = filepath.ToSlash(codec.Target)
codec.Target = filepath.ToSlash(codec.Target)
}
func Run(encoding config.EncodeOpts, codec config.CodecOpts, pass int, pid int, input string, output string) {
encode := fluentffmpeg.NewCommand("")
extras := []string{
"-pix_fmt", "yuv420p",
}
if encoding.Passes > 1 {
extras = append(extras,
"-pass", fmt.Sprint(pass),
"-passlogfile", fmt.Sprintf("ffmpegpass-%x.log", pid))
}
if encoding.Preset != "" {
extras = append(extras, "-preset", encoding.Preset)
}
if codec.Resize {
extras = append(extras,
"-vf", fmt.Sprintf("scale=%s:flags=lanczos", codec.Scale),
"-aspect", codec.Scale)
}
if pass != encoding.Passes || VideoOnly(codec) {
extras = append(extras, "-an")
} else {
encode.AudioCodec(codec.AudioCodec)
extras = append(extras,
"-b:a", fmt.Sprint(codec.AudioBitrate * 1024))
}
if encoding.Test > 0 {
encode.InputOptions("-ss", fmt.Sprintf("00:%d", encoding.TestStart))
extras = append(extras, "-t", fmt.Sprint(encoding.Test))
}
if codec.VideoCodec == "none" {
extras = append(extras, "-vn")
} else {
encode.VideoCodec(codec.VideoCodec).
VideoBitRate(codec.VideoBitrate * 1024).
FrameRate(encoding.FPS).
ConstantRateFactor(encoding.CRF)
}
extras = append(extras, encoding.Extras...)
if encoding.Dash {
extras = append(extras, "-dash", "1")
}
encode.OutputOptions(extras...)
cmd := encode.InputPath(input).
OutputFormat(encoding.Format).
OutputPath(output).
OutputLogs(os.Stdout).
Overwrite(true).
Build()
fmt.Println("====== FFMPEG ======\n", cmd.String(), "\n============")
err := cmd.Run()
if err != nil { log.Fatalf("%v", err) }
encode := fluentffmpeg.NewCommand("")
extras := []string{
"-pix_fmt", "yuv420p",
}
in_extras := []string{}
if encoding.Passes > 1 {
extras = append(extras,
"-pass", fmt.Sprint(pass),
"-passlogfile", fmt.Sprintf("ffmpegpass-%x.log", pid))
}
if encoding.Preset != "" {
extras = append(extras, "-preset", encoding.Preset)
}
if codec.Resize {
extras = append(extras,
"-vf", fmt.Sprintf("scale=%s:flags=lanczos", codec.Scale),
"-aspect", codec.Scale)
}
if pass != encoding.Passes || VideoOnly(codec) {
extras = append(extras, "-an")
} else {
encode.AudioCodec(codec.AudioCodec)
extras = append(extras,
"-b:a", fmt.Sprint(codec.AudioBitrate * 1024))
}
if encoding.Test > 0 {
in_extras = append(in_extras, "-ss", fmt.Sprintf("00:%d", encoding.TestStart))
extras = append(extras, "-t", fmt.Sprint(encoding.Test))
}
if encoding.HardwareAccel != "" {
in_extras = append(in_extras, "-hwaccel", encoding.HardwareAccel)
}
if codec.VideoCodec == "none" {
extras = append(extras, "-vn")
} else {
encode.VideoCodec(codec.VideoCodec).
VideoBitRate(codec.VideoBitrate * 1024).
FrameRate(encoding.FPS).
ConstantRateFactor(encoding.CRF)
}
extras = append(extras, encoding.Extras...)
if encoding.Dash {
extras = append(extras, "-dash", "1")
}
encode.InputOptions(in_extras...)
encode.OutputOptions(extras...)
cmd := encode.InputPath(input).
OutputFormat(encoding.Format).
OutputPath(output).
OutputLogs(os.Stdout).
Overwrite(true).
Build()
fmt.Println("====== FFMPEG ======\n", cmd.String(), "\n============")
err := cmd.Run()
if err != nil { log.Fatalf("%v", err) }
}
func DevNull() string {
if runtime.GOOS == "windows" {
return "NUL"
} else {
return "/dev/null"
}
if runtime.GOOS == "windows" {
return "NUL"
} else {
return "/dev/null"
}
}
func FileExists(path string) bool {
@ -136,125 +143,131 @@ func FileExists(path string) bool {
}
func RenderFile(encoding config.EncodeOpts, pid int, source_path string, force bool) {
for i := 0; i < len(encoding.Variants); i++ {
codec := &encoding.Variants[i]
for i := 0; i < len(encoding.Variants); i++ {
codec := &encoding.Variants[i]
SetCodecTarget(codec, source_path, encoding)
SetCodecTarget(codec, source_path, encoding)
// ensure the target directories exist
target_dir := filepath.Dir(codec.Target)
// ensure the target directories exist
target_dir := filepath.Dir(codec.Target)
if !FileExists(codec.Target) {
err := os.MkdirAll(target_dir, 0750)
if err != nil {
log.Fatal("failed to make target path: %s: %v", target_dir, err)
}
}
if !FileExists(codec.Target) {
err := os.MkdirAll(target_dir, 0750)
if err != nil {
log.Fatal("failed to make target path: %s: %v", target_dir, err)
}
}
if FileExists(codec.Target) && !force {
fmt.Println("^^^ SKIP", source_path, "->", codec.Target)
return
}
if FileExists(codec.Target) && !force {
fmt.Println("^^^ SKIP", source_path, "->", codec.Target)
return
}
fmt.Println("--- PATH", source_path, "->", codec.Target)
for i := 1; i < encoding.Passes; i++ {
Run(encoding, *codec, i, pid, source_path, DevNull())
}
fmt.Println("--- PATH", source_path, "->", codec.Target)
for i := 1; i < encoding.Passes; i++ {
Run(encoding, *codec, i, pid, source_path, DevNull())
}
Run(encoding, *codec, encoding.Passes, pid, source_path, codec.Target)
}
Run(encoding, *codec, encoding.Passes, pid, source_path, codec.Target)
}
SaveMPD(source_path, encoding)
if encoding.Dash {
SaveMPD(source_path, encoding)
}
}
func StrSeq(up_to int) iter.Seq[string] {
return func (yield func(x string) bool) {
for i := range up_to {
if !yield(strconv.Itoa(i)) { return }
}
}
return func (yield func(x string) bool) {
for i := range up_to {
if !yield(strconv.Itoa(i)) { return }
}
}
}
func SaveMPD(source_path string, encoding config.EncodeOpts) {
args := make([]string, 0, 10)
var the_audio string
args := make([]string, 0, 10)
var the_audio string
// force it to overwrite
args = append(args, "-y")
// force it to overwrite
args = append(args, "-y")
for _, codec := range encoding.Variants {
if VideoOnly(codec) {
args = append(args, "-f", "webm_dash_manifest", "-i", codec.Target)
} else if AudioOnly(codec) {
the_audio = codec.Target
}
}
for _, codec := range encoding.Variants {
if VideoOnly(codec) {
args = append(args, "-f", "webm_dash_manifest", "-i", codec.Target)
} else if AudioOnly(codec) {
the_audio = codec.Target
}
}
args = append(args, "-f", "webm_dash_manifest", "-i", the_audio)
args = append(args, "-f", "webm_dash_manifest", "-i", the_audio)
// create map for each
args = append(args, "-c", "copy")
// create map for each
args = append(args, "-c", "copy")
for i := 0; i < len(encoding.Variants); i++ {
args = append(args, "-map", strconv.Itoa(i))
}
for i := 0; i < len(encoding.Variants); i++ {
args = append(args, "-map", strconv.Itoa(i))
}
// generate adaptation sets, separating the audio
// id=0 is videos, id=1 is audio
args = append(args, "-f", "webm_dash_manifest", "-adaptation_sets")
// generate adaptation sets, separating the audio
// id=0 is videos, id=1 is audio
args = append(args, "-f", "webm_dash_manifest", "-adaptation_sets")
da_ints := slices.Collect(StrSeq(len(encoding.Variants) - 1))
da_ints := slices.Collect(StrSeq(len(encoding.Variants) - 1))
video_set := strings.Join(da_ints, ",")
video_set := strings.Join(da_ints, ",")
adapt_set := fmt.Sprintf("id=0,streams=%s id=1,streams=%d",
video_set, len(encoding.Variants) - 1)
adapt_set := fmt.Sprintf("id=0,streams=%s id=1,streams=%d",
video_set, len(encoding.Variants) - 1)
args = append(args, adapt_set)
args = append(args, adapt_set)
dir, base := ExtractBase(source_path, encoding.OutDir)
mpd_path := filepath.ToSlash(filepath.Join(dir, base, "manifest.mpd"))
args = append(args, mpd_path)
dir, base := ExtractBase(source_path, encoding.OutDir)
mpd_path := filepath.ToSlash(filepath.Join(dir, base, "manifest.mpd"))
args = append(args, mpd_path)
ffmpeg := exec.Command("ffmpeg", args...)
fmt.Println("==== MPD COMMAND ====\n", ffmpeg.String(), "\n==============")
out, err := ffmpeg.CombinedOutput()
ffmpeg := exec.Command("ffmpeg", args...)
fmt.Println("==== MPD COMMAND ====\n", ffmpeg.String(), "\n==============")
out, err := ffmpeg.CombinedOutput()
os.Stdout.Write(out)
log.Fatal(err)
os.Stdout.Write(out)
if err != nil { log.Fatal(err) }
}
func RenderToDir(encoding config.EncodeOpts, force bool) {
matches, err := filepath.Glob(encoding.Input)
matches, err := filepath.Glob(encoding.Input)
if err != nil { log.Fatalf("%v", err) }
if err != nil { log.Fatal(err) }
for _, source_path := range matches {
RenderFile(encoding, rand.Int(), source_path, force)
}
for _, source_path := range matches {
// BUG: glob will return these even if it's not in the regex
if source_path != "." || source_path != ".." {
RenderFile(encoding, rand.Int(), source_path, force)
}
}
}
func main() {
settings := config.Load()
if settings.Serve != "" {
dir_handler := http.FileServer(http.Dir(settings.Serve))
http.HandleFunc("/", func (w http.ResponseWriter, r *http.Request) {
fmt.Println(r.Method, r.URL)
dir_handler.ServeHTTP(w, r)
})
log.Fatal(http.ListenAndServe(settings.Port, nil))
} else {
for i := 0; i < len(settings.Encodings); i++ {
encoding := &settings.Encodings[i];
if encoding.OutDir != "" {
RenderToDir(*encoding, settings.Force)
} else {
log.Fatal("config file needs either Output or OutDir")
}
}
}
settings := config.Load()
if settings.Serve != "" {
dir_handler := http.FileServer(http.Dir(settings.Serve))
http.HandleFunc("/", func (w http.ResponseWriter, r *http.Request) {
fmt.Println(r.Method, r.URL)
dir_handler.ServeHTTP(w, r)
})
log.Fatal(http.ListenAndServe(settings.Port, nil))
} else {
for i := 0; i < len(settings.Encodings); i++ {
encoding := &settings.Encodings[i];
if encoding.OutDir != "" {
RenderToDir(*encoding, settings.Force)
} else {
log.Fatal("config file needs either Output or OutDir")
}
}
}
}

Loading…
Cancel
Save