Development¶
afmpeg follows spec-driven development: no implementation change without a spec it implements. The specs are the authoritative decision record; the code is downstream of them.
Picking up implementation? Start at the implementation roadmap: the phased build order across all specs (0013–0034) with dependencies and prerequisites, and its Pick-up menu of the remaining trigger-gated work. Phases 0–4 are shipped (through vocab v9, job progress), and Phase 5 (the native backend + matrix, HEVC/AV1, perf) is largely shipped too. Current anchors: afmpeg v0.16.0, ffmpeg-wasi n9.0.1-3, job-spec vocab v10. What remains is all optional/trigger-gated:
0009CLI,0030WASM threads, arm64/darwin native, HW-accel encode,0025/0026.
Contributor docs¶
- CI security scanning: how the MR security gate works (govulncheck / osv-scanner / trivy / gitleaks), and the recorded decisions behind the osv-scanner ignore + job overrides (incl. the unfixable, unreachable x/crypto advisory).
Specs¶
The source of truth. They live in the project wiki, see the register for all of them, in number order, with their current status.
Start with 0001-afmpeg, the thesis; it decomposes into the
component specs.
They moved out of docs/ because a spec is a point-in-time decision record: read
for the conclusion it reached, not kept true as the code changes. docs/ holds only
what does change with the code.
| Also | Scope |
|---|---|
| external review: validation and disposition | The commissioned external review + our per-finding validation verdicts and spec mapping |
Method¶
- Spec first. Get a spec to
APPROVED, then implement against it test-first. New specs claim the next number from the register and are published to the wiki asspecs/NNNN-<slug>, not committed to this repository. - Library before CLI. Logic lives in
pkg/; any command layer is a thin adapter. - Test-first from the spec's contracts, table-driven with
t.Parallel(); the per-package coverage bar is ≥90% on newpkg/code. - Every package carries a
doc.go. The package-level documentation lives in a dedicateddoc.go(not scattered above a random file'spackageclause), so the package's purpose is discoverable in one place and onpkg.go.dev. - Docs land with the code, not after. A change that adds or reshapes a component ships its Diátaxis documentation in the same MR, an explanation page for a new component, a how-to for a new task, reference for a new config/CLI surface. Docs are part of "done", never an afterthought.
- Verify before PR:
just ci(tidy, generate, test, race, lint).
Local workflow¶
just # build (tidy + generate + CGO_ENABLED=0 build)
just test # unit tests with coverage
just test-race # race detector
just lint # golangci-lint
just ci # the full local gate
just docs-serve # preview this site
Integration test (real ffmpeg)¶
The runtime has a gated integration suite that loads real ffmpeg-wasi modules and transcodes in memory. It skips unless pointed at a module, and there is one variable per capability profile (spec 0022):
| Variable | Module |
|---|---|
AFMPEG_TEST_FFMPEG_WASI |
a lean build (or richer) |
AFMPEG_TEST_FFMPEG_WASI_INTERMEDIATE |
an intermediate build |
Profiles are cumulative but not interchangeable. Roughly a third of the suite exercises mpegts, HLS, libopus/libmp3lame/libvpx, yadif, loudnorm, libass burn-in or AV1 decode, none of which a lean build carries, so those tests need the intermediate module and skip without it, naming the variable to set. An intermediate build satisfies a lean test, so setting only the intermediate variable runs everything too.
or directly:
AFMPEG_TEST_FFMPEG_WASI=/path/to/ffmpeg-wasi-lgpl.wasm \
AFMPEG_TEST_FFMPEG_WASI_INTERMEDIATE=/path/to/ffmpeg-wasi-intermediate-lgpl.wasm \
go test ./pkg/afmpeg/ -run Integration -v
Both are published on every ffmpeg-wasi release.
Backend B (the native driver)¶
The pkg/afmpeg/native tests drive a native driver binary rather than a WASM module, and
a driver varies on two independent axes: the capability profile and the licence variant:
| Variable | Driver |
|---|---|
AFMPEG_TEST_NATIVE_DRIVER |
lean / lgpl |
AFMPEG_TEST_NATIVE_DRIVER_GPL |
lean / gpl |
AFMPEG_TEST_NATIVE_DRIVER_INTERMEDIATE |
intermediate / lgpl |
AFMPEG_TEST_NATIVE_DRIVER_INTERMEDIATE_GPL |
intermediate / gpl |
AFMPEG_TEST_NATIVE_DRIVER_FULL |
full / lgpl |
AFMPEG_TEST_NATIVE_DRIVER_FULL_GPL |
full / gpl |
Both axes are ordered, so a richer driver satisfies a poorer requirement and the resolver picks the least-rich adequate one supplied. In practice one intermediate/gpl driver runs every native test, because gpl is a superset of lgpl and intermediate of lean.
The variant axis is not cosmetic: cropdetect carries cropdetect_filter_deps="gpl"
upstream, so it is absent from every lgpl build no matter how rich the profile, as is the
libx264 encoder. A test needing either skips on an lgpl driver rather than failing with a
message about a missing filter.
Put these in a .env (the justfile sets dotenv-load) and just test-integration picks
them up. Full is native-only, per spec
0022 §4:
there is no WASM-full module.
The runtime provides the env setjmp/longjmp host module and the WebAssembly feature set a
real FFmpeg build needs (spec 0004 R-0004-9), so a released
ffmpeg-wasi engine (spec
0007) loads and runs.