DEV Community

Robert
Robert

Posted on

I haven't posted in 4 years. Here's what I built: A Local Audio Track Remover with React, Node.js, and FFmpeg

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
Enter fullscreen mode Exit fullscreen mode

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');
};
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode
// Frontend - listening for events
const eventSource = new EventSource(`/api/events?jobId=${jobId}`);
eventSource.onmessage = (event) => {
    const data = JSON.parse(event.data);
    setProcessProgress(data.progress);
};
Enter fullscreen mode Exit fullscreen mode

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());
Enter fullscreen mode Exit fullscreen mode

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.

GitHub Repository

Video Demo

Let's Connect

If you found this project interesting and are looking for a Full Stack Developer, feel free to reach out!

Thanks for reading! 🙌

Top comments (0)