I haven't written on Dev.to since 2022. A lot has changed in 4 years — new jobs, new projects, and new tech. I'm back with a project I'm genuinely excited about.
Table of Contents
Introduction
Have you ever wanted to remove one or more audio tracks from a video?
Maybe you have a movie or a TV series episode with multiple audio tracks, but you only want to keep the one you need. Or perhaps you want to remove all audio tracks for editing purposes or accessibility reasons.
To solve this, I used to run a command like this:
ffmpeg -i test-video.1080p-Dual.mkv -map 0:v -map 0:a:1 -c copy D:test-video.es.mkv
What does this command do?
| Part | Explanation |
|---|---|
ffmpeg |
The program that executes the command |
-i test-video.1080p-Dual.mkv |
Input file (the original video) |
-map 0:v |
Selects all video tracks from file 0 |
-map 0:a:1 |
Selects audio track number 1 from file 0 (in this case, Spanish) |
-c copy |
Copies the codecs (no re-encoding) |
D:test-video.es.mkv |
Output file (the processed video) |
It worked, but every time I had to remember the syntax, check which track was which, and wait without knowing the progress. It was tedious and error-prone.
So I built a tool that does the same thing, but with a clean UI, real-time progress, and batch processing.
The Problem
Video files often contain multiple audio tracks:
- Different languages (English, Spanish, French, etc.)
- Commentary tracks
- Audio descriptions for accessibility
- Multiple formats or bitrates
Most video editors are overkill for this task. I wanted a lightweight, fast, and free tool that:
- Works locally (no uploads to the cloud)
- Processes multiple videos at once
- Shows real-time progress
- Remembers track selections per video
- Has a clean and easy-to-use interface
Tech Stack
| Layer | Tech |
|---|---|
| Frontend | React, TypeScript, Vite |
| Backend | Node.js, Express, TypeScript |
| Video Processing | FFmpeg (fluent-ffmpeg) |
| Real-time Updates | Server-Sent Events (SSE) |
| Queue | In-memory processing queue |
| Testing | Vitest |
Key Challenges & Solutions
1. Processing Videos Without Uploading
Since this is a local tool, I didn't want to upload files to a server. The backend reads videos directly from the filesystem using the path configured in .env.
// config.ts
const getVideoFolder = (): string => {
const envPath = process.env.VIDEO_FOLDER_PATH;
return envPath ? path.resolve(envPath) : path.join(__dirname, '../videos');
};
Why does this matter? The tool is 100% local. There are no uploads, no file size limits, no internet dependency, and your files never leave your computer.
2. Real-time Progress with SSE
FFmpeg emits progress events, but I needed to stream them to the frontend. I chose Server-Sent Events over WebSockets because it's simpler for one-way communication.
// Backend - emitting progress events
command.on('progress', (progress) => {
const percent = Math.round(progress.percent || 0);
this.emit('job-progress', item, percent);
});
// Frontend - listening for events
const eventSource = new EventSource(`/api/events?jobId=${jobId}`);
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
setProcessProgress(data.progress);
};
Why SSE? It's built into browsers, handles reconnections automatically, and is perfect for server-to-client updates.
3. Each video remembers its own track choices
I needed to remember which tracks the user selected for each video. I used a Map in the frontend state:
const [selectedTracksMap, setSelectedTracksMap] = useState<Map<string, number[]>>(new Map());
When you switch between videos, the selected/deselected tracks are preserved.
4. Batch Processing with a Queue
Processing multiple videos simultaneously would overwhelm the system. So I implemented a simple in-memory queue.
5. Handling Videos with No Audio Tracks
I wanted to add the option to remove all audio tracks just to try it out. The backend supports tracksToKeep: [] using FFmpeg's -an flag.
Conclusion
This project started as a personal solution to a problem: manually running commands to remove audio tracks or having to create scripts for video batches was very repetitive. This project ended up being a practical exercise in building a local application.
What makes this project interesting:
- FFmpeg integration – Working with a powerful CLI tool from Node.js taught me how to handle subprocesses, parse output, and stream progress events.
- Real-time communication – Server-Sent Events proved to be a simple yet effective alternative to WebSockets for pushing progress updates to the client.
- Architecture – Separating the frontend into components and custom hooks, and the backend into controllers and services, kept the codebase maintainable as the project grew.
- Local-first – Building a tool that runs entirely on the user's machine without cloud dependencies has real value for privacy and speed.
The project is open source. Feel free to use it, modify it, or learn from it.
Let's Connect
If you found this project interesting and are looking for a Full Stack Developer, feel free to reach out!
- GitHub: github.com/rtagliaviaz
- Telegram: @rtaglia
-
Discord:
appulino - Email: rtagliavia.pp@gmail.com
Thanks for reading! 🙌
Top comments (0)