Compress and convert images, videos, audio and PDFs — entirely in your browser.
No uploads. No accounts. No data trail. Open source.
Live demo · Features · Stack · Local setup · Contributing · License
- Every common format Images (JPG · PNG · WebP · AVIF · GIF · HEIC · TIFF · BMP · SVG), video (MP4 · MOV · WebM · AVI · MKV), audio (MP3 · WAV · AAC · FLAC · OGG · Opus · M4A), and PDFs. Drop them in any mix.
- Truly local — everything runs in your browser. Video and audio transcode via FFmpeg WASM, images via canvas-based encoders, PDFs via
pdf-lib. No API routes, no server roundtrip, no telemetry. - One-click conversion presets — For the web (WebP / MP4-H.264 / MP3), Same format (preserve input format), Smallest (AVIF / H.265 / Opus), Works everywhere (JPEG / MP4-H.264 / MP3).
- Batch + ZIP — drop a folder of 500 files, get a single ZIP. Drop a ZIP, we'll unpack it first.
- Per-file overrides — each file row exposes its own quality, format, resolution, and (for tall images > 4000px) slice-count controls.
- Slice tall images — chop page-sized slices out of long screenshots or landing-page captures.
- Real-time progress — actual FFmpeg progress bars, live elapsed/ETA counters, and a pre-flight estimate on the cards that would take longer.
- Light / dark themes with an EU-flag palette (
#00339bblue +#ffcc02gold).
- Next.js 16 (App Router) with React 19
- Tailwind CSS v4 + custom design tokens
- shadcn/ui primitives (Button, Slider, Select, Alert, Card, …)
@ffmpeg/ffmpeg— WebAssembly video + audio pipelinebrowser-image-compression— canvas-based image pipelinepdf-lib+pdfjs-dist— PDF re-encode + renderingjszip— batch ZIP / unzipreact-dropzone— drag-and-drop surface
# 1. Install
npm install
# 2. Run the dev server
npm run dev
# 3. Open http://localhost:3000Scripts:
| Command | What it does |
|---|---|
npm run dev |
Next.js dev server with hot reload |
npm run build |
Production build |
npm start |
Start the built server |
npm run lint |
Run ESLint |
npm run typecheck |
Strict tsc --noEmit |
Requirements: Node 20+.
src/
├── app/ Next.js App Router (no API routes — fully static)
│ ├── globals.css Tailwind + design tokens (EU blue + gold)
│ ├── layout.tsx Root layout, fonts, theme provider
│ └── page.tsx Mounts <Optimizer />
├── components/
│ ├── optimizer/ Feature components
│ │ ├── Optimizer.tsx Top-level client component (state + handlers)
│ │ ├── Hero.tsx Landing/empty state
│ │ ├── Features.tsx Bento grid (Drop-route, Convert, Batch/ZIP, Private)
│ │ ├── DropOverlay.tsx Fullscreen drag catcher
│ │ ├── CommandCard.tsx Active command surface once files are loaded
│ │ ├── FileList.tsx Filtered list of FileCards + empty state
│ │ ├── FileCard.tsx Per-file row (thumb, stats, actions, progress)
│ │ ├── FileSettings.tsx Per-file quality/format/resolution drawer
│ │ ├── InfoTip.tsx Hover tooltip used in the tune panel
│ │ ├── constants.ts Presets, quality/format/resolution tables
│ │ ├── types.ts UploadedFile, OptimizedFile, FileKind, Slice
│ │ └── utils.ts formatSize, detectFileKind, sameFormatOutput, estimators
│ ├── theme/ next-themes provider + toggle
│ └── ui/ shadcn primitives
├── hooks/
│ └── use-window-drag.ts Detects file drags anywhere on the window
├── lib/
│ ├── optimizer/ Pure pipeline implementations
│ │ ├── client-image-optimizer.ts browser-image-compression wrapper
│ │ ├── client-video-optimizer.ts FFmpeg WASM loader + video pipeline
│ │ ├── client-audio-optimizer.ts FFmpeg WASM audio pipeline
│ │ ├── client-pdf-optimizer.ts pdf-lib + pdfjs-dist pipeline
│ │ └── types.ts Shared format + option types
│ └── utils.ts Global `cn()` class-merger
└── public/ symbol.svg · favicon.png
- All images run client-side via
browser-image-compression(canvas + web worker). - HEIC / TIFF / BMP inputs fall back to JPEG output (browser canvases can't encode those formats directly).
- AVIF output falls back to WebP on browsers that can't encode AVIF via canvas.
@ffmpeg/ffmpeg0.12.6 single-threaded core, fetched from jsDelivr and registered as blob URLs (noSharedArrayBuffer/ COOP+COEP headers required).- Progress events are wired through a per-call guard flag so stale listeners on the shared FFmpeg instance can't leak between files.
- Quality (0–100) maps to codec-specific CRF values and bitrates (H.264 / H.265 / VP9 for video; libmp3lame / aac / libopus / libvorbis / flac / pcm_s16le for audio).
pdf-libre-encodes + strips metadata.pdfjs-distrenders thumbnails when needed.
jszipproduces a single archive from all successfully optimized outputs. Split-slice outputs get nested folders.
- Every byte stays in your browser. No API routes, no server roundtrip, no upload.
- No telemetry, no analytics, no crash reporter, no cookies, no accounts.
- The build is fully static — host it on any static host (Vercel, Cloudflare Pages, Netlify, a plain S3 bucket). No server runtime needed.
PRs welcome. Keep the no-accounts, no-uploads promise intact.
# Typical flow
git checkout -b feature/your-idea
npm run typecheck
npm run lint
npm run build
git commit -m "Add: your idea"MIT — see LICENSE.