diff --git a/Makefile b/Makefile index c15d0ac..bfe8560 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,5 @@ -GO_IS_STUPID_EXE= - -ifeq '$(OS)' 'Windows_NT' - GO_IS_STUPID_EXE=.exe -endif - build: go build . + +install: build + sudo cp vidcrunch /usr/local/bin/ diff --git a/config.json b/config.json index 73114a5..024ffab 100644 --- a/config.json +++ b/config.json @@ -6,9 +6,8 @@ "VideoBitrate": 900, "AudioBitrate": 192, "VideoCodec": "libvpx-vp9", - "Preset": "veryfast", "CleanFilename": false, - "CRF": 30, + "CRF": 20, "FPS": 30, "Tune": "animation", "Input": "LGo*.mp4", @@ -17,27 +16,13 @@ "Extras": [ "-deadline", "realtime", "-row-mt", "1", - "-cpu-used", "8" + "-cpu-used", "8", + "-g", "150", + "-keyint_min", "150", + "-tile-columns", "4", + "-tile-rows", "2", + "-frame-parallel", "1", + "-tune-content", "1" ] - }, - { - "Format": "webm", - "VideoCodec": "libvpx-vp9", - "Scale": "1280:720", - "Resize": false, - "VideoBitrate": 600, - "AudioBitrate": 192, - "CleanFilename": false, - "CRF": 30, - "FPS": 30, - "Tune": "animation", - "Input": "LGo*.mp4", - "OutDir": "dash_test", - "Passes": 1, - "Extras": [ - "-deadline", "realtime", - "-row-mt", "1", - "-cpu-used", "8" - ] } ] diff --git a/config/settings.go b/config/settings.go index 3ae9c6a..fdf8865 100644 --- a/config/settings.go +++ b/config/settings.go @@ -31,7 +31,8 @@ type VideoOpts struct { type Settings struct { Debug int Force bool - Server string + Serve string + Port string ConfigPath string Encodings []VideoOpts } @@ -40,7 +41,8 @@ func ParseFlags(c *Settings) { flag.IntVar(&c.Debug, "debug", 0, "1=print the ffmpeg command, 2=and its stderr output") flag.BoolVar(&c.Force, "force", false, "Force the render even if the file exists.") flag.StringVar(&c.ConfigPath, "config", "config.json", "config.json to load") - flag.StringVar(&c.Server, "server", ":8099", "start a simple HTTP server for testing") + flag.StringVar(&c.Serve, "serve", "", "serve a directory webserver") + flag.StringVar(&c.Port, "port", ":8099", "start a simple HTTP server for testing") flag.Parse() } diff --git a/main.go b/main.go index 32d98b1..00f4110 100644 --- a/main.go +++ b/main.go @@ -1,128 +1,139 @@ package main import ( - "fmt" - "path/filepath" - "runtime" - "log" - "os" - "math/rand" - "strings" - "lcthw.dev/go/vidcrunch/config" - "github.com/modfy/fluent-ffmpeg" + "fmt" + "path/filepath" + "runtime" + "log" + "os" + "math/rand" + "net/http" + "strings" + "lcthw.dev/go/vidcrunch/config" + "github.com/modfy/fluent-ffmpeg" ) func ModFile(fname string, encoding config.VideoOpts) string { - cleaned := filepath.Clean(fname) - dir, file := filepath.Split(cleaned) - ext := filepath.Ext(file) + 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?!") } + base, found := strings.CutSuffix(file, ext) + if !found { panic("no extension found?!") } - dim := strings.Replace(encoding.Scale, ":", ".", 1) - renamed := fmt.Sprint(base, ".", dim, ".", encoding.Format) - return filepath.Join(dir, renamed) + 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", - "-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 { - 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)) - } - - encode.VideoCodec(encoding.VideoCodec). - VideoBitRate(encoding.VideoBitrate * 1024). - FrameRate(encoding.FPS). - ConstantRateFactor(encoding.CRF) - - extras = append(extras, encoding.Extras...) - encode.OutputOptions(extras...) - - cmd := encode.InputPath(input). - OutputFormat(encoding.Format). - OutputPath(output). + encode := fluentffmpeg.NewCommand("") + + extras := []string{ + "-pix_fmt", "yuv420p", + "-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 { + 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)) + } + + encode.VideoCodec(encoding.VideoCodec). + VideoBitRate(encoding.VideoBitrate * 1024). + FrameRate(encoding.FPS). + ConstantRateFactor(encoding.CRF) + + extras = append(extras, encoding.Extras...) + encode.OutputOptions(extras...) + + cmd := encode.InputPath(input). + OutputFormat(encoding.Format). + OutputPath(output). OutputLogs(os.Stdout). Overwrite(true). - Build() + Build() - fmt.Println(">", cmd.String()) + fmt.Println(">", cmd.String()) - err := cmd.Run() - if err != nil { log.Fatalf("%v", err) } + 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 RenderFile(encoding config.VideoOpts, pid int, path string, target string) { - for i := 1; i < encoding.Passes; i++ { - Run(encoding, i, pid, path, DevNull()) - } + for i := 1; i < encoding.Passes; i++ { + Run(encoding, i, pid, path, DevNull()) + } - Run(encoding, encoding.Passes, pid, path, target) + Run(encoding, encoding.Passes, pid, path, target) } func RenderToDir(encoding config.VideoOpts, force bool) { - matches, err := filepath.Glob(encoding.Input) - if err != nil { log.Fatalf("%v", err) } - - 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) - } - } + matches, err := filepath.Glob(encoding.Input) + if err != nil { log.Fatalf("%v", err) } + + 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) + } + } } func main() { - settings := config.Load() - - for _, encoding := range settings.Encodings { - 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 _, encoding := range settings.Encodings { + if encoding.OutDir != "" { + RenderToDir(encoding, settings.Force) + } else { + log.Fatal("config file needs either Output or OutDir") + } + } + } }