omni-compress - v2.3.1
    Preparing search index...

    omni-compress - v2.3.1

    Omni Compress

    NPM Version License NPM Downloads CI Status Coverage Tested with Vitest TypeScript

    Universal compression and archiving for browsers and Node.js.
    One API. Three engines. Isomorphic ZIP & Media processing.

    Live Neo-Brutalist Demo 🚀


    omni-compress is a high-performance, isomorphic compression library. It automatically routes media compression (images/audio/video) to the fastest available engine at runtime — native Web APIs, FFmpeg WebAssembly, or OS-level binaries — and provides built-in ZIP archiving for any file type.

    Problem How omni-compress solves it
    Browser and Node need different code paths Single Isomorphic API — environment detection is automatic
    Archiving or batching needs separate libs Built-in ZIP archive() and archiveStream() for any file type
    FFmpeg Wasm is heavy (~30 MB) and slow to load Uses native OffscreenCanvas/WebCodecs for standard formats (0 KB Wasm)
    Media processing freezes the UI ALL browser work runs in Web Workers with zero-copy Transferable transfers
    FFmpeg Wasm is too slow Multi-threading support via @ffmpeg/core-mt (requires COOP/COEP)
    Wasm memory leaks crash browser tabs FFmpeg singleton with idle-timeout auto-termination; VFS cleanup per-operation
    Large files crash the browser silently FileTooLargeError thrown before loading files > 250 MB into Wasm
    Fast Path fails on unsupported browsers Automatic fallback from Fast Path to Heavy Path on any runtime error
    No way to cancel a running compression Full AbortSignal support — terminates Wasm/child process on abort
    Silent failures when file extension ≠ content detectFormat() reads magic bytes to identify the real format
    npm install omni-compress
    
    # bun
    bun add omni-compress

    # pnpm
    pnpm add omni-compress

    # yarn
    yarn add omni-compress

    Previously published as @dharanish/omni-compress (deprecated — please migrate to omni-compress).

    Node.js users: For the Node adapter to work, install ffmpeg-static (bundled as an optional dependency) or ensure ffmpeg is available on your system PATH.

    AVIF in the browser: AVIF encoding uses @jsquash/avif (standalone libaom-av1 Wasm from Google's Squoosh, 1.1 MB gzipped). It is bundled automatically -- no extra install or configuration needed. No SharedArrayBuffer or special headers required.

    import { compressImage, compressAudio, compressVideo } from 'omni-compress';

    // Image → WebP
    const { blob, ratio } = await compressImage(imageFile, {
    format: 'webp',
    quality: 0.8,
    });
    console.log(`Saved ${Math.round((1 - ratio) * 100)}%`);

    // Audio → Opus (with cancellation)
    const controller = new AbortController();
    const { blob: audio } = await compressAudio(audioFile, {
    format: 'opus',
    bitrate: '96k',
    onProgress: (p) => console.log(`${p}%`),
    signal: controller.signal,
    });

    // Video → MP4
    const { blob: video } = await compressVideo(videoFile, {
    format: 'mp4',
    bitrate: '1M',
    });

    // Archive multiple files into a ZIP
    import { archive } from 'omni-compress';
    const { blob: zip } = await archive([
    { name: 'photo.webp', data: blob },
    { name: 'audio.opus', data: audio },
    { name: 'video.mp4', data: video },
    ]);

    Compresses an image using the fastest available engine (OffscreenCanvas fast path, FFmpeg Wasm heavy path, or native ffmpeg on Node).

    import { compressImage } from 'omni-compress';

    const result = await compressImage(file, {
    format: 'webp',
    quality: 0.8,
    maxWidth: 1920,
    signal: controller.signal,
    });
    // result.blob → the compressed Blob
    // result.ratio → e.g. 0.62 (38% smaller)
    // result.originalSize / result.compressedSize → bytes

    ImageOptions

    Property Type Default Description
    format 'webp' | 'avif' | 'jpeg' | 'png' Target output format
    quality number 0.8 Lossy quality 0.01.0
    maxWidth number Max output width in px (aspect ratio preserved)
    maxHeight number Max output height in px (aspect ratio preserved)
    preserveMetadata boolean false Keep EXIF data in the output
    useWorker boolean Auto Force Web Worker (true) or Main Thread (false)
    onProgress (percent: number) => void Progress callback 0100
    signal AbortSignal Cancel the operation — throws AbortError when signalled

    Compresses an audio file via WebCodecs (fast path) or FFmpeg Wasm (heavy path).

    AudioOptions

    Property Type Default Description
    format 'opus' | 'mp3' | 'flac' | 'wav' | 'aac' Target output format
    bitrate string '128k' Target bitrate, e.g. '96k', '192k'
    channels 1 | 2 Auto Output channel count (1 = mono, 2 = stereo)
    sampleRate number Auto Output sample rate in Hz, e.g. 48000
    preserveMetadata boolean false Keep audio tags in the output
    useWorker boolean Auto Force Web Worker (true) or Main Thread (false)
    onProgress (percent: number) => void Progress callback 0100
    signal AbortSignal Cancel the operation — throws AbortError when signalled

    Compresses a video file via WebCodecs (fast path foundation) or FFmpeg Wasm (heavy path).

    VideoOptions

    Property Type Default Description
    format 'mp4' | 'webm' Target output format
    bitrate string '1M' Target video bitrate, e.g. '500k', '2M'
    fps number Auto Output frame rate
    maxWidth number Max output width in px
    maxHeight number Max output height in px
    preserveMetadata boolean false Keep metadata in the output
    useWorker boolean Auto Force Web Worker (true) or Main Thread (false)
    onProgress (percent: number) => void Progress callback 0100
    signal AbortSignal Cancel the operation — throws AbortError when signalled

    compressImage, compressAudio, and compressVideo return a CompressResult:

    interface CompressResult {
    blob: Blob; // The compressed output
    originalSize: number; // Input size in bytes
    compressedSize: number; // Output size in bytes
    ratio: number; // compressedSize / originalSize (< 1.0 = smaller)
    format: string; // Target format used (e.g. 'webp')
    }

    Compresses an array of files into a ZIP archive. Works identically in browser and Node.js.

    import { archive } from 'omni-compress';

    const result = await archive(
    [
    { name: 'images/photo.webp', data: imageBlob },
    { name: 'audio/track.opus', data: audioBlob },
    ],
    { level: 6, signal: controller.signal },
    );
    // result.blob → the ZIP Blob (application/zip)
    // result.ratio → compression ratio

    Streaming ZIP output — prefer this for large archives where you want to start sending bytes before all entries are compressed.

    import { archiveStream } from 'omni-compress';

    const stream = archiveStream(entries, { level: 6 });
    const response = new Response(stream, {
    headers: { 'Content-Type': 'application/zip' },
    });

    ArchiveOptions

    Property Type Default Description
    format 'zip' 'zip' Archive format (only ZIP supported currently)
    level 09 6 fflate deflate level (0 = store, 9 = max compression)
    onProgress (percent: number) => void Progress callback 0100
    signal AbortSignal Cancel — throws AbortError

    Reads the first 16 bytes of a buffer and returns the file's actual format from its magic bytes — not its extension.

    import { detectFormat } from 'omni-compress';

    const buffer = await file.arrayBuffer();
    const format = detectFormat(buffer);
    // e.g. 'webp', 'jpeg', 'flac', 'ogg', null (unknown)

    Supported signatures: jpeg, png, gif, webp, wav, avif, flac, ogg, mp3, aac.


    OmniCompressor.process() is deprecated as of v2.0. It will continue to work until v3.0 but returns a raw Blob instead of the richer CompressResult. Migrate to compressImage() or compressAudio().

    import { OmniCompressor } from 'omni-compress';

    /** @deprecated Use compressImage() or compressAudio() instead */
    const blob = await OmniCompressor.process(file, {
    type: 'image',
    format: 'webp',
    quality: 0.8,
    });

    Pass an AbortSignal to any compression or archive call to cancel it mid-flight:

    const controller = new AbortController();

    // Cancel after 5 seconds
    setTimeout(() => controller.abort(), 5000);

    try {
    const result = await compressImage(file, {
    format: 'avif',
    signal: controller.signal,
    });
    } catch (err) {
    if (err instanceof AbortError) {
    console.log('Compression was cancelled');
    }
    }

    When a browser compression is cancelled, the underlying Web Worker is terminated (killing FFmpeg Wasm mid-run) and a fresh worker is created for the next call. On Node.js, the ffmpeg child process receives SIGTERM.


    All library errors extend OmniCompressError and carry a machine-readable code field:

    import {
    OmniCompressError,
    FileTooLargeError,
    FormatNotSupportedError,
    InvalidOptionsError,
    AbortError,
    EncoderError,
    } from 'omni-compress';

    try {
    await compressImage(file, { format: 'webp' });
    } catch (err) {
    if (err instanceof FileTooLargeError) {
    console.log(err.fileSize, err.maxSize); // bytes
    } else if (err instanceof AbortError) {
    console.log('Cancelled'); // err.code === 'ABORTED'
    } else if (err instanceof FormatNotSupportedError) {
    console.log(err.format); // e.g. 'hevc'
    }
    }
    Error Class Code When Thrown
    OmniCompressError Base class for all library errors
    FileTooLargeError FILE_TOO_LARGE Input exceeds 250 MB (browser) — prevents Wasm OOM
    FormatNotSupportedError FORMAT_NOT_SUPPORTED Requested format is not valid for the given media type
    InvalidOptionsError INVALID_OPTIONS Options object is missing required fields or contains invalid values
    AbortError ABORTED AbortSignal fired before or during processing
    EncoderError ENCODER_FAILED FFmpeg or fflate encoder threw — wraps the underlying cause

    Format Fast Path (OffscreenCanvas) Heavy Path (FFmpeg Wasm) Node (OS binary)
    WebP ✅ libwebp ✅ ffmpeg
    AVIF ❌ (not supported by OffscreenCanvas) ✅ @jsquash/avif (libaom-av1) ✅ ffmpeg
    JPEG ✅ ffmpeg
    PNG ✅ ffmpeg
    HEIC ✅ ffmpeg
    TIFF ✅ ffmpeg
    Format Fast Path (WebCodecs) Heavy Path (FFmpeg Wasm) Node (OS binary)
    MP3 ✅ libmp3lame ✅ ffmpeg
    Opus/OGG ✅ (Opus) ✅ libopus ✅ ffmpeg
    FLAC ✅ flac ✅ ffmpeg
    WAV ✅ ffmpeg
    AAC ✅ ffmpeg
    Format Heavy Path (FFmpeg Wasm) Node (OS binary)
    MP4 ✅ libx264 ✅ ffmpeg
    WebM ✅ libvpx-vp9 ✅ ffmpeg

    compressImage() / compressAudio() / compressVideo()


    ┌─────────┐
    Router │ ← Evaluates runtime + format + size
    └────┬────┘

    ┌────┴────────────────────────────┐
    │ │ │
    ▼ ▼ ▼
    Fast Path Heavy Path Node Adapter
    (Native) (FFmpeg Wasm) (child_process)
    │ │ │
    OffscreenCanvas @ffmpeg/ffmpeg OS ffmpeg binary
    WebCodecs (A/V) Multi-threaded Via ffmpeg-static
    │ │ │
    └────────────────┴────────────────┘

    ┌───────────┴───────────┐
    │ │
    Main Thread Path Web Worker Path
    (High Speed) (Isolation)
    Files < 4MB Files > 4MB
    Zero-latency Non-blocking

    omni-compress includes a smart switching engine that dynamically chooses between the Main Thread and Web Workers:

    • Main Thread Path: Standard web files (< 4MB) using native Fast Paths run directly on the main thread. This eliminates postMessage communication latency (~50-150ms), matching the performance of legacy main-thread-only libraries like compressorjs.
      • Note: AVIF uses a lower 512KB threshold due to higher CPU intensity.
    • Web Worker Path: Large files and all FFmpeg Heavy Path tasks are automatically dispatched to background workers. This ensures that long-running operations never freeze your application's UI.

    API Docs — full TypeDoc-generated reference for all functions, options, types, and error classes. Auto-regenerated on every release.

    Why omni-compress — feature matrix vs 7 competitors, quality benchmarks, architecture overview.

    From Guide
    browser-image-compression Migrate from browser-image-compression
    compressorjs Migrate from compressorjs
    jimp Migrate from jimp
    lamejs Migrate from lamejs
    heic2any Migrate from heic2any
    @ffmpeg/ffmpeg Migrate from @ffmpeg/ffmpeg

    The Live Demo features a Neo-Brutalist "Laboratory" UI with 25 distinct persona-based themes (Shakespeare, Picasso, Aryabhata, etc.) supporting multiple languages with culturally relevant quotes and accurate technical terminology.


    We welcome contributions! Please see the Contributing Guide for setup instructions and guidelines.

    MIT © Dharanish V