Lightweight FFmpeg and FFprobe for Node, built as modern WebAssembly for local media automation.
This repository is intentionally small in scope: one reproducible Emscripten build, one TypeScript wrapper, and enough codecs/protocols for media inspection, audio extraction, thumbnails, pipe I/O, and segmentation.
Many media workflows need predictable FFmpeg and FFprobe behavior without carrying a full native FFmpeg bundle. This package builds a narrow LGPL FFmpeg wasm core and exposes it through:
ffmpeg-wasm, a CLI-compatible FFmpeg entrypoint.ffprobe-wasm, a CLI-compatible FFprobe entrypoint.- TypeScript APIs for binary-safe buffered or streaming execution.
The wrapper, scripts, and documentation in this repository are MIT licensed.
Generated FFmpeg assets in dist/ are copied from FFmpeg and are LGPL-2.1-or-later. The build does not pass --enable-gpl or --enable-nonfree. FFmpeg license files are copied into dist/ during pnpm build.
Keep wrapper code and generated FFmpeg binaries conceptually separate when this moves under OpenClaw packaging. A downstream package can stay MIT for the wrapper while distributing the FFmpeg wasm artifacts under the LGPL terms that apply to FFmpeg.
Default refs:
- FFmpeg:
n8.1.1 - LAME:
masterfromffmpegwasm/lame - Runtime: Node 24+
- Compiler: Emscripten via
emcc
Enabled programs:
ffmpegffprobe
Enabled protocols:
datafdfilepipe
Enabled demuxers:
aacflachlsimage2matroskamovmp3mpegtsoggwav
Enabled muxers:
image2movmp4mp3nullrawvideosegmentwav
Enabled decoders:
aacflach263h264hevcmpeg4mjpegmp3opuspcm_s16lepngvorbisvp8vp9
Enabled encoders:
h263libmp3lamempeg4pcm_s16lepngrawvideowrapped_avframe
Enabled filters:
aformataresampleformatmetadatanullscaleselectshowinfosignalstats
External libraries:
libmp3lamezlib
The verified dist/ size is about 8.1 MB on current builds.
pnpm installRequired system tools for a full wasm build:
- Emscripten SDK with
emcc,em++,emar, andemranlibonPATH - Autotools for LAME
make,pkg-config,nasm,yasm- Native
ffmpegforpnpm verify
On macOS:
brew install autoconf automake ffmpeg libtool nasm pkg-config yasmpnpm build
pnpm docs:build
pnpm playground
pnpm playground:e2e
pnpm verify
pnpm test:e2e
pnpm checkpnpm build compiles TypeScript, fetches the configured FFmpeg/LAME refs into .cache/, builds static LAME, builds FFmpeg/FFprobe wasm, and writes generated assets to dist/.
pnpm docs:build builds the static site into dist/docs-site: the media workbench at /, its compiled TypeScript client and browser ffmpac worker, the browser wasm bundle, and documentation under /docs/.
pnpm playground starts a local one-page media bench at http://127.0.0.1:4173. It lets you load a video, choose a supported preset, inspect the generated FFmpeg args, render through the wasm wrapper, preview the output inline, and save via the browser file picker or download fallback.
pnpm playground:e2e starts the playground on a temporary port, launches Chrome through DevTools, loads the sample video, renders a smaller MP4 and an MP3, and writes .tmp/playground-e2e.png for visual proof. Set PLAYGROUND_E2E_STATIC=1 to test the static dist/docs-site workbench with /api/* blocked and only browser ffmpac available.
pnpm verify creates a tiny native test video, then exercises FFprobe text and JSON output, WAV/MP3 extraction, stdin pipe input, stdout pipe output, PNG frame output, rawvideo byte equality, segmentation, cwd/dist overrides, API validation failures, and CLI success/failure paths.
pnpm test:e2e rebuilds the wasm assets from source, then runs the same live verifier against the generated FFmpeg/FFprobe wrappers.
pnpm check runs tsgo, strict oxlint, and oxfmt --check.
After pnpm build, use the compiled entrypoints directly:
node lib/src/ffprobe-cli.js -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 input.mp4
node lib/src/cli.js -hide_banner -loglevel error -i input.mp4 -vn -ac 1 -ar 16000 audio.wav
node lib/src/ffprobe-cli.js -v error -show_format input.mp4
node lib/src/cli.js -hide_banner -i input.mp4 -frames:v 1 frame.png
node lib/src/cli.js -hide_banner -ss 0 -i input.mp4 -t 5 -map 0:v:0 -map 0:a? -c copy -movflags +faststart clip.mp4The package also declares ffmpeg-wasm and ffprobe-wasm bin entries for downstream package-manager linking.
import { execFfmpeg, runFfmpeg, runFfprobe } from "@steipete/ffmpeg-wasm-local";
const probe = await runFfprobe([
"-v",
"error",
"-show_entries",
"format=duration",
"-of",
"default=noprint_wrappers=1:nokey=1",
"input.mp4",
]);
if (probe.exitCode !== 0) throw new Error(probe.stderrText);
const wav = await runFfmpeg([
"-hide_banner",
"-loglevel",
"error",
"-i",
"input.mp4",
"-vn",
"-ac",
"1",
"-ar",
"16000",
"-f",
"wav",
"-",
]);
await execFfmpeg(["-hide_banner", "-i", "input.mp4", "audio.mp3"]);runFfmpeg and runFfprobe buffer stdout/stderr and return Buffer fields plus UTF-8 text helpers. Use these for probes, small generated files, and tests.
execFfmpeg and execFfprobe stream stdio and return only the exit code. Use these for CLI-style work or larger outputs.
Both APIs accept:
distDirto point at a custom generated asset directory.cwdandenvfor process isolation.stdinfor pipe input.timeoutMsfor opt-in time limits.
ffmpeg receives -nostdin automatically unless the caller already supplied it. Explicit -i - stdin input still works through the wrapper.
Build once:
pnpm buildThen point a downstream tool at the compiled wrappers:
FFMPEG_PATH="$PWD/lib/src/cli.js" \
FFPROBE_PATH="$PWD/lib/src/ffprobe-cli.js" \
your-tool ...For direct package usage, import the TypeScript API and keep dist/ next to the compiled lib/ tree.
Override source refs per build:
FFMPEG_VERSION=n8.1.1 LAME_REF=master pnpm buildTo keep the binary small, only add codecs, demuxers, muxers, filters, or protocols when a real caller needs them. Prefer adding one capability and extending scripts/verify.ts with a matching proof.
Useful places:
playground/: one-page local editor assets, TypeScript client, and browser ffmpac worker.docs/: Cloudflare Pages source docs and screenshot assets.scripts/build.ts: configure flags and Emscripten linker flags.scripts/build-docs-site.ts: static docs site builder.scripts/playground-server.ts: local editor server and render endpoints.scripts/verify.ts: behavioral coverage for the generated wasm..oxlintrc.json: strict lint policy.
GitHub Actions runs two jobs:
- TypeScript, lint, and format on Node 24.
- Static docs site build.
- Full live wasm E2E with Emscripten, server and static browser workbench tests, build caching, and
dist/artifact upload.
CI intentionally builds from source instead of trusting checked-in wasm output. dist/ is ignored and regenerated.
