Drop in a video or audio file, pick a size or a quality, and Mediabunny re-encodes it smaller right here in your browser — and re-checks the result so “fits Discord” is guaranteed, not guessed. Read the source below; nothing is ever uploaded.
Drop or pick several files to batch-compress. URLs stream via Mediabunny's UrlSource — a direct file link on a CORS-enabled host (not a YouTube/streaming page).
Compress a file above and these fill with real numbers from your own device — how much smaller, the compression ratio, throughput, and how many times faster than real-time it ran. No synthetic benchmark.
| Mediabunny | ffmpeg.wasm | Server transcode | Cloud compressor | |
|---|---|---|---|---|
| Where it runs | Your browser · WebCodecs | Your browser · wasm | Your server | Cloud upload |
| Download to start | ~156 KB gz ¹ | ~25–31 MB wasm ² | — | — |
| Uploads your file? | No | No | Yes | Yes |
| Hits a size ceiling? | Yes · re-checks + retries | Manual 2-pass scripting | Depends | Sometimes |
| Speed | HW-accelerated | Software codecs, slower | Fast + up/download | Queue + up/download |
| Privacy | Never leaves the tab | Never leaves the tab | On your server | On their servers |
| Cost | Free | Free | $ server time | $ / watermark / signup |
¹ Full library, gzipped, before tree-shaking — measured from the installed package. ² ffmpeg.wasm core size & cross-origin-isolation requirement per its project docs. Other cells are qualitative.
WAV/FLAC aren't offered — they're lossless and can't shrink. Compression always re-encodes via WebCodecs (HW-accelerated where available).
Everything runs on-device — turn Wi-Fi off and it still compresses. Nothing leaves the tab.
1import {2 Input, Output, Conversion, ALL_FORMATS,3 BlobSource, BufferTarget, Mp4OutputFormat, QUALITY_MEDIUM,4} from 'mediabunny';56// Compression = a Conversion forced to TRANSCODE with smaller parameters.7// Always set a bitrate (numeric target or a QUALITY_* preset) so it never8// silently remuxes (a copy = 0 % saved). tracks:'primary' drops extra tracks.9export async function compress(file, opts) {10 const input = new Input({ source: new BlobSource(file), formats: ALL_FORMATS });11 const output = new Output({ format: new Mp4OutputFormat(), target: new BufferTarget() });1213 const conversion = await Conversion.init({14 input, output,15 tracks: 'primary',16 trim: opts.trim, // { start, end } seconds17 video: {18 codec: 'avc',19 bitrate: opts.videoBitrate ?? QUALITY_MEDIUM, // number, clamped ≤ source20 width: opts.width, height: opts.height, // width-only keeps aspect21 frameRate: opts.frameRate,22 },23 audio: opts.mute24 ? { discard: true }25 : { codec: 'aac', bitrate: opts.audioBitrate ?? QUALITY_MEDIUM,26 numberOfChannels: opts.channels, sampleRate: opts.sampleRate },27 });28 if (!conversion.isValid)29 throw new Error(conversion.discardedTracks.map((t) => t.reason).join(', '));30 conversion.onProgress = opts.onProgress; // 0 → 131 await conversion.execute();32 return new Blob([output.target.buffer], { type: 'video/mp4' });33}