npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ffmpeg-wasm-farm

v0.3.0

Published

Multicore browser transcoding by farming independent FFmpeg.wasm segments across Web Workers.

Readme

ffmpeg-wasm-farm

A multicore browser transcoder built on the single-thread @ffmpeg/core. It gets parallelism by transcoding independent, keyframe-aligned segments in separate FFmpeg Web Workers, then concatenating them in source order.

This is intentionally different from @ffmpeg/core-mt: it does not use SharedArrayBuffer, Emscripten pthreads, COOP, or COEP.

What is included

  • A strict TypeScript library with cancellation, progress, structured logs, argument validation, and collision-safe virtual filenames.
  • A responsive browser demo in demo/ with drag and drop, presets, progress, cancellation, result preview, diagnostics, and matching light/dark themes.
  • Unit, integration, cancellation, concurrency, command-topology, and native FFmpeg smoke tests.

Install

npm install ffmpeg-wasm-farm @ffmpeg/ffmpeg @ffmpeg/core @ffmpeg/util

Library usage

import { FFmpeg } from "@ffmpeg/ffmpeg";
import { toBlobURL } from "@ffmpeg/util";
import { ParallelFFmpeg } from "ffmpeg-wasm-farm";

const baseURL = "https://cdn.jsdelivr.net/npm/@ffmpeg/[email protected]/dist/esm";
const loadConfig = {
  coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, "text/javascript"),
  wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, "application/wasm"),
};

const farm = new ParallelFFmpeg(() => new FFmpeg());
const controller = new AbortController();

const result = await farm.transcode(file, {
  inputName: file.name,
  outputName: "output.mp4",
  workerCount: ParallelFFmpeg.recommendedWorkerCount(file.size),
  segmentSeconds: 12,
  // Optional hard stop before allocating FFmpeg heaps for unexpectedly large uploads.
  maxInputBytes: 2 * 1024 ** 3,
  // "auto" spills intermediate segment buffers to OPFS where supported.
  intermediateStorage: "auto",

  // Encode audio once to avoid codec priming delay at every segment boundary.
  audioStrategy: "single-pass",
  audioArgs: ["-c:a", "aac", "-b:a", "128k"],

  // Applied independently to each video segment.
  encodingArgs: [
    "-c:v", "libx264",
    "-preset", "veryfast",
    "-crf", "23",
    "-pix_fmt", "yuv420p",
  ],
  muxArgs: ["-movflags", "+faststart"],
  loadConfig,
  signal: controller.signal,
  onProgress: ({ stage, ratio, completedSegments, totalSegments }) => {
    console.log(stage, Math.round(ratio * 100), `${completedSegments}/${totalSegments}`);
  },
  onLog: ({ scope, worker, message }) => {
    console.debug(scope, worker, message);
  },
});

const outputURL = URL.createObjectURL(
  new Blob([result.data.slice().buffer], { type: "video/mp4" }),
);

Calling controller.abort() rejects the operation with an AbortError and terminates active FFmpeg workers.

Run the demo app

npm install
npm run demo

Production build:

npm run demo:build

The demo uses a single token-based theme for every tab, card, dialog state, log view, and responsive layout. It remembers the selected light/dark theme locally.

Choosing concurrency

Start with two to four workers. Each active worker owns a full FFmpeg WebAssembly heap, so matching a high-end desktop's full logical CPU count can exhaust browser memory. Automatic concurrency is capped at four, leaves one logical core for the browser, and becomes more conservative as input size grows. Chromium's navigator.deviceMemory, when exposed, is used as an additional safety signal.

Intermediate source/audio/encoded buffers use intermediateStorage: "auto" by default. The farm spills them to the Origin Private File System (OPFS) when available, then releases each entry as soon as the next FFmpeg stage takes ownership. If OPFS is unavailable or a default-mode write fails, it transparently falls back to the previous in-memory behavior. Use "memory" to force the legacy path or "opfs" to require spill storage. This substantially lowers JavaScript heap retention but cannot remove planner/assembler MEMFS high-water marks inside ffmpeg.wasm itself.

Useful segment sizes are usually 8–20 seconds. Tiny segments increase startup and mux overhead; very large segments reduce load balancing. User-provided workerCount values are limited to 16 as a final guard against accidental memory exhaustion. Applications handling untrusted uploads can also set maxInputBytes so oversized inputs fail before the FFmpeg core is loaded or the input is copied.

Audio strategies

  • single-pass: probe and encode audio once, transcode video segments in parallel, then mux them together. Best default for MP4 and WebM. The final mux preserves relative A/V stream start offsets as well as the full video duration; add "-shortest" to muxArgs only when deliberate truncation to the shorter stream is wanted.
  • per-segment: keep audio in each segment. Audio is stream-copied by default unless the encoding arguments explicitly request audio processing.
  • drop: produce video without audio.

Safety and correctness guards

  • Anonymous byte inputs are sniffed for common container signatures. Unknown inputs must provide inputName.
  • Filenames are sanitized and leading dashes are neutralized so FFmpeg cannot interpret them as options.
  • Every run uses a unique internal prefix, preventing collisions with source and output filenames.
  • Managed FFmpeg options such as extra inputs, stream maps, output formats, and final codecs are rejected in the wrong argument group.
  • Runtime callers receive clear errors for malformed argument arrays, invalid audio strategies, unsafe worker counts, and contradictory single-pass audio flags.
  • Caller-owned option arrays are snapshotted before asynchronous work, so mid-run mutations cannot make segments use different codecs or filters.
  • Overall progress is monotonic even when individual worker progress events reset.
  • Recent FFmpeg output is attached to nonzero-exit errors.
  • Observer callback failures are reported without discarding an otherwise successful export.

What parallelizes well

Independent video transcodes, resizing, pixel-format conversion, overlays that do not depend on global timeline state, and per-segment filters.

What does not

Two-pass encoding, exact whole-file bitrate allocation, filters whose state must flow across segment boundaries, commands with multiple external inputs, and lossless preservation of inter-segment codec state.

Browser and bundler notes

Use the single-thread @ffmpeg/core asset, not @ffmpeg/core-mt. Host core files on your own origin in production when possible. The FFmpeg class already runs each core in a dedicated worker; creating several instances creates the process-level worker pool.

The FFmpeg progress event is best-effort. Segment counts and stage transitions remain useful even when codec progress is not exact.

Development

npm run check
bash tests/real-ffmpeg-smoke.sh

GitHub Actions configuration is included for Node.js 20/22/24 CI, native FFmpeg smoke testing, npm package artifacts, Dependabot updates, and tag-driven npm/GitHub releases. See RELEASING.md for the one-time setup and release procedure.

The package has no runtime dependency on a concrete FFmpeg class. Its FFmpeg peer packages are optional, and it accepts a structural factory so applications control the exact build, asset URLs, and bundling strategy.

License

MIT. See LICENSE.