Shrink a video to fit Discord, WhatsApp or email. It is encoded in your browser with WebCodecs and WebAssembly, so the file never leaves your device.
- Private: the video is read and encoded on your device. Nothing is uploaded and there is no account.
- Fast: WebCodecs encodes on your GPU. ffmpeg.wasm takes over for formats and browsers WebCodecs cannot handle (AVI, MPEG-4 Part 2...).
- Hits the target: the output is measured, and re-encoded with a corrected budget if it is too large.
- Platform presets: Discord, Discord Nitro, WhatsApp, Gmail / Outlook, generic sizes, or any custom size from 1 to 2048 MB.
- Large files: videos up to 5 GB, in MP4, MOV, MKV, WebM, AVI and more.
- Options: drag and drop, trim the start or the end, mute the sound, keep the original frame rate, or keep the audio only.
- Five languages: English, French, Polish, Korean and Arabic, with light, dark and Catppuccin themes.
Time from clicking "Compress Video" to the download being ready, measured on an Apple M4 MacBook with Chrome 153 (macOS 27), production build, median of two runs.
| Clip | Original | Target | Output | Reduction | Time |
|---|---|---|---|---|---|
| 4K 30 fps, 24 s | 148.5 MB | 25 MB | 22.9 MB | 84.6% | 2.8 s |
| 4K 30 fps, 24 s | 148.5 MB | Discord (20 MB) | 18.2 MB | 87.7% | 2.8 s |
| Phone, portrait 60 fps, 44 s | 125.6 MB | 25 MB | 23.3 MB | 81.5% | 4.3 s |
| Phone, portrait 60 fps, 44 s | 125.6 MB | Discord (20 MB) | 18.7 MB | 85.1% | 4.3 s |
| 1080p film, 60 s | 52.6 MB | 25 MB | 23.3 MB | 55.6% | 4.3 s |
| 1080p film, 60 s | 52.6 MB | Discord (20 MB) | 18.7 MB | 64.5% | 4.3 s |
| 1080p film, 2 min 30 s | 170.2 MB | 25 MB | 23.3 MB | 86.3% | 10.6 s |
| 1080p film, 2 min 30 s | 170.2 MB | Discord (20 MB) | 18.7 MB | 89.0% | 10.6 s |
| 1080p film, 2 min 30 s | 170.2 MB | 8 MB | 7.5 MB | 95.6% | 10.9 s |
Every run used WebCodecs and met the target on the first attempt. Resolution and frame rate drop when the target requires it: the 4K clip comes out at 1440x810, the portrait clip at 810x1440 and 30 fps.
Clips: Volcano eruption of Litli-Hrútur by Giles Laurent (CC BY-SA 4.0), VTA light rail arriving at Hamilton station by Grendelkhan (CC BY-SA 4.0), Tears of Steel and Sintel by the Blender Foundation (CC BY 3.0), re-encoded to H.264 at camera-like bitrates before the test.
The image runs on amd64 and arm64 and listens on port 3000.
docker run -d --name compress -p 3000:3000 --restart unless-stopped ghcr.io/sowahq/compress.lol:latestOr with Docker Compose (compose.yaml):
services:
compress:
image: ghcr.io/sowahq/compress.lol:latest
ports:
- '3000:3000'
restart: unless-stopped- Serve it over HTTPS (or open it on
localhost). Browsers only enable WebCodecs andSharedArrayBufferin a secure context, so a plainhttp://LAN address will not compress anything. Any reverse proxy with a certificate works (Caddy, Traefik, nginx). - No analytics: a self-hosted instance loads no tracking script and sends no data.
- Environment variables:
PORT(default3000) andHOST(default0.0.0.0). - Build it yourself:
docker build -t compress.lol .from a clone of this repository.
- Analysis: Mediabunny reads the file (duration, resolution, frame rate, codec, rotation). Files it cannot read are probed with ffmpeg.wasm.
- Settings: resolution, frame rate and bitrates are derived from the target size, the duration and how much motion the video has. Targets too small for the duration are refused before encoding.
- Encoding: WebCodecs encodes H.264 and AAC into an MP4. If WebCodecs cannot handle the file, fails or stalls, the same attempt runs with ffmpeg.wasm, loaded on demand from jsDelivr (unpkg as fallback) and checked against pinned SHA-256 hashes.
- Fit check: an output over the target is re-encoded with a corrected budget, up to three attempts.
The flow lives in src/lib/compression/, starting with compressor.ts. All sizes are decimal megabytes (1 MB = 1,000,000 bytes), the unit platforms use. Presets live in src/lib/compression/presets.json, each with its official source and the date it was checked.
- WebCodecs (fast path): Chrome and Edge 94+, Safari 16.4+, Firefox 130+. Audio is always encoded with a WebAssembly AAC encoder, because native AAC encoders ignore or reject low bitrates.
- Fallback: any browser with WebAssembly and
SharedArrayBuffer. The app sends the cross-origin isolation headers this requires.
Videos are never uploaded. Analysis and encoding run in your browser. compress.lol counts anonymous usage events with Umami, without cookies: the resolution tier, size and duration ranges, file extension, chosen target, engine used and whether the target was met. File names and contents are never sent.
Bug reports, new platform presets, translations and code are welcome. Read CONTRIBUTING.md for the setup, the checks to run and the pull request conventions, and Translations to add a language. This project follows a code of conduct.
npm ci # install dependencies
npm run dev # development server on http://localhost:5173
npm test # unit tests
npm run check # type check
npm run lint # formatting check (npm run format to fix)
npm run build # production buildRequirements: Node.js 20.9 or later and npm. The /lab page, available in development only, compresses the same file with every engine and compares speed, size and target accuracy. compress.lol runs on Cloudflare Workers, and every merge to main is deployed by Cloudflare Workers Builds.
compress.lol is open source under the Apache License 2.0.
- Mediabunny and its AAC encoder extension: reading, WebCodecs encoding and writing of media files
- FFmpeg and ffmpeg.wasm: the fallback engine
- Svelte and SvelteKit: application framework
- Paraglide JS: type-safe translations
- Tailwind CSS, shadcn-svelte and Bits UI: styling and UI components
- Lucide: icons
- Catppuccin: color themes
- Umami: cookie-free analytics
- Cloudflare Workers: hosting
- jsDelivr and unpkg: delivery of the ffmpeg.wasm core
- Contributor Covenant: code of conduct
