A pure-Go FFmpeg binding that runs on a virtual / in-memory filesystem. No CGO,
no host FFmpeg install, no temp files: FFmpeg is supplied as a separate WebAssembly
module and executed via wazero (a zero-dependency, pure-Go WASM
runtime), with its I/O bridged to an afero.Fs — so
inputs and outputs can live entirely in memory (or any afero backend), and the whole
thing cross-compiles to a single static binary.
Status: released
afmpeg runs real FFmpeg over a virtual filesystem today: the
vfs bridge, the runtime
(New/Run/RunJob/Probe/Close), the Command builder (JobSpec()/RunJob for
the ffmpeg-wasi engine), and both
signature-verified WithModuleRelease and bring-your-own
WithModuleURL module acquisition. Pair it with a released
ffmpeg-wasi module to transcode,
remux, clip, filter, burn in subtitles, edit metadata, extract frames, and read
analysis-filter measurements (ProcessResult.Analysis) — entirely in
memory. A running job reports live progress on a channel
(WithProgress) — completion, frames, media time and encode speed. For encode- or
throughput-bound work there is also an opt-in
native backend — the same engine as a signed native
subprocess, for native-speed encode and the full profile's HEVC/AV1. See the
latest afmpeg release; design rationale
is in the specs under Development (start with
0001).
Why it exists¶
It was extracted from a need in keryx: keryx renders short reels by shelling out to the ffmpeg binary, which needs real files on disk — so it can't render an in-memory project (a remote cloned into RAM, no local checkout). Every existing Go option was rejected: purego bindings are immature and still need host libav; CGO bindings break a clean static cross-compile; and the existing wazero/WASM binding lacks the filters and codecs real workflows need. afmpeg is the "wazero + WASM done right" synthesis — a maintained FFmpeg-WASM build with the codecs/filters we need, a first-class afero virtual-filesystem I/O layer, and a clean Go API. keryx now renders its reels through afmpeg — in-memory, pure Go, with no local checkout.
How it works¶
Three layers — the middle one is the novel engineering:
- The FFmpeg-WASM module — current FFmpeg compiled to
wasm32-wasi, configured to only the codecs/filters needed; shipped as a separate artifact, never embedded. - The afero ↔ wazero vfs bridge (the heart) — routes the guest ffmpeg's WASI
filesystem syscalls to a
sys.FSbacked by the caller'safero.Fs, so reads and writes hit an in-memory filesystem with no host disk touched. - The Go API — compile the module once into a reusable
Runtime, thenRunan ffmpeg invocation over a suppliedafero.Fs; a general command builder layers on top.
The runtime sits behind a backend seam (spec 0028): the WASM module is the default, and an opt-in native backend runs the same engine as a native subprocess for native-speed encode and HEVC/AV1 — same API, same afero I/O, no CGO.
See the architecture explainer for the full flow, and the roadmap for how the specs decompose the build.
Where to go next¶
- Your first in-memory transcode — start here: a working program in about fifteen minutes.
- How-to guides — solve a specific task.
- Explanation — the architecture and the why.
- Reference — every option, field, default and limit.
- Development — contributor docs.
The Go API reference lives on pkg.go.dev.
Further reading¶
Everything written about the estate, including the curated guides, is on the blog.
Ask phpbotscout

He answers questions about the projects over on the Discord, citing the docs where they already cover it, and offering to raise an issue where they don't. Bring a bug, an idea, or a questionable engineering decision.