Skip to content

openclaw/ffmpeg-wasm

Repository files navigation

ffmpeg-wasm local

CI

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.

Media Bench playground

Why

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.

License

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. Linked libvpx code is BSD-licensed. The build does not pass --enable-gpl or --enable-nonfree. FFmpeg and libvpx 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.

Build Surface

Default refs:

  • FFmpeg: n8.1.1
  • LAME: 2badea1974ae36cb8312afe99cff1e6b3b5decee from ffmpegwasm/lame
  • libvpx: v1.16.0
  • Runtime: Node 24+
  • Compiler: Emscripten via emcc

Enabled programs:

  • ffmpeg
  • ffprobe

Enabled protocols:

  • data
  • fd
  • file
  • pipe

Enabled demuxers:

  • aac
  • flac
  • hls
  • image2
  • matroska
  • mov
  • mp3
  • mpegts
  • ogg
  • wav

Enabled muxers:

  • image2
  • mov
  • mp4
  • mp3
  • null
  • rawvideo
  • segment
  • wav
  • webm

Enabled decoders:

  • aac
  • flac
  • h263
  • h264
  • hevc
  • mpeg4
  • mjpeg
  • mp3
  • opus
  • pcm_s16le
  • png
  • vorbis
  • vp8
  • vp9
  • wrapped_avframe

Enabled encoders:

  • h263
  • libmp3lame
  • libvpx (VP8)
  • mpeg4
  • opus
  • pcm_s16le
  • png
  • rawvideo
  • wrapped_avframe

Enabled filters:

  • aformat
  • aresample
  • format
  • metadata
  • null
  • scale
  • select
  • showinfo
  • signalstats

External libraries:

  • libmp3lame
  • libvpx
  • zlib

Each generated Node or browser FFmpeg/FFprobe bundle is about 8.5 MB on current builds.

Install

pnpm install

Required system tools for a full wasm build:

  • Emscripten SDK with emcc, em++, emar, and emranlib on PATH
  • Autotools for LAME
  • make, pkg-config, nasm, yasm

On macOS:

brew install autoconf automake libtool nasm pkg-config yasm

Commands

pnpm build
pnpm docs:build
pnpm playground
pnpm playground:e2e
pnpm verify
pnpm test:e2e
pnpm check

pnpm build compiles TypeScript, fetches the configured FFmpeg/LAME/libvpx refs into .cache/, builds static LAME and libvpx, 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-server.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; that mode writes .tmp/playground-e2e-static.png.

pnpm verify creates a tiny test video through the wasm build with external executable lookup disabled, then exercises FFprobe text and JSON output, API and CLI MP4/MP3 conversions, codec and dimension assertions, stdin pipe input, stdout pipe output, PNG frame output, rawvideo byte equality, segmentation, cwd/dist overrides, API validation failures, and CLI failure paths. Set FFMPEG_WASM_VERIFY_OUTPUT_DIR to retain the converted files and JSON manifest.

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.

CLI

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.mp4

The package also declares ffmpeg-wasm and ffprobe-wasm bin entries for downstream package-manager linking.

TypeScript API

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:

  • distDir to point at a custom generated asset directory.
  • cwd and env for process isolation.
  • stdin for pipe input.
  • timeoutMs for opt-in time limits.

ffmpeg receives -nostdin automatically unless the caller already supplied it. Explicit -i - stdin input still works through the wrapper.

Downstream Wiring

Build once:

pnpm build

Then 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.

Build Tuning

Override source refs per build:

FFMPEG_VERSION=n8.1.1 LAME_REF=2badea1974ae36cb8312afe99cff1e6b3b5decee LIBVPX_REF=v1.16.0 pnpm build

To 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.

CI

GitHub Actions runs two jobs:

  • TypeScript, lint, and format on Node 24.
  • Static docs site build.
  • Full live wasm E2E with Emscripten, explicit API and CLI media conversions, codec/dimension assertions, native executable auditing, server and static browser workbench tests, build caching, and proof artifact uploads.

CI intentionally builds from source instead of trusting checked-in wasm output. dist/ is ignored and regenerated. The wasm job uploads dist/, converted media, a verification manifest, and both browser screenshots for inspection.

About

Lightweight FFmpeg and FFprobe for Node, built as modern WebAssembly for local media automation.

Resources

License

Security policy

Stars

Watchers

Forks

Releases

No releases published

Contributors