You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
236 lines
5.4 KiB
236 lines
5.4 KiB
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"
|
|
)
|
|
|
|
func AudioOnly(encoding config.VideoOpts) bool {
|
|
return encoding.Scale == "" && encoding.VideoCodec == "none"
|
|
}
|
|
|
|
func VideoOnly(encoding config.VideoOpts) bool {
|
|
return encoding.AudioCodec == "none"
|
|
}
|
|
|
|
func ModFile(fname string, encoding config.VideoOpts) string {
|
|
cleaned := filepath.Clean(fname)
|
|
dir, file := filepath.Split(cleaned)
|
|
ext := filepath.Ext(file)
|
|
|
|
base, found := strings.CutSuffix(file, ext)
|
|
if !found { panic("no extension found?!") }
|
|
|
|
if AudioOnly(encoding) {
|
|
renamed := fmt.Sprint(base, ".audio.", encoding.Format)
|
|
return filepath.Join(dir, renamed)
|
|
} else {
|
|
dim := strings.Replace(encoding.Scale, ":", ".", 1)
|
|
renamed := fmt.Sprint(base, ".", dim, ".", encoding.Format)
|
|
return filepath.Join(dir, renamed)
|
|
}
|
|
}
|
|
|
|
func Run(encoding config.VideoOpts, 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 encoding.Resize {
|
|
extras = append(extras,
|
|
"-vf", fmt.Sprintf("scale=%s:flags=lanczos", encoding.Scale),
|
|
"-aspect", encoding.Scale)
|
|
}
|
|
|
|
if pass != encoding.Passes || VideoOnly(encoding) {
|
|
extras = append(extras, "-an")
|
|
} else {
|
|
encode.AudioCodec(encoding.AudioCodec)
|
|
extras = append(extras,
|
|
"-b:a", fmt.Sprint(encoding.AudioBitrate * 1024))
|
|
}
|
|
|
|
|
|
if encoding.Test > 0 {
|
|
encode.InputOptions("-ss", fmt.Sprintf("00:%d", encoding.TestStart))
|
|
extras = append(extras, "-t", fmt.Sprint(encoding.Test))
|
|
}
|
|
|
|
if encoding.VideoCodec == "none" {
|
|
extras = append(extras, "-vn")
|
|
} else {
|
|
encode.VideoCodec(encoding.VideoCodec).
|
|
VideoBitRate(encoding.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(">", cmd.String())
|
|
|
|
err := cmd.Run()
|
|
if err != nil { log.Fatalf("%v", err) }
|
|
}
|
|
|
|
func DevNull() string {
|
|
if runtime.GOOS == "windows" {
|
|
return "NUL"
|
|
} else {
|
|
return "/dev/null"
|
|
}
|
|
}
|
|
|
|
func RenderFile(encoding config.VideoOpts, pid int, path string, target string) {
|
|
for i := 1; i < encoding.Passes; i++ {
|
|
Run(encoding, i, pid, path, DevNull())
|
|
}
|
|
|
|
Run(encoding, encoding.Passes, pid, path, target)
|
|
}
|
|
|
|
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 }
|
|
}
|
|
}
|
|
}
|
|
|
|
func SaveMPD(settings config.Settings) {
|
|
args := make([]string, 0, 10)
|
|
var the_audio string
|
|
|
|
// force it to overwrite
|
|
args = append(args, "-y")
|
|
|
|
for _, encoding := range settings.Encodings {
|
|
if VideoOnly(encoding) {
|
|
args = append(args, "-f", "webm_dash_manifest", "-i", encoding.FakeResult)
|
|
} else if AudioOnly(encoding) {
|
|
the_audio = encoding.FakeResult
|
|
}
|
|
}
|
|
|
|
args = append(args, "-f", "webm_dash_manifest", "-i", the_audio)
|
|
|
|
// create map for each
|
|
args = append(args, "-c", "copy")
|
|
|
|
for i := 0; i < len(settings.Encodings); 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")
|
|
|
|
da_ints := slices.Collect(StrSeq(len(settings.Encodings) - 1))
|
|
|
|
video_set := strings.Join(da_ints, ",")
|
|
|
|
adapt_set := fmt.Sprintf("id=0,streams=%s id=1,streams=%d",
|
|
video_set, len(settings.Encodings) - 1)
|
|
|
|
args = append(args, adapt_set)
|
|
args = append(args, filepath.Join(settings.Encodings[0].OutDir, "manifest.mpd"))
|
|
|
|
fmt.Println("\n\n\n\nARGS", args)
|
|
|
|
ffmpeg := exec.Command("ffmpeg", args...)
|
|
out, err := ffmpeg.CombinedOutput()
|
|
|
|
os.Stdout.Write(out)
|
|
log.Fatal(err)
|
|
}
|
|
|
|
func RenderToDir(encoding config.VideoOpts, force bool) string {
|
|
matches, err := filepath.Glob(encoding.Input)
|
|
if err != nil { log.Fatalf("%v", err) }
|
|
target := ""
|
|
|
|
for _, path := range matches {
|
|
base := filepath.Base(path)
|
|
target = filepath.Join(encoding.OutDir, base)
|
|
target = ModFile(target, encoding)
|
|
|
|
_, err := os.Stat(target)
|
|
|
|
if err != nil || force {
|
|
fmt.Println("--- PATH", path, "->", target)
|
|
RenderFile(encoding, rand.Int(), path, target)
|
|
} else {
|
|
fmt.Println("^^^ SKIP", path, "->", target)
|
|
}
|
|
}
|
|
|
|
if target == "" { log.Fatal("fuck!") }
|
|
|
|
return target
|
|
}
|
|
|
|
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++ {
|
|
fmt.Println("LOOP", i)
|
|
encoding := &settings.Encodings[i];
|
|
|
|
if encoding.OutDir != "" {
|
|
encoding.FakeResult = RenderToDir(*encoding, settings.Force)
|
|
fmt.Println("ENCODING result", encoding.FakeResult, "len=", len(settings.Encodings))
|
|
} else {
|
|
log.Fatal("config file needs either Output or OutDir")
|
|
}
|
|
}
|
|
|
|
SaveMPD(settings)
|
|
}
|
|
}
|
|
|