# FFmpeg WebAssembly build record av-tools ships the locally rebuilt, reviewed core profile `0.12.10-reviewed-stack5m.1` in single-thread and multithread forms. The browser wrapper is `@ffmpeg/ffmpeg` **0.12.15** and helpers are `@ffmpeg/util` **0.12.2**. All versions and build inputs are exact, not ranges. ## Underlying build The base ffmpeg.wasm upstream release is `v12.15` at `71aa99d37c02a7b4c435275ca9ef50e612f6efa1`. Its Dockerfile selects: - FFmpeg **n5.1.4** (libavcodec 59.37.100); - Emscripten SDK **3.1.40**; - production `-O3 -msimd128`; - pthread flags for the MT build and explicit thread disabling for ST. The reviewed profile then applies upstream ffmpeg.wasm commit `b409e36475bc21f0451b5b1e1d126fa82871439a` to select a 5 MiB Emscripten stack. That upstream fix addresses the libopus zero-byte/out-of-bounds failures reported in issues `#591` and `#775`. The local driver additionally makes the upstream container-stage dependency isolation explicit, disables native x265 assembly for the wasm32 build, bounds parallel jobs and refuses output that contains the maintainer's repository or SDK paths. The build configuration embedded in both copied WASM files is: ```text --target-os=none --arch=x86_32 --enable-cross-compile --disable-asm --disable-stripping --disable-programs --disable-doc --disable-debug --disable-runtime-cpudetect --disable-autodetect --nm=emnm --ar=emar --ranlib=emranlib --cc=emcc --cxx=em++ --objcc=emcc --dep-cc=emcc --extra-cflags='-I../../../../install/include -O3 -msimd128' --extra-cxxflags='-I../../../../install/include -O3 -msimd128' --enable-gpl --enable-libx264 --enable-libx265 --enable-libvpx --enable-libmp3lame --enable-libtheora --enable-libvorbis --enable-libopus --enable-zlib --enable-libwebp --enable-libfreetype --enable-libfribidi --enable-libass --enable-libzimg ``` The ST string also has `--disable-pthreads --disable-w32threads --disable-os2threads`; the MT build supplies Emscripten pthread flags instead. At runtime the application executes `-version`, `-buildconf`, `-formats`, `-codecs`, `-encoders` and `-filters` sequentially. Presets are enabled only when the active core reports every required muxer, encoder and filter. Build flags alone are not treated as a capability guarantee. ## External libraries The upstream build references x264 `4-cores`, x265 3.4, libvpx v1.13.1, LAME `master`, Ogg v1.3.4, Theora v1.1.1, Opus v1.3.1, Vorbis v1.3.3, zlib v1.2.11, libwebp v1.3.2, FreeType VER-2-10-4, FriBidi v1.0.9, HarfBuzz 5.2.0, libass 0.15.0 and zimg release-3.0.5. These resolve to x264 `33cac6b77d5b9259c552156013a817ab23119612`, x265 `2bb5520e9596f361bf0ed81b3b8da0d7fd999069` and LAME `2badea1974ae36cb8312afe99cff1e6b3b5decee`. All three commits predate the ffmpeg.wasm v12.15 release; the branch-based x264/LAME commits remain the respective branch tips. `SOURCE.md` records retrieval commands. ## Shipped files | Mode | File | Bytes | SHA-256 | | ---- | -------------------------- | -------: | ------------------------------------------------------------------ | | ST | `st/ffmpeg-core.js` | 111804 | `80c05d79d0e4e9434977b76cb851d10ddf2bfd378570178700d2c43b8afdcb24` | | ST | `st/ffmpeg-core.wasm` | 32232580 | `fe41ddc77220cef6c04f5d48eeffcaaaef179ad270642e098f9945013aa0c9dc` | | MT | `mt/ffmpeg-core.js` | 128953 | `4f2650099ab70cb2583951c0421147c62bea6c18dfbba5cfae4d7698b5d0ab62` | | MT | `mt/ffmpeg-core.wasm` | 32718455 | `abbead010cb0448b26f01a36a3c8c03a6b04aca07812ddd4719a7f4b6bf4b645` | | MT | `mt/ffmpeg-core.worker.js` | 2115 | `f77898d631dc010b45c29c23cb4379c611a7d7b131bf591d08a656bb729a4ca3` | The deterministic five-file repository artifact is 21,338,974 bytes with SHA-256 `ebc068c6d096de55ae3335b04b7121130665f5b0ffa7a828067122733f55185f`. `npm run vendor:ffmpeg` verifies and unpacks the deterministic tracked core artifact to `public/vendor/ffmpeg/0.12.10-reviewed-stack5m.1/`; `npm run vendor:verify-runtime` checks the build profile, source/toolchain/patch identity, tracked artifact, generated paths, sizes and digests without requiring a release-directory download. The stricter `npm run vendor:verify` release gate also hashes the build-qualified Corresponding Source archive. Vite then copies the verified bytes into `dist`. A normal production build performs no network download and cannot fall back to the older published npm core bytes. ## Multithread Chromium guardrail The pinned multithread core can stall in Chromium when FFmpeg lets codecs use the browser's full reported logical-core count. This is tracked upstream in [ffmpeg.wasm issue 654](https://github.com/ffmpegwasm/ffmpeg.wasm/issues/654). av-tools keeps the pthread core but adds `-threads 2` as an input and output codec option for MT jobs. ST arguments remain unchanged. A regular browser test performs an actual MT H.264/AAC conversion; loading the core alone is not accepted as functional evidence. Every execution also has a watchdog: an explicit shorter plan timeout wins; otherwise the timeout is at least two minutes, scales to thirty times the expected media duration, and is capped at the displayed four-hour product limit. Timeout is still a failure, not a successful partial export. ## Long-session heap guardrail The pinned Emscripten core was observed to end a later heterogeneous browser workflow with a bare `Aborted()` even though the same command passed in a fresh core and all per-job virtual files had been removed. Its `tile` filter also aborted deterministically on a fourth consecutive contact sheet in local stress runs. av-tools therefore refreshes an idle core before its seventeenth media execution and before a fourth contact-sheet execution. Application queue state, sources and completed results remain outside the worker and are not discarded. A bare core abort also triggers recreation after cleanup so a manual or queued retry starts on a fresh instance. This is a conservative browser-memory guardrail, not a guarantee that sixteen large jobs or three large contact sheets fit in memory. Cancellation and failed filesystem-cleanup recovery continue to recreate the core immediately. ## Opus regression evidence Published core 0.12.10 predates the upstream stack-size fix and stalled in the real Opus preset path even though it advertised the `opus` muxer and `libopus` encoder. Capability discovery alone was therefore not accepted as enablement evidence. On 2026-07-26 the Chromium harness used the checked `tests/fixtures/generated/tone.wav` fixture (32,102 bytes, SHA-256 `0fd1f54d6be2f1cc7fd778775dd29cf184770d3716cf3394bf03ed54ac264c3c`) and the real built-in `audio-opus` preset/command planner. After rebuilding with the reviewed stack profile, the isolated ST and MT runs each: - loaded the requested mode rather than a fallback; - mapped PCM audio to `opus (libopus)`; - opened the declared Opus output and reported its libopus encoder; - returned non-empty bytes within its independent containment limit; - re-probed the result as positive-duration Opus audio. ```sh # Record JSON evidence. npm run evidence:opus # Positive Opus enablement/release gate. npm run test:opus-regression ``` The Opus audio and VP9/Opus presets are enabled only when the reviewed-core lock records both passing modes and runtime discovery finds every preset requirement. This is exact Chromium fixture evidence, not a claim about every browser, input or possible Opus build. ## Licence consequence The actual core build enables GPL and links external codecs. The wrapper/util are MIT. av-tools is GPL-3.0-or-later, and the distributed application must not be described as MIT-only. Publish the reviewed Corresponding Source and external notices with the binary release. See `LICENSE`, `LICENSES/`, `THIRD_PARTY_NOTICES.md` and `SOURCE.md`.