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.1, ffmpeg-wasi n9.0.1-8, 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.
Option names in the docs¶
A wrong option name in an example compiles, lints clean and fails only at runtime, so two
checks cover the names docs/ and the README teach
(afmpeg#11):
- The lexical check in
internal/docsoptionsreads every key set throughEncoderOption,VideoOption,AudioOption,SubtitleOption,FormatOption,DemuxerOptionor one of theOptionsmaps, and fails on a key with a:in it (b:v) or on a muxer-only name in an encoder dictionary and the reverse (movflags). It runs injust test,just check(asjust lint-docs-options) and in every MR pipeline that touches the docs. It cannot see a name that is merely wrong, such ascrff. - The driver check,
TestIntegration_DocumentedOptionsAreAcceptedinpkg/afmpeg/native, offers each key to the encoder, muxer or demuxer the docs set it on and requires the job to succeed, which catchescrfftoo. It is gated like the other native tests, so the MR pipeline never runs it. One full/gpl driver covers every example:
AFMPEG_TEST_NATIVE_DRIVER_FULL_GPL=/path/to/ffmpeg-wasi-driver-linux-amd64-full-gpl \
go test ./pkg/afmpeg/native/ -run DocumentedOptions -v
A poorer driver runs what it can and skips the rest, naming the driver each example needs.