Architecture¶
afmpeg is three layers. The middle one is the novel engineering — everything else is wiring around it.
caller's afero.Fs pkg/afmpeg (the Go API)
(MemMapFs / OsFs / …) New → compile module once; Run/Probe per call
│ │
▼ ▼
internal/vfs ──────────────► internal/wasm ──────────────► ffmpeg.wasm
afero.Fs → wazero module wiring (FFmpeg + x264,
experimental/sys.FS over wazero wasm32-wasi)
▲ │
└──────────────── WASI fs syscalls (path_open, fd_read, ──────┘
fd_write, fd_seek, …) routed to the afero.Fs
1. The embedded FFmpeg-WASM module¶
FFmpeg and its dependencies (x264, …) compiled to wasm32-wasi, configured down to only
the codecs/filters real workflows need. It is produced by a reproducible build pipeline
and — per the licensing decision — shipped as a separate downloadable artifact, not
//go:embed-ed, so the GPL obligation stays at arm's length from the permissively
licensed Go package. See spec
0002.
2. The afero ↔ wazero vfs bridge (the heart)¶
ffmpeg-in-the-guest issues WASI filesystem syscalls. wazero routes them to a mounted
experimental/sys.FS. afmpeg implements that sys.FS backed by an afero.Fs — so
the guest's reads and writes hit the caller's filesystem (e.g. an in-memory MemMapFs)
with no host disk touched. It also provides a writable /tmp and /dev/null the guest
needs, and must handle seek-on-write (the mp4 muxer rewrites the moov atom under
+faststart). This is what every other Go ffmpeg-wasm binding lacks. See spec
0003.
3. The Go API¶
New compiles the module once (the expensive step) into a reusable Runtime. Run
mounts a caller-supplied afero.Fs, runs the module with the given args, and returns the
exit code + captured stdout/stderr; RunJob/Command.JobSpec render a job for the
ffmpeg-wasi engine, and Probe reports a file's
container, duration, and streams over the same bridge. The use-case-agnostic command
builder layers on top (a consumer's reel/timeline is built on it, in the consumer's code).
See specs 0004 and
0005.
Why this shape¶
The alternatives — purego/dlopen bindings (immature, still need host libav), CGO libav bindings (break a clean static cross-compile), and the stock wazero binding (missing filters/AAC, not filesystem-virtualised) — each failed at least one of pure-Go, in-memory, or has-the-codecs-we-need. afmpeg is the synthesis that holds all three. The full reasoning is in spec 0001 §11.