Native Node.js bindings for FFmpeg with full TypeScript support - including type-safe options (autocomplete + validation) for every codec, format, filter, and bitstream filter, generated from FFmpeg's own metadata. Provides direct access to FFmpeg's C APIs through N-API. Includes both raw FFmpeg bindings for full control and higher-level abstractions. Automatic resource management via Disposable pattern, hardware acceleration support and prebuilt binaries for Windows, Linux, and macOS.
Direct access to FFmpeg's C APIs with minimal abstractions. Perfect when you need full control over FFmpeg functionality.
import { AVERROR_EOF, AVMEDIA_TYPE_VIDEO } from'node-av/constants';
import { Codec, CodecContext, FFmpegError, FormatContext, Frame, Packet, Rational } from'node-av/lib';
// Open input fileawait using ifmtCtx = newFormatContext();
let ret = await ifmtCtx.openInput('input.mp4');
FFmpegError.throwIfError(ret, 'Could not open input file');
ret = await ifmtCtx.findStreamInfo();
FFmpegError.throwIfError(ret, 'Could not find stream info');
// Find video streamconst videoStreamIndex = ifmtCtx.findBestStream(AVMEDIA_TYPE_VIDEO);
const videoStream = ifmtCtx.streams?.[videoStreamIndex];
if (!videoStream) {
thrownewError('No video stream found');
}
// Create codecconst codec = Codec.findDecoder(videoStream.codecpar.codecId);
if (!codec) {
thrownewError('Codec not found');
}
// Allocate codec context for the decoder
using decoderCtx = newCodecContext();
decoderCtx.allocContext3(codec);
ret = decoderCtx.parametersToContext(videoStream.codecpar);
FFmpegError.throwIfError(ret, 'Could not copy codec parameters to decoder context');
// Open decoder context
ret = await decoderCtx.open2(codec, null);
FFmpegError.throwIfError(ret, 'Could not open codec');
// Process packets
using packet = newPacket();
packet.alloc();
using frame = newFrame();
frame.alloc();
while (true) {
let ret = await ifmtCtx.readFrame(packet);
if (ret < 0) {
break;
}
if (packet.streamIndex === videoStreamIndex) {
// Send packet to decoder
ret = await decoderCtx.sendPacket(packet);
if (ret < 0 && ret !== AVERROR_EOF) {
FFmpegError.throwIfError(ret, 'Error sending packet to decoder');
}
// Receive decoded frameswhile (true) {
const ret = await decoderCtx.receiveFrame(frame);
if (ret === AVERROR_EOF || ret < 0) {
break;
}
console.log(`Decoded frame ${frame.pts}, size: ${frame.width}x${frame.height}`);
// Process frame data...
}
}
packet.unref();
}
High-Level API
Higher-level abstractions for common tasks like decoding, encoding, filtering, and transcoding. Easier to use while still providing access to low-level details when needed.
import { Decoder, Demuxer, Encoder, HardwareContext, Muxer } from'node-av/api';
import { FF_ENCODER_LIBX264 } from'node-av/constants';
// Open Demuxerawait using input = awaitDemuxer.open('input.mp4');
// Get video streamconst videoStream = input.video()!;
// Optional, setup hardware acceleration
using hw = HardwareContext.auto();
// Create decoder
using decoder = awaitDecoder.create(videoStream, {
hardware: hw, // Optional, use hardware acceleration if available
});
// Create encoder
using encoder = awaitEncoder.create(FF_ENCODER_LIBX264, {
decoder, // Optional, copy settings from decoder
});
// Open Muxerawait using output = awaitMuxer.open('output.mp4', {
input, // Optional, used to copy global headers and metadata
});
// Add stream to outputconst outputIndex = output.addStream(encoder, {
inputStream: videoStream, // Optional, copy settings from input stream
});
// Create processing generatorsconst inputGenerator = input.packets(videoStream.index);
const decoderGenerator = decoder.frames(inputGenerator);
const encoderGenerator = encoder.packets(decoderGenerator);
// Process packetsforawait (using packet of encoderGenerator) {
await output.writePacket(packet, outputIndex);
}
// Done
Pipeline API
A simple way to chain together multiple processing steps like decoding, filtering, encoding, and muxing.
import { Decoder, Demuxer, Encoder, HardwareContext, Muxer, pipeline } from'node-av/api';
import { FF_ENCODER_LIBX264 } from'node-av/constants';
// Simple transcode pipeline: input ā decoder ā encoder ā output// Open Demuxerawait using input = awaitDemuxer.open('input.mp4');
// Get video streamconst videoStream = input.video()!;
// Optional, setup hardware acceleration
using hw = HardwareContext.auto();
// Create decoder
using decoder = awaitDecoder.create(videoStream, {
hardware: hw, // Optional, use hardware acceleration if available
});
// Create encoder
using encoder = awaitEncoder.create(FF_ENCODER_LIBX264, {
decoder, // Optional, copy settings from decoder
});
// Open Muxerawait using output = awaitMuxer.open('output.mp4', {
input, // Optional, used to copy global headers and metadata
});
const control = pipeline(input, decoder, encoder, output);
await control.completion;
Hardware Acceleration
The library supports all hardware acceleration methods available in FFmpeg. The specific hardware types available depend on your FFmpeg build and system configuration.
Auto-Detection
import { HardwareContext } from'node-av/api';
import { FF_ENCODER_LIBX264 } from'node-av/constants';
// Automatically detect best available hardwareconst hw = HardwareContext.auto();
console.log(`Using hardware: ${hw.deviceTypeName}`);
// Use with decoderconst decoder = awaitDecoder.create(stream, {
hardware: hw
});
// Use with encoder (use hardware-specific codec)const encoderCodec = hw?.getEncoderCodec('h264') ?? FF_ENCODER_LIBX264;
const encoder = awaitEncoder.create(encoderCodec, {
decoder,
});
Specific Hardware
import { AV_HWDEVICE_TYPE_CUDA, AV_HWDEVICE_TYPE_VAAPI } from'node-av/constants';
// Use specific hardware typeconst cuda = HardwareContext.create(AV_HWDEVICE_TYPE_CUDA);
const vaapi = HardwareContext.create(AV_HWDEVICE_TYPE_VAAPI, '/dev/dri/renderD128');
Stream Processing
From Files or Network
const media = awaitDemuxer.open('input.mp4');
// orconst media = awaitDemuxer.open('rtsp://example.com/stream');
Capture video, audio, and screen content directly from system devices. Platform-specific formats are handled automatically.
import { DeviceAPI } from'node-av/api';
// List devices and query capabilitiesconst devices = awaitDeviceAPI.list();
const modes = awaitDeviceAPI.modes(devices.find(d => d.type === 'video')!.name);
// Cameraawait using camera = awaitDeviceAPI.openCamera({ videoDevice: 0, width: 1280, height: 720, frameRate: 30 });
// Microphoneawait using mic = awaitDeviceAPI.openMicrophone({ audioDevice: 0, sampleRate: 48000, channels: 2 });
// Combined video + audio (macOS/Windows)await using device = awaitDeviceAPI.openDevice({ videoDevice: 0, audioDevice: 0, width: 1280, height: 720, frameRate: 30 });
// Screen captureawait using screen = awaitDeviceAPI.openScreen({ frameRate: 30, drawMouse: true });
// Screen capture with system audio (macOS 13.0+)await using screen2 = awaitDeviceAPI.openScreen({
frameRate: 30,
avfoundation: { captureSystemAudio: true, audioSampleRate: 48000, audioChannels: 2 },
});
Platform
Video
Audio
Screen
macOS
AVFoundation
AVFoundation
AVFoundation (ScreenCaptureKit)
Linux
V4L2
ALSA
x11grab
Windows
DirectShow
DirectShow
GDIGrab
FFmpeg Binary Access
Need direct access to the FFmpeg binary? The library provides an easy way to get FFmpeg binaries that automatically downloads and manages platform-specific builds.
import { ffmpegPath, isFfmpegAvailable } from'node-av/ffmpeg';
import { execFile } from'node:child_process';
import { promisify } from'node:util';
const execFileAsync = promisify(execFile);
// Check if FFmpeg binary is availableif (isFfmpegAvailable()) {
console.log('FFmpeg binary found at:', ffmpegPath());
// Use FFmpeg binary directlyconst { stdout } = awaitexecFileAsync(ffmpegPath(), ['-version']);
console.log(stdout);
} else {
console.log('FFmpeg binary not available - install may have failed');
}
// Direct usage exampleasyncfunctionconvertVideo(input: string, output: string) {
const args = [
'-i', input,
'-c:v', 'libx264',
'-crf', '23',
'-c:a', 'aac',
output
];
awaitexecFileAsync(ffmpegPath(), args);
}
The FFmpeg binary is automatically downloaded during installation from GitHub releases and matches the same build used by the native bindings.
Resource Management
The library supports automatic resource cleanup using the Disposable pattern:
// Automatic cleanup with 'using'
{
await using media = awaitDemuxer.open('input.mp4');
using decoder = awaitDecoder.create(media.video());
// Resources automatically cleaned up at end of scope
}
// Manual cleanupconst media = awaitDemuxer.open('input.mp4');
try {
// Process media
} finally {
await media.close();
}
Imports and Tree Shaking
The library provides multiple entry points for optimal tree shaking:
// High-Level API only - Recommended for most use casesimport { Demuxer, Muxer, Decoder, Encoder } from'node-av/api';
// Low-Level API only - Direct FFmpeg bindingsimport { FormatContext, CodecContext, Frame, Packet } from'node-av/lib';
// Constants only - When you just need FFmpeg constantsimport { AV_PIX_FMT_YUV420P, AV_CODEC_ID_H264 } from'node-av/constants';
// Channel layouts only - For audio channel configurationsimport { AV_CHANNEL_LAYOUT_STEREO, AV_CHANNEL_LAYOUT_5POINT1 } from'node-av/layouts';
// Default export - Includes everythingimport * as ffmpeg from'node-av';
Key Features
Beyond basic transcoding, NodeAV provides advanced media processing capabilities:
Fully Typed FFmpeg Options
Every FFmpeg option is typed - across codecs, formats, ~580 filters, and bitstream filters. Keys autocomplete, enum values are validated, and typos become compile-time errors, all generated directly from FFmpeg's AVOption metadata (including each option's description and a link to the FFmpeg docs as JSDoc). No more grepping the FFmpeg docs for flag names.
Speech Recognition with Whisper
Integrate automatic speech-to-text transcription using OpenAI's Whisper model through the whisper.cpp implementation. The library handles automatic model downloading from HuggingFace, supports multiple model sizes (tiny, base, small, medium, large) for different accuracy/performance tradeoffs, and provides hardware-accelerated inference through Metal (macOS), Vulkan (cross-platform), or OpenCL backends. Transcription results include precise timestamps and can be processed in real-time from any audio source.
Advanced Video Filtering with FilterComplexAPI
Build sophisticated video processing pipelines using FFmpeg's complete filter ecosystem. The FilterComplexAPI provides direct access to complex filtergraphs with multiple inputs and outputs, enabling advanced operations like picture-in-picture overlays, multi-stream composition (side-by-side, grid layouts), real-time video effects, and custom processing chains. All filters support hardware acceleration where available, and filter configurations can be dynamically constructed based on runtime requirements.
Browser Streaming
Stream any media source directly to web browsers through fragmented MP4 (fMP4) or WebRTC protocols. The library can process inputs from RTSP cameras, local files, network streams, or custom sources and package them for browser consumption with minimal latency. Complete examples demonstrate both Media Source Extensions (MSE) based playback for on-demand content and WebRTC integration for real-time streaming scenarios.
RTSP Backchannel / Talkback
Implements bidirectional RTSP communication for IP camera integration. The library provides native support for RTSP backchannel streams, enabling audio transmission to camera devices. Transport is handled automatically with support for both TCP (interleaved mode) and UDP protocols, with proper RTP packet formatting and stream synchronization.
Image Scaling & Snapshots
The Scaler turns decoded frames into raw pixel buffers (rgb/rgba/gray/nv12/yuv420p) or JPEG/PNG images for detection, thumbnail, and snapshot workloads. One reusable instance pools its swscale contexts, GPU filter graphs, and encoders for zero per-frame allocation; hardware frames are cropped/scaled on the GPU with only the small result downloaded. Recurring resolutions can also be served directly by EncoderPool.
See the Examples section for complete implementations.
Performance
NodeAV executes all media operations directly through FFmpeg's native C libraries. The Node.js bindings add minimal overhead - mostly just the JavaScript-to-C boundary crossings. During typical operations like transcoding or filtering, most processing time is spent in FFmpeg's optimized C code.
Benchmarks
Performance comparison with FFmpeg CLI (4K 60fps, 30s test files on Apple M3 Max):
Every async method in NodeAV has a corresponding synchronous variant with the Sync suffix:
Async methods (default) - Non-blocking operations using N-API's AsyncWorker. Methods like decode(), encode(), read(), packets() return Promises or AsyncGenerators.
Sync methods - Direct FFmpeg calls without AsyncWorker overhead. Same methods with Sync suffix: decodeSync(), encodeSync(), readSync(), packetsSync().
The key difference: Async methods don't block the Node.js event loop, allowing other operations to run concurrently. Sync methods block until completion but avoid AsyncWorker overhead, making them faster for sequential processing.
Memory Safety Considerations
NodeAV provides direct bindings to FFmpeg's C APIs, which work with raw memory pointers. The high-level API adds safety abstractions and automatic resource management, but incorrect usage can still cause crashes. Common issues include mismatched video dimensions, incompatible pixel formats, or improper frame buffer handling. The library validates parameters where possible, but can't guarantee complete memory safety without limiting functionality. When using the low-level API, pay attention to parameter consistency, resource cleanup, and format compatibility. Following the documented patterns helps avoid memory-related issues.
Electron
NodeAV fully supports Electron applications. The prebuilt binaries are ABI-compatible with Electron, so no native rebuild is required during packaging. Both the native bindings and the bundled FFmpeg CLI binaries work seamlessly within Electron's main process.
For hardware-accelerated video processing with Intel GPUs on Linux, you need to install specific system packages. The FFmpeg binaries included with this library are built with libva 2.20, which requires Ubuntu 24.04+ or Debian 13+ as minimum OS versions.
Installation Steps
Add Kisak-Mesa PPA (recommended for newer Mesa versions with better hardware support):
After installation, verify hardware acceleration is working:
# Check VAAPI support
vainfo
# Check Vulkan support
vulkaninfo
# Should show available profiles and entrypoints for your Intel GPU
Note: If you're running an older Ubuntu version (< 24.04) or Debian version (< 13), you'll need to upgrade your OS to use hardware acceleration with this library.
License
This project is licensed under the MIT License. See the LICENSE file for details.
Important: FFmpeg itself is licensed under LGPL/GPL. Please ensure compliance with FFmpeg's license terms when using this library. The FFmpeg libraries themselves retain their original licenses, and this wrapper library does not change those terms. See FFmpeg License for details.
Contributing
Contributions are welcome! Please read CONTRIBUTING.md for development setup, code standards, and contribution guidelines before submitting pull requests.
Support
For issues and questions, please use the GitHub issue tracker.
The npm package node-av receives a total of 16,708 weekly downloads. As such, node-av popularity was classified as popular.
We found that node-av demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.Ā It has 1 open source maintainer collaborating on the project.
Package last updated on 04 Jun 2026
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.