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).
$ # macOS
$ brew install ffmpeg --with-sdl2 --with-fdk-aac
$ # Debian/Ubuntu
$ sudo apt install ffmpeg
$ # Windows (scoop/chocolatey)
$ scoop install ffmpeg
ffmpeg-static
– pre‑compiled binaries for Linux / macOS / Windowsffprobe-static
– companion for metadata@ffmpeg-installer/ffmpeg
– lightweight wrapper$ npm i ffmpeg-static ffprobe-static
Tip: shipping your own binary guarantees identical behaviour across hosts & CI.
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));
fluent-ffmpeg
– fluent APIMost 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();
@ffmpeg/ffmpeg
– WASMPure‑JS/WebAssembly build that runs in Node and browsers—no native binary required. Latest v0.12.15 (2025‑01).
ffmpeg-kit
Native wrapper for React‑Native / Electron; useful when you need mobile & desktop parity.
ezffmpeg
– goal: opinionated simple API (new in 2025)ffmpeg-concat
, fluent-ffmpeg-extended
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));
ffmpeg('in.mp4')
.videoCodec('libvpx-vp9')
.audioCodec('libopus')
.format('webm')
.save('out.webm');
ffmpeg('in.mov')
.noVideo()
.audioCodec('aac')
.save('song.m4a');
ffmpeg('talk.mp4')
.setStartTime('00:02:00')
.setDuration(90) // seconds
.save('highlight.mp4');
ffmpeg('in.mp4')
.screenshots({ count: 1, filename: 'thumb.png', folder: './' });
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');
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'));
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
-c:v h264_nvenc
-c:v av1_amf
-c:v h264_videotoolbox
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');
Use bullmq
, piscina
or Node’s built‑in Worker to isolate CPU‑heavy encodes.
-preset ultrafast
for preview clips-c copy
when possible
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"]
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).
# 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=..." ...
node-fluent-ffmpeg
)ffmpeg -filters