A browser-first MP4 merger built with Vite, React, TypeScript, @dnd-kit, and ffmpeg.wasm. Drop in your clips, reorder them on a timeline, trim segments, and export a single MP4 — all without leaving your browser.
- Features
- Demo
- Tech Stack
- Architecture
- Installation
- Getting Started
- Usage
- Engine Modes
- Scripts
- Project Structure
- Roadmap
- Contributing
- License
- Acknowledgements
- Drag & Drop Upload – Add multiple
.mp4files via picker, drop zone, or paste. - Smart Auto-Sort – Clips are automatically ordered by file modification time.
- Clip Inspector – See duration, resolution, aspect ratio, and audio stream info per clip.
- Sortable Timeline – Reorder clips with mouse or keyboard via
@dnd-kit. - Trim Segments – Cut in/out points on any clip before merging.
- Flexible Output – Choose from common output aspect ratios (16:9, 9:16, 1:1, 4:3, etc.).
- Aspect Ratio Fit – Mismatched source frames are placed over a blurred background to fill the canvas.
- Audio Normalization – Clips without audio receive a silent AAC track so mixed projects merge cleanly.
- Hybrid Engine – Falls back to native backend FFmpeg for larger projects; browser FFmpeg for everything else.
- 100% Private – Files are processed locally in the browser. Nothing is uploaded to a server.
- Modern UI – React 18 + Lucide icons, accessible keyboard interactions.
- Clone the repo and run the dev server (see Installation).
- Open
http://localhost:5173. - Drop a few
.mp4files into the upload area. - Reorder / trim as needed.
- Pick an output ratio and click Merge.
Sample clips are available in the examples/ directory (git-ignored) for quick testing.
| Layer | Technology |
|---|---|
| UI Framework | React 18 |
| Build Tool | Vite 6 |
| Language | TypeScript 5 |
| Drag & Drop | @dnd-kit |
| Browser Engine | @ffmpeg/ffmpeg (WebAssembly) |
| Backend Engine | Native ffmpeg / ffprobe via Express |
| HTTP Server | Express 5 |
| File Uploads | multer |
| Icons | lucide-react |
| Testing | Vitest + jsdom |
| Concurrency | concurrently |
┌────────────────────────┐ ┌──────────────────────────┐
│ React Frontend (Vite) │ /api │ Express Backend (Node) │
│ localhost:5173 │ ──────► │ localhost:5174 │
│ │ │ │
│ • Drag & Drop UI │ │ • Upload endpoint │
│ • Timeline / Trimmer │ │ • FFmpeg/ffprobe probe │
│ • Browser FFmpeg.wasm │ │ • Native merge fallback │
└────────────────────────┘ └──────────────────────────┘
The UI is engine-agnostic. A small MergeEngine interface lets the app swap between:
BrowserFfmpegEngine–ffmpeg.wasmrunning in a Web Worker. Always available, no install needed.BackendFfmpegEngine– Nativeffmpegon the Node backend. Faster for large projects.- Hybrid (default) – Picks the best engine per project size and backend availability.
- Node.js
>= 20.x(tested on 24.x) - npm
>= 10.x - FFmpeg (optional, for the backend engine)
git clone https://github.com/johannesWen/Video-Merger.git
cd Video-Mergernpm installThe browser engine works out of the box. To enable the faster backend engine:
# Debian / Ubuntu
sudo apt install ffmpeg
# macOS
brew install ffmpeg
# Windows
choco install ffmpegVerify with:
ffmpeg -version
ffprobe -versionStart both the frontend and backend dev servers in one command:
npm run devThis uses concurrently to run:
| Service | URL | Description |
|---|---|---|
| Frontend | http://localhost:5173 | The React app (Vite dev server) |
| Backend | http://localhost:5174 | Express API for uploads and native FFmpeg jobs |
The Vite dev server automatically sets the Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy headers required by WebAssembly workers, and proxies /api/* to the backend.
Open the frontend URL in a modern browser (Chrome, Edge, or Firefox recommended).
- Add clips
- Click the upload area, drag & drop files, or paste from clipboard.
- Only
.mp4is supported (H.264 / AAC recommended).
- Review & reorder
- Clips are auto-sorted by
File.lastModified. - Drag clips on the timeline to reorder, or use the keyboard with
@dnd-kit(Tab + Space + Arrows).
- Clips are auto-sorted by
- Trim (optional)
- Open the Edit dialog on any clip to set in/out points and preview.
- Choose output
- Pick an aspect ratio (the default blurs the background to keep mismatched sources looking clean).
- Merge
- Click Merge and wait for processing.
- Download the resulting MP4.
| Action | Shortcut |
|---|---|
| Focus next clip | Tab |
| Pick up / drop clip | Space |
| Move clip | Arrow Keys |
| Cancel drag | Escape |
| Open clip editor | Enter on focused |
You can switch the engine from the Engine control in the UI:
| Mode | Behavior |
|---|---|
| Hybrid | Backend FFmpeg when available and project is large, otherwise browser. |
| Backend | Always uses native FFmpeg on the Node server. Requires ffmpeg install. |
| Browser | Always uses ffmpeg.wasm in the browser worker. No backend needed. |
Hybrid is the default and recommended mode for the best balance of speed and portability.
npm run dev # Run frontend + backend together
npm run dev:frontend # Vite dev server only (5173)
npm run dev:backend # Backend only with tsx watch (5174)
npm run build # Type-check (tsc) and build the production bundle
npm run preview # Preview the production build
npm test # Run the Vitest test suite onceVideo-Merger/
├── assets/ # README screenshots
│ ├── video_merger.png
│ └── edit_dialog.png
├── examples/ # Sample MP4s (git-ignored)
├── src/
│ ├── backend/ # Express server, multer uploads, native FFmpeg
│ ├── frontend/ # React UI: App, ClipPreviewModal, MissingClipsDialog
│ ├── processing/ # Engine adapters (Browser, Backend, filters, segments)
│ └── shared/ # Public types, media utils, session file, trim helpers
├── index.html # Vite entry
├── vite.config.ts # Vite config (proxy + COOP/COEP)
├── tsconfig*.json # TypeScript project references
└── package.json
Key files:
src/frontend/App.tsx– Root React component, drag & drop, engine selection.src/frontend/ClipPreviewModal.tsx– Per-clip editor with trim controls.src/processing/BrowserFfmpegEngine.ts–ffmpeg.wasmadapter.src/processing/BackendFfmpegEngine.ts– Native FFmpeg adapter.src/processing/ffmpegFilters.ts– Shared filter graph builder.src/backend/server.ts– Express API for uploads and merges.src/shared/types.ts– Public media types and output settings.
- Additional container formats (
.mov,.webm) - GPU-accelerated encoding passthrough
- Persistent projects (IndexedDB sessions)
- Plugin system for custom filters
Contributions are welcome!
- Fork the repository.
- Create a feature branch:
git checkout -b feat/amazing-feature - Commit your changes:
git commit -m "feat: add amazing feature" - Push to your branch:
git push origin feat/amazing-feature - Open a Pull Request.
Please run npm test and npm run build before submitting.
Released under the MIT License.
- FFmpeg.wasm for in-browser video processing.
@dnd-kitfor accessible drag and drop.- Lucide for the icon set.
- The React, Vite, and TypeScript teams for the amazing tooling.

