diff --git a/go.mod b/go.mod index 2b7ab98fd..f136be772 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( codeberg.org/gruf/go-debug v1.3.0 codeberg.org/gruf/go-errors/v2 v2.3.2 codeberg.org/gruf/go-fastcopy v1.1.3 - codeberg.org/gruf/go-ffmpreg v0.4.2 + codeberg.org/gruf/go-ffmpreg v0.6.0 codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf codeberg.org/gruf/go-kv v1.6.5 codeberg.org/gruf/go-list v0.0.0-20240425093752-494db03d641f diff --git a/go.sum b/go.sum index 72e252234..b15a61bb8 100644 --- a/go.sum +++ b/go.sum @@ -46,8 +46,8 @@ codeberg.org/gruf/go-fastcopy v1.1.3 h1:Jo9VTQjI6KYimlw25PPc7YLA3Xm+XMQhaHwKnM7x codeberg.org/gruf/go-fastcopy v1.1.3/go.mod h1:GDDYR0Cnb3U/AIfGM3983V/L+GN+vuwVMvrmVABo21s= codeberg.org/gruf/go-fastpath/v2 v2.0.0 h1:iAS9GZahFhyWEH0KLhFEJR+txx1ZhMXxYzu2q5Qo9c0= codeberg.org/gruf/go-fastpath/v2 v2.0.0/go.mod h1:3pPqu5nZjpbRrOqvLyAK7puS1OfEtQvjd6342Cwz56Q= -codeberg.org/gruf/go-ffmpreg v0.4.2 h1:HKkPapm/PWkxsnUdjyQOGpwl5Qoa2EBrUQ09s4R4/FA= -codeberg.org/gruf/go-ffmpreg v0.4.2/go.mod h1:Ar5nbt3tB2Wr0uoaqV3wDBNwAx+H+AB/mV7Kw7NlZTI= +codeberg.org/gruf/go-ffmpreg v0.6.0 h1:/cfUJ9bFKEoXT9LDYZy3eZ0HF60YWcO+0nGciepJKMw= +codeberg.org/gruf/go-ffmpreg v0.6.0/go.mod h1:Ar5nbt3tB2Wr0uoaqV3wDBNwAx+H+AB/mV7Kw7NlZTI= codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf h1:84s/ii8N6lYlskZjHH+DG6jyia8w2mXMZlRwFn8Gs3A= codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf/go.mod h1:zZAICsp5rY7+hxnws2V0ePrWxE0Z2Z/KXcN3p/RQCfk= codeberg.org/gruf/go-kv v1.6.5 h1:ttPf0NA8F79pDqBttSudPTVCZmGncumeNIxmeM9ztz0= diff --git a/internal/media/ffmpeg.go b/internal/media/ffmpeg.go index 1d7b01905..c225d4378 100644 --- a/internal/media/ffmpeg.go +++ b/internal/media/ffmpeg.go @@ -181,6 +181,10 @@ func ffmpeg(ctx context.Context, inpath string, outpath string, args ...string) } fscfg = fscfg.WithFSMount(shared, path.Dir(inpath)) + // Set anonymous module name. + modcfg = modcfg.WithName("") + + // Update with prepared fs config. return modcfg.WithFSConfig(fscfg) }, }) @@ -247,6 +251,10 @@ func ffprobe(ctx context.Context, filepath string) (*result, error) { } fscfg = fscfg.WithFSMount(in, path.Dir(filepath)) + // Set anonymous module name. + modcfg = modcfg.WithName("") + + // Update with prepared fs config. return modcfg.WithFSConfig(fscfg) }, }) diff --git a/internal/media/ffmpeg/ffmpeg.go b/internal/media/ffmpeg/ffmpeg.go index 0571c029a..b978201c1 100644 --- a/internal/media/ffmpeg/ffmpeg.go +++ b/internal/media/ffmpeg/ffmpeg.go @@ -21,6 +21,7 @@ package ffmpeg import ( "context" + "errors" "codeberg.org/gruf/go-ffmpreg/wasm" ) @@ -35,12 +36,25 @@ var ffmpegRunner runner // prepares the runner to only allow max given concurrent running instances. func InitFfmpeg(ctx context.Context, max int) error { ffmpegRunner.Init(max) - return compileFfmpeg(ctx) + return initWASM(ctx) } // Ffmpeg runs the given arguments with an instance of ffmpeg. func Ffmpeg(ctx context.Context, args Args) (uint32, error) { return ffmpegRunner.Run(ctx, func() (uint32, error) { - return wasm.Run(ctx, runtime, ffmpeg, args) + + // Load WASM rt and module. + ffmpreg := ffmpreg.Load() + if ffmpreg == nil { + return 0, errors.New("wasm not initialized") + } + + // Call into ffmpeg. + args.Name = "ffmpeg" + return wasm.Run(ctx, + ffmpreg.run, + ffmpreg.mod, + args, + ) }) } diff --git a/internal/media/ffmpeg/ffprobe.go b/internal/media/ffmpeg/ffprobe.go index ccd5072dd..410935d9c 100644 --- a/internal/media/ffmpeg/ffprobe.go +++ b/internal/media/ffmpeg/ffprobe.go @@ -21,6 +21,7 @@ package ffmpeg import ( "context" + "errors" "codeberg.org/gruf/go-ffmpreg/wasm" ) @@ -35,12 +36,25 @@ var ffprobeRunner runner // prepares the runner to only allow max given concurrent running instances. func InitFfprobe(ctx context.Context, max int) error { ffprobeRunner.Init(max) - return compileFfprobe(ctx) + return initWASM(ctx) } // Ffprobe runs the given arguments with an instance of ffprobe. func Ffprobe(ctx context.Context, args Args) (uint32, error) { return ffprobeRunner.Run(ctx, func() (uint32, error) { - return wasm.Run(ctx, runtime, ffprobe, args) + + // Load WASM rt and module. + ffmpreg := ffmpreg.Load() + if ffmpreg == nil { + return 0, errors.New("wasm not initialized") + } + + // Call into ffprobe. + args.Name = "ffprobe" + return wasm.Run(ctx, + ffmpreg.run, + ffmpreg.mod, + args, + ) }) } diff --git a/internal/media/ffmpeg/wasm.go b/internal/media/ffmpeg/wasm.go index b23809d93..a5612ba14 100644 --- a/internal/media/ffmpeg/wasm.go +++ b/internal/media/ffmpeg/wasm.go @@ -22,72 +22,27 @@ package ffmpeg import ( "context" "os" + "sync/atomic" + "unsafe" - ffmpeglib "codeberg.org/gruf/go-ffmpreg/embed/ffmpeg" - ffprobelib "codeberg.org/gruf/go-ffmpreg/embed/ffprobe" + "codeberg.org/gruf/go-ffmpreg/embed" "codeberg.org/gruf/go-ffmpreg/wasm" "github.com/tetratelabs/wazero" ) -var ( - // shared WASM runtime instance. - runtime wazero.Runtime +// ffmpreg is a concurrency-safe pointer +// to our necessary WebAssembly runtime +// and compiled ffmpreg module instance. +var ffmpreg atomic.Pointer[struct { + run wazero.Runtime + mod wazero.CompiledModule +}] - // ffmpeg / ffprobe compiled WASM. - ffmpeg wazero.CompiledModule - ffprobe wazero.CompiledModule -) - -// compileFfmpeg ensures the ffmpeg WebAssembly has been -// pre-compiled into memory. If already compiled is a no-op. -func compileFfmpeg(ctx context.Context) error { - if ffmpeg != nil { - return nil - } - - // Ensure runtime already initialized. - if err := initRuntime(ctx); err != nil { - return err - } - - // Compile the ffmpeg WebAssembly module into memory. - cmod, err := runtime.CompileModule(ctx, ffmpeglib.B) - if err != nil { - return err - } - - // Set module. - ffmpeg = cmod - return nil -} - -// compileFfprobe ensures the ffprobe WebAssembly has been -// pre-compiled into memory. If already compiled is a no-op. -func compileFfprobe(ctx context.Context) error { - if ffprobe != nil { - return nil - } - - // Ensure runtime already initialized. - if err := initRuntime(ctx); err != nil { - return err - } - - // Compile the ffprobe WebAssembly module into memory. - cmod, err := runtime.CompileModule(ctx, ffprobelib.B) - if err != nil { - return err - } - - // Set module. - ffprobe = cmod - return nil -} - -// initRuntime initializes the global wazero.Runtime, -// if already initialized this function is a no-op. -func initRuntime(ctx context.Context) (err error) { - if runtime != nil { +// initWASM safely prepares new WebAssembly runtime +// and compiles ffmpreg module instance, if the global +// pointer has not been already. else, is a no-op. +func initWASM(ctx context.Context) error { + if ffmpreg.Load() != nil { return nil } @@ -105,7 +60,59 @@ func initRuntime(ctx context.Context) (err error) { cfg = cfg.WithCompilationCache(cache) } + var ( + run wazero.Runtime + mod wazero.CompiledModule + err error + set bool + ) + + defer func() { + if err == nil && set { + // Drop binary. + embed.B = nil + return + } + + // Close module. + if !isNil(mod) { + mod.Close(ctx) + } + + // Close runtime. + if !isNil(run) { + run.Close(ctx) + } + }() + // Initialize new runtime from config. - runtime, err = wasm.NewRuntime(ctx, cfg) - return + run, err = wasm.NewRuntime(ctx, cfg) + if err != nil { + return err + } + + // Compile ffmpreg WebAssembly into memory. + mod, err = run.CompileModule(ctx, embed.B) + if err != nil { + return err + } + + // Try set global WASM runtime and module, + // or if beaten to it defer will handle close. + set = ffmpreg.CompareAndSwap(nil, &struct { + run wazero.Runtime + mod wazero.CompiledModule + }{ + run: run, + mod: mod, + }) + + return nil +} + +// isNil will safely check if 'v' is nil without +// dealing with weird Go interface nil bullshit. +func isNil(i interface{}) bool { + type eface struct{ Type, Data unsafe.Pointer } + return (*eface)(unsafe.Pointer(&i)).Data == nil } diff --git a/vendor/codeberg.org/gruf/go-ffmpreg/embed/ffmpeg/ffmpeg.wasm b/vendor/codeberg.org/gruf/go-ffmpreg/embed/ffmpeg/ffmpeg.wasm deleted file mode 100644 index 9d1faa3ed..000000000 Binary files a/vendor/codeberg.org/gruf/go-ffmpreg/embed/ffmpeg/ffmpeg.wasm and /dev/null differ diff --git a/vendor/codeberg.org/gruf/go-ffmpreg/embed/ffmpeg/lib.go b/vendor/codeberg.org/gruf/go-ffmpreg/embed/ffmpeg/lib.go deleted file mode 100644 index abe32d7c1..000000000 --- a/vendor/codeberg.org/gruf/go-ffmpreg/embed/ffmpeg/lib.go +++ /dev/null @@ -1,25 +0,0 @@ -package ffmpeg - -import ( - _ "embed" - "os" -) - -func init() { - // Check for WASM source file path. - path := os.Getenv("FFMPEG_WASM") - if path == "" { - return - } - - var err error - - // Read file into memory. - B, err = os.ReadFile(path) - if err != nil { - panic(err) - } -} - -//go:embed ffmpeg.wasm -var B []byte diff --git a/vendor/codeberg.org/gruf/go-ffmpreg/embed/ffmpreg.wasm.gz b/vendor/codeberg.org/gruf/go-ffmpreg/embed/ffmpreg.wasm.gz new file mode 100644 index 000000000..1545d58bc Binary files /dev/null and b/vendor/codeberg.org/gruf/go-ffmpreg/embed/ffmpreg.wasm.gz differ diff --git a/vendor/codeberg.org/gruf/go-ffmpreg/embed/ffprobe/ffprobe.wasm b/vendor/codeberg.org/gruf/go-ffmpreg/embed/ffprobe/ffprobe.wasm deleted file mode 100644 index 0094c53f4..000000000 Binary files a/vendor/codeberg.org/gruf/go-ffmpreg/embed/ffprobe/ffprobe.wasm and /dev/null differ diff --git a/vendor/codeberg.org/gruf/go-ffmpreg/embed/ffprobe/lib.go b/vendor/codeberg.org/gruf/go-ffmpreg/embed/ffprobe/lib.go deleted file mode 100644 index c3c3a3df1..000000000 --- a/vendor/codeberg.org/gruf/go-ffmpreg/embed/ffprobe/lib.go +++ /dev/null @@ -1,25 +0,0 @@ -package ffprobe - -import ( - _ "embed" - "os" -) - -func init() { - // Check for WASM source file path. - path := os.Getenv("FFPROBE_WASM") - if path == "" { - return - } - - var err error - - // Read file into memory. - B, err = os.ReadFile(path) - if err != nil { - panic(err) - } -} - -//go:embed ffprobe.wasm -var B []byte diff --git a/vendor/codeberg.org/gruf/go-ffmpreg/embed/lib.go b/vendor/codeberg.org/gruf/go-ffmpreg/embed/lib.go new file mode 100644 index 000000000..7829b5524 --- /dev/null +++ b/vendor/codeberg.org/gruf/go-ffmpreg/embed/lib.go @@ -0,0 +1,39 @@ +package embed + +import ( + "bytes" + "compress/gzip" + _ "embed" + "io" + "os" +) + +func init() { + var err error + + if path := os.Getenv("FFMPREG_WASM"); path != "" { + // Read file into memory. + B, err = os.ReadFile(path) + if err != nil { + panic(err) + } + } + + // Wrap bytes in reader. + b := bytes.NewReader(B) + + // Create unzipper from reader. + gz, err := gzip.NewReader(b) + if err != nil { + panic(err) + } + + // Extract gzipped binary. + B, err = io.ReadAll(gz) + if err != nil { + panic(err) + } +} + +//go:embed ffmpreg.wasm.gz +var B []byte diff --git a/vendor/codeberg.org/gruf/go-ffmpreg/wasm/run.go b/vendor/codeberg.org/gruf/go-ffmpreg/wasm/run.go index 62ce2bc25..7b07d851d 100644 --- a/vendor/codeberg.org/gruf/go-ffmpreg/wasm/run.go +++ b/vendor/codeberg.org/gruf/go-ffmpreg/wasm/run.go @@ -14,6 +14,11 @@ import ( // wazero.Runtime on module instantiation. type Args struct { + // Program name, depending on the + // module being run this may or may + // not be necessary. + Name string + // Optional further module configuration function. // (e.g. to mount filesystem dir, set env vars, etc). Config func(wazero.ModuleConfig) wazero.ModuleConfig @@ -39,7 +44,7 @@ func Run( // Prefix arguments with module name. cargs := make([]string, len(args.Args)+1) - cargs[0] = module.Name() + cargs[0] = args.Name copy(cargs[1:], args.Args) // Prepare new module configuration. diff --git a/vendor/modules.txt b/vendor/modules.txt index 10c1e595c..e11598f09 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -24,10 +24,9 @@ codeberg.org/gruf/go-fastcopy # codeberg.org/gruf/go-fastpath/v2 v2.0.0 ## explicit; go 1.14 codeberg.org/gruf/go-fastpath/v2 -# codeberg.org/gruf/go-ffmpreg v0.4.2 +# codeberg.org/gruf/go-ffmpreg v0.6.0 ## explicit; go 1.22.0 -codeberg.org/gruf/go-ffmpreg/embed/ffmpeg -codeberg.org/gruf/go-ffmpreg/embed/ffprobe +codeberg.org/gruf/go-ffmpreg/embed codeberg.org/gruf/go-ffmpreg/wasm # codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf ## explicit; go 1.21