Why use FFmpeg with Node.js?

FFmpeg is a cross‑platform command‑line toolkit for decoding, encoding, transcoding, muxing, streaming, and filtering audio & video. Pairing it with Node.js lets you build:

Note: the latest stable FFmpeg release is 7.1.1 “Péter” (2025‑03‑03).

Installing the FFmpeg binary

2 · 1 System packages

$ # macOS
$ brew install ffmpeg --with-sdl2 --with-fdk-aac

$ # Debian/Ubuntu
$ sudo apt install ffmpeg

$ # Windows (scoop/chocolatey)
$ scoop install ffmpeg

2 · 2 Bundling with npm

$ npm i ffmpeg-static ffprobe-static

Tip: shipping your own binary guarantees identical behaviour across hosts & CI.

Choosing a Node library

3 · 1 Raw child_process

Use Node’s spawn/execFile to run FFmpeg exactly as on the CLI.


import { spawn } from 'node:child_process';

const ff = spawn('ffmpeg', ['-i', 'in.mp4', '-c:v', 'libx264', 'out.mp4']);

ff.stderr.on('data', d => process.stderr.write(d));   // progress
ff.on('close', code => console.log('exit', code));

3 · 2 fluent-ffmpeg – fluent API

Most popular wrapper; weekly downloads > 1 M, maintained on GitHub.


import ffmpeg from 'fluent-ffmpeg';
ffmpeg('in.mov')
  .outputOptions('-c:v libx264', '-preset veryfast')
  .output('out.mp4')
  .on('progress', p => console.log(p.percent + '%'))
  .on('end',    () => console.log('done'))
  .run();

3 · 3 @ffmpeg/ffmpeg – WASM

Pure‑JS/WebAssembly build that runs in Node and browsers—no native binary required. Latest v0.12.15 (2025‑01).

3 · 4 ffmpeg-kit

Native wrapper for React‑Native / Electron; useful when you need mobile & desktop parity.

3 · 5 Other ecosystem tools

Every‑day tasks (recipes)

4 · 1 Get media metadata with ffprobe


import ffprobe from 'ffprobe-static';
import { promisify } from 'node:util';
import { execFile } from 'node:child_process';

const probe = promisify(execFile);
const { stdout } = await probe(ffprobe.path, ['-v', 'quiet', '-print_format', 'json', '-show_format', '-show_streams', 'in.mp4']);
console.log(JSON.parse(stdout));

4 · 2 Transcode video


ffmpeg('in.mp4')
  .videoCodec('libvpx-vp9')
  .audioCodec('libopus')
  .format('webm')
  .save('out.webm');

4 · 3 Extract audio


ffmpeg('in.mov')
  .noVideo()
  .audioCodec('aac')
  .save('song.m4a');

4 · 4 Clip segment


ffmpeg('talk.mp4')
  .setStartTime('00:02:00')
  .setDuration(90)      // seconds
  .save('highlight.mp4');

4 · 5 Generate thumbnail


ffmpeg('in.mp4')
  .screenshots({ count: 1, filename: 'thumb.png', folder: './' });

Advanced filters & streaming

5 · 1 Complex filter graphs


ffmpeg('a.mp4')
  .input('logo.png')
  .complexFilter([
    '[0:v]scale=1280:-2[main]',
    '[1:v]scale=200:-1[logo]',
    '[main][logo]overlay=W-w-10:H-h-10'
  ])
  .videoCodec('libx264')
  .audioCodec('copy')
  .outputOptions('-preset fast')
  .save('watermarked.mp4');

5 · 2 Piping streams (no temp files)


import { createReadStream, createWriteStream } from 'node:fs';
import ffmpegPath from 'ffmpeg-static';
import { spawn } from 'node:child_process';

const ff = spawn(ffmpegPath, [
	'-i', 'pipe:0',            // stdin
	'-vf', 'scale=640:-2',
	'-f', 'mp4',
	'pipe:1'                   // stdout
]);
createReadStream('big.mp4').pipe(ff.stdin);
ff.stdout.pipe(createWriteStream('small.mp4'));

5 · 3 Live RTMP ➜ HLS

ffmpeg -i rtmp://ingest/stream \
       -c:v libx264 -preset veryfast -g 50 -sc_threshold 0 \
       -c:a aac -b:a 128k \
       -f hls -hls_time 4 -hls_list_size 5 -hls_flags delete_segments \
       public/stream.m3u8

5 · 4 Hardware acceleration

Performance & scaling

6 · 1 Progress & stderr parsing


ffmpeg('in.mp4')
  .addOption('-progress', 'pipe:2')
  .on('stderr', line => {
    const m = /frame=\s*(\d+)/.exec(line);
    if (m) console.log('frame', m[1]);
  })
  .save('out.mp4');

6 · 2 Job queues & Worker Threads

Use bullmq, piscina or Node’s built‑in Worker to isolate CPU‑heavy encodes.

6 · 3 Parallelism tips

Packaging & deployment

7 · 1 Docker


FROM node:20-bullseye
RUN apt-get update && \
    apt-get install -y ffmpeg && \
    npm i --global pnpm
WORKDIR /app
COPY . .
RUN pnpm i --prod
CMD ["node","index.js"]

7 · 2 Serverless & edge

Traditional serverless functions have short CPU limits—prefer pre‑processing, WASM builds (@ffmpeg/ffmpeg), or queue out to a container‑based worker (e.g., AWS Fargate).

Debugging cheatsheet

# Unknown encoder
ffmpeg -encoders | grep h264

# See compiled‑in libraries / configuration
ffmpeg -buildconf

# Validate corrupted input
ffprobe -v error -show_error -i bad.mp4

# Visually inspect filtergraph
ffmpeg -i in.mp4 -vf "drawbox=..." ...

Resources