|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"path/filepath"
|
|
|
|
"runtime"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"math/rand"
|
|
|
|
"strings"
|
|
|
|
"lcthw.dev/vidcrunch/config"
|
|
|
|
"github.com/modfy/fluent-ffmpeg"
|
|
|
|
)
|
|
|
|
|
|
|
|
func ModFile(fname string, scale string) 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?!") }
|
|
|
|
|
|
|
|
dim := strings.Replace(scale, ":", ".", 1)
|
|
|
|
renamed := fmt.Sprint(base, ".", dim, ext)
|
|
|
|
return filepath.Join(dir, renamed)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func Run(pass int, pid int, input string, output string) {
|
|
|
|
encode := fluentffmpeg.NewCommand("")
|
|
|
|
|
|
|
|
mp4_opts := []string{
|
|
|
|
"-vf", fmt.Sprintf("scale=%s:flags=lanczos", config.Settings.Scale),
|
|
|
|
"-aspect", config.Settings.Scale,
|
|
|
|
"-pix_fmt", "yuv420p",
|
|
|
|
"-tune", config.Settings.Tune,
|
|
|
|
"-movflags", "faststart",
|
|
|
|
"-pass", fmt.Sprint(pass),
|
|
|
|
"-passlogfile", fmt.Sprintf("ffmpeg2pass-%x.log", pid),
|
|
|
|
"-preset", config.Settings.Speed,
|
|
|
|
}
|
|
|
|
|
|
|
|
if pass != 3 {
|
|
|
|
mp4_opts = append(mp4_opts, "-an")
|
|
|
|
} else {
|
|
|
|
encode.AudioCodec("aac")
|
|
|
|
mp4_opts = append(mp4_opts,
|
|
|
|
"-b:a", fmt.Sprint(config.Settings.AudioBitrate * 1024))
|
|
|
|
}
|
|
|
|
|
|
|
|
if config.Settings.Test > 0 {
|
|
|
|
encode.InputOptions(
|
|
|
|
"-ss", fmt.Sprintf("00:%d", config.Settings.TestStart))
|
|
|
|
mp4_opts = append(mp4_opts, "-t", fmt.Sprint(config.Settings.Test))
|
|
|
|
}
|
|
|
|
|
|
|
|
encode.VideoCodec("libx264").
|
|
|
|
VideoBitRate(config.Settings.VideoBitrate * 1024).
|
|
|
|
FrameRate(config.Settings.FPS).
|
|
|
|
ConstantRateFactor(config.Settings.CRF)
|
|
|
|
|
|
|
|
encode.OutputOptions(mp4_opts...)
|
|
|
|
|
|
|
|
cmd := encode.InputPath(input).
|
|
|
|
OutputFormat("mp4").
|
|
|
|
OutputPath(output).
|
|
|
|
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(pid int, path string, target string) {
|
|
|
|
target = ModFile(target, config.Settings.Scale)
|
|
|
|
Run(1, pid, path, DevNull())
|
|
|
|
Run(2, pid, path, DevNull())
|
|
|
|
Run(3, pid, path, target)
|
|
|
|
}
|
|
|
|
|
|
|
|
func RenderToDir() {
|
|
|
|
matches, err := filepath.Glob(config.Settings.Input)
|
|
|
|
if err != nil { log.Fatalf("%v", err) }
|
|
|
|
|
|
|
|
for _, path := range matches {
|
|
|
|
base := filepath.Base(path)
|
|
|
|
target := filepath.Join(config.Settings.OutDir, base)
|
|
|
|
|
|
|
|
_, err := os.Stat(target)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println("--- PATH", path, "->", target)
|
|
|
|
RenderFile(rand.Int(), path, target)
|
|
|
|
} else {
|
|
|
|
fmt.Println("^^^ SKIP", path, "->", target)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
config.Load()
|
|
|
|
|
|
|
|
if config.Settings.Output != "" {
|
|
|
|
RenderFile(rand.Int(), config.Settings.Input, config.Settings.Output)
|
|
|
|
} else if config.Settings.OutDir != "" {
|
|
|
|
RenderToDir()
|
|
|
|
} else {
|
|
|
|
log.Fatal("config file needs either Output or OutDir")
|
|
|
|
}
|
|
|
|
}
|