# ffmpeg-core (audio-only, LGPL) build recipe This directory holds the FFmpeg WebAssembly core that the free audio tools on ownvox.app use to convert files the browser cannot decode (AMR voice notes, some iPhone `.mov`, `.webm`) into 16 kHz mono WAV before Whisper runs. It replaces the npm package `@ffmpeg/core` (GPL-2.0-or-later, bundles libx264, about 31 MB) with a build that contains only audio demuxers and decoders, the `wav` muxer and the `pcm_s16le` encoder, and no external codec library at all. FFmpeg's configure therefore reports **License: LGPL version 2.1 or later** (see `build-ffmpeg.log` excerpt below). The JavaScript API is unchanged: the `@ffmpeg/ffmpeg` wrapper from npm loads this core exactly like the upstream one. ## Files | File | Purpose | | --- | --- | | `ffmpeg-core.js` | Emscripten glue (ES module), loaded by the `@ffmpeg/ffmpeg` worker | | `ffmpeg-core.wasm` | The FFmpeg binary | | `SHA256SUMS` | Checksums of the two files above, as produced in the build container | | `LICENSE` | GNU LGPL 2.1 (FFmpeg's `COPYING.LGPLv2.1`) | | `BUILD.md` | This recipe. `scripts/copy-ffmpeg-core.mjs` publishes it as `/ffmpeg/BUILD.txt` | ## Sources | Component | Version | Source | | --- | --- | --- | | FFmpeg | `n5.1.4` (commit `4729204c17f756e186d622060088371d10b34f7e`) | https://github.com/FFmpeg/FFmpeg/tree/n5.1.4 | | ffmpeg.wasm toolchain (patched `fftools`, JS bindings, build scripts) | tag `v12.15` (commit `71aa99d37c02a7b4c435275ca9ef50e612f6efa1`) | https://github.com/ffmpegwasm/ffmpeg.wasm/tree/v12.15 | | Emscripten | 3.1.40 (`emscripten/emsdk:3.1.40`, image digest `sha256:c1e807a6e03ac5bd5b37bae2ace3c46c08579e2ddeb951037a3b8dac7067f2cc`) | https://github.com/emscripten-core/emsdk | FFmpeg itself is unmodified. The only patched sources are ffmpeg.wasm's `src/fftools/*` (the `ffmpeg`/`ffprobe` command-line front ends turned into callable functions) and `src/bind/ffmpeg/bind.js`, both taken verbatim from the toolchain tag above. ## How it was built Everything runs inside Docker (linux/amd64; on Apple silicon via emulation, which is slow but deterministic). From a checkout of the ffmpeg.wasm toolchain at tag `v12.15`, add the four files under `audio/` reproduced below and run: ```bash git clone https://github.com/ffmpegwasm/ffmpeg.wasm.git cd ffmpeg.wasm && git checkout v12.15 mkdir audio # then create the four files listed below docker buildx build --progress=plain \ --build-arg EXTRA_CFLAGS="-O3 -msimd128" \ --build-arg EXTRA_LDFLAGS="" \ --build-arg FFMPEG_ST=yes \ -f audio/Dockerfile -o audio/out . # results: audio/out/dist/esm/ffmpeg-core.{js,wasm}, audio/out/dist/SHA256SUMS, # build logs, COPYING.LGPLv2.1, FFMPEG_COMMIT ``` `EXTRA_CFLAGS="-O3 -msimd128"` and `FFMPEG_ST=yes` are the toolchain's own production single-thread settings (`make prd` in its Makefile). Single thread on purpose: the core then needs no SharedArrayBuffer and works in Safari. ### `audio/Dockerfile` Derived from the toolchain's `Dockerfile`: the same `emsdk-base` stage, but no x264/x265/libvpx/lame/ogg/vorbis/opus/theora/webp/freetype/fribidi/harfbuzz/ libass/zimg stages and no SDL2 port, because nothing here links them. ```dockerfile # syntax=docker/dockerfile:1 # Audio-only, LGPL-only single-thread ffmpeg.wasm core for ownvox.app. # Derived from ffmpegwasm/ffmpeg.wasm tag v12.15 (Dockerfile + build/ffmpeg.sh # + build/ffmpeg-wasm.sh). No external codec libraries at all: every decoder # is FFmpeg's own LGPL implementation (aac, mp3, opus, vorbis, flac, alac, # amrnb, amrwb, ac3, wma, pcm/adpcm), plus the wav muxer and pcm_s16le. FROM --platform=linux/amd64 emscripten/emsdk:3.1.40 AS emsdk-base ARG EXTRA_CFLAGS ARG EXTRA_LDFLAGS ARG FFMPEG_ST ENV INSTALL_DIR=/opt ENV FFMPEG_VERSION=n5.1.4 ENV CFLAGS="-I$INSTALL_DIR/include $CFLAGS $EXTRA_CFLAGS" ENV CXXFLAGS="$CFLAGS" ENV LDFLAGS="-L$INSTALL_DIR/lib $LDFLAGS $CFLAGS $EXTRA_LDFLAGS" ENV FFMPEG_ST=$FFMPEG_ST RUN apt-get update && apt-get install -y pkg-config FROM emsdk-base AS ffmpeg-builder RUN git clone --depth 1 --branch $FFMPEG_VERSION https://github.com/FFmpeg/FFmpeg.git /src WORKDIR /src COPY audio/ffmpeg.sh /src/build.sh COPY audio/configure-flags.txt /src/configure-flags.txt RUN bash -x /src/build.sh $(cat /src/configure-flags.txt) 2>&1 | tee /src/build-ffmpeg.log RUN grep -n "^License:" /src/build-ffmpeg.log && grep -q "^License: LGPL version 2.1 or later" /src/build-ffmpeg.log FROM ffmpeg-builder AS ffmpeg-wasm-builder COPY src/bind /src/src/bind COPY src/fftools /src/src/fftools COPY audio/ffmpeg-wasm.sh /src/build-wasm.sh RUN mkdir -p /src/dist/esm && bash -x /src/build-wasm.sh \ -sEXPORT_ES6 \ -o dist/esm/ffmpeg-core.js 2>&1 | tee /src/build-wasm.log RUN cp /src/build-ffmpeg.log /src/build-wasm.log /src/configure-flags.txt /src/COPYING.LGPLv2.1 /src/dist/ && \ git -C /src rev-parse HEAD > /src/dist/FFMPEG_COMMIT && \ cd /src/dist/esm && sha256sum ffmpeg-core.js ffmpeg-core.wasm > /src/dist/SHA256SUMS && ls -l FROM scratch AS exportor COPY --from=ffmpeg-wasm-builder /src/dist /dist ``` ### `audio/configure-flags.txt` Appended to the toolchain's fixed flags in `build/ffmpeg.sh` (`--target-os=none --arch=x86_32 --enable-cross-compile --disable-asm --disable-stripping --disable-programs --disable-doc --disable-debug --disable-runtime-cpudetect --disable-autodetect`, the em* toolchain overrides, and `--disable-pthreads --disable-w32threads --disable-os2threads` from `FFMPEG_ST`): ``` --disable-everything --disable-gpl --disable-nonfree --disable-version3 --disable-postproc --disable-network --enable-protocol=file --enable-demuxer=mov,matroska,ogg,amr,wav,mp3,aac,flac,asf,avi,mpegts,ac3,eac3,aiff,caf,loas --enable-decoder=aac,aac_latm,mp1float,mp2float,mp3float,opus,vorbis,flac,alac,amrnb,amrwb,ac3,eac3,wmav1,wmav2,gsm,gsm_ms,adpcm_ima_wav,adpcm_ima_qt,adpcm_ms,pcm_alaw,pcm_mulaw,pcm_u8,pcm_s8,pcm_s16le,pcm_s16be,pcm_s24le,pcm_s24be,pcm_s32le,pcm_s32be,pcm_f32le,pcm_f32be,pcm_f64le,pcm_f64be --enable-parser=aac,aac_latm,mpegaudio,opus,vorbis,flac,ac3,amr --enable-encoder=pcm_s16le --enable-muxer=wav --enable-filter=abuffer,abuffersink,aformat,anull,aresample,atrim,asetpts,apad,acopy,buffer,buffersink,format,null,scale,trim,setpts,hflip,vflip,transpose ``` Note on parsers: FFmpeg's raw demuxers (`amr`, `mp3`, `aac`, `flac`, `ac3`, `loas`) read fixed-size chunks and rely on the matching parser to cut them into frames. Without `--enable-parser=amr` an `.amr` file decoded to 0.18 s of audio and stopped (nine 1 KB chunks, one frame each); that is why every raw demuxer above has its parser listed. ### `audio/ffmpeg.sh` The toolchain's `build/ffmpeg.sh` with one change so the build fits in a small VM: ```diff -emmake make -j +emmake make -j${MAKE_JOBS:-2} ``` ### `audio/ffmpeg-wasm.sh` The toolchain's `build/ffmpeg-wasm.sh` (the emcc link step) with three lines removed, because libpostproc is GPL and not built and SDL2 is only used by ffplay, which is not compiled: ```diff - -Llibpostproc - -lpostproc - -sUSE_SDL=2 # use emscripten SDL2 lib port ``` ## What configure reported From `build-ffmpeg.log` (the full log is not committed; rebuilding reproduces it): ``` install prefix /usr/local source path . C compiler emcc C library host C compiler gcc host C library glibc ARCH c (generic) big-endian no runtime cpu detection no debug symbols no strip symbols no optimize for size no optimizations yes static yes shared no postprocessing support no network support no threading support no safe bitstream reader yes texi2html enabled no perl enabled yes pod2man enabled yes makeinfo enabled no makeinfo supports HTML no xmllint enabled no External libraries: iconv External libraries providing hardware acceleration: Libraries: avcodec avformat swscale avdevice avutil avfilter swresample Programs: Enabled decoders: aac gsm_ms pcm_s16le aac_latm mp1float pcm_s24be ac3 mp2float pcm_s24le adpcm_ima_qt mp3float pcm_s32be adpcm_ima_wav opus pcm_s32le adpcm_ms pcm_alaw pcm_s8 alac pcm_f32be pcm_u8 amrnb pcm_f32le vorbis amrwb pcm_f64be wmav1 eac3 pcm_f64le wmav2 flac pcm_mulaw gsm pcm_s16be Enabled encoders: pcm_s16le Enabled hwaccels: Enabled parsers: aac amr opus aac_latm flac vorbis ac3 mpegaudio Enabled demuxers: aac caf mp3 ac3 eac3 mpegts aiff flac ogg amr loas wav asf matroska avi mov Enabled muxers: wav Enabled protocols: file Enabled filters: acopy asetpts scale aformat atrim setpts anull format transpose apad hflip trim aresample null vflip Enabled bsfs: Enabled indevs: Enabled outdevs: License: LGPL version 2.1 or later ``` ## Result | | `@ffmpeg/core` 0.12.10 | this build | | --- | --- | --- | | Licence | GPL-2.0-or-later | LGPL-2.1-or-later | | `ffmpeg-core.wasm` | about 32.2 MB | 2,724,150 bytes (2.7 MB) | | `ffmpeg-core.js` | about 0.1 MB | 85,115 bytes | ``` 842fd2b68e5da0713589d688ac94e52c0ef650bfe9431030f13ecc9fb9cbff89 ffmpeg-core.js d5c7e76473f25c6482c3996cd23c0b77360d24a7a94252194b7481e43f8b4e94 ffmpeg-core.wasm ``` Build time on the machine that produced this: 14 min 11 s (2026-09-20; configure about 8.5 min, make about 4.5 min, link about 1 min) (Colima VM, 2 CPUs, 2 GiB, amd64 emulated with QEMU; a native amd64 host is many times faster). ## Updating 1. Rebuild as above (change `audio/configure-flags.txt` for new formats). 2. Replace `ffmpeg-core.js`, `ffmpeg-core.wasm` and `SHA256SUMS` here, update the tables in this file. 3. Bump `CORE_VERSION` in `src/lib/ffmpeg/convert.ts` (the files keep their names and `/ffmpeg/*` is cached for a week). 4. If the size changes, update the offer copy in `src/messages/*.json` (the FAQ answers quote it), `CONVERTER_SIZE_MB` in `convert.ts`, and privacy policy section 11.1 (`de` first, then the translations; bump its version).