Back to contents·The Bitstream·Vol.1GitHub

Compress down to size.

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.

What you can do
Analyze
  • Drop, pick or paste a URL
  • Read real container + codecs
  • Show the source bitrate
  • Honest about unreadable formats
Shrink
  • Hit a hard size limit
  • One-click platform presets
  • Quality presets (CRF-like)
  • Exact bitrate target
Reduce
  • Downscale resolution
  • Cap the frame rate
  • Stereo → mono · downsample
  • Trim to the part you need
Prove
  • Re-opens the output (verify)
  • Before → after panel
  • Live × real-time meter
  • Guaranteed ≤ your target
Trust
  • Private · no upload
  • No watermark · no signup
  • Batch many at once
  • 0 bytes uploaded
Fig. 01 — Live CompressionReady
SourceDrop a fileawaiting input
EngineMediabunnyWebCodecs · on-device
Outputsmaller file
Drop a file to compress
or click to browse — MP4 · MOV · MKV · WEBM · MP3 · WAV · OGG · FLAC · AAC
Stays on your device · nothing 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).

Drop a file to see the size-reducing outputs your browser can produce.
A compressor is a Conversion forced to transcode with smaller parameters — decoded, re-encoded and muxed on your device. Nothing is uploaded.
Fig. 02 — Why Mediabunnyvs the usual ways to compress
Live — measured on your file, just now

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 vs the usual ways to compress
Mediabunnyffmpeg.wasmServer transcodeCloud compressor
Where it runsYour browser · WebCodecsYour browser · wasmYour serverCloud upload
Download to start~156 KB gz ¹~25–31 MB wasm ²
Uploads your file?NoNoYesYes
Hits a size ceiling?Yes · re-checks + retriesManual 2-pass scriptingDependsSometimes
SpeedHW-acceleratedSoftware codecs, slowerFast + up/downloadQueue + up/download
PrivacyNever leaves the tabNever leaves the tabOn your serverOn their servers
CostFreeFree$ 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.

What it reads → writes (size-reducing targets only)
ReadsMP4 · MOV · MKV · WebM · MP3 · WAV · OGG · FLAC · AAC
WritesMP4 (H.264) · WebM (VP9) · MP3 · AAC / M4A · Opus

WAV/FLAC aren't offered — they're lossless and can't shrink. Compression always re-encodes via WebCodecs (HW-accelerated where available).

Small, private, header-free
156 KBgzipped library ¹
~30 MBffmpeg.wasm core ²
0bytes uploaded
NoneCOOP/COEP headers

Everything runs on-device — turn Wi-Fi off and it still compresses. Nothing leaves the tab.

FIG. 02 — Measured live where it's your file, cited where it's the alternative. No ffmpeg.wasm is loaded on this page; the comparison is for reference.
The source — every file, nothing hidden
Fig. 01a — Files
compress-demoread-only
Fig. 01b — CodeTS
compress-demo/src/compress.ts
1import {
2 Input, Output, Conversion, ALL_FORMATS,
3 BlobSource, BufferTarget, Mp4OutputFormat, QUALITY_MEDIUM,
4} from 'mediabunny';
5 
6// Compression = a Conversion forced to TRANSCODE with smaller parameters.
7// Always set a bitrate (numeric target or a QUALITY_* preset) so it never
8// 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() });
12 
13 const conversion = await Conversion.init({
14 input, output,
15 tracks: 'primary',
16 trim: opts.trim, // { start, end } seconds
17 video: {
18 codec: 'avc',
19 bitrate: opts.videoBitrate ?? QUALITY_MEDIUM, // number, clamped ≤ source
20 width: opts.width, height: opts.height, // width-only keeps aspect
21 frameRate: opts.frameRate,
22 },
23 audio: opts.mute
24 ? { 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 → 1
31 await conversion.execute();
32 return new Blob([output.target.buffer], { type: 'video/mp4' });
33}
The real source — click any file on the left to read it. Runs as-is, no ffmpeg.wasm, no server.