Reducio

Often, I need to compress videos. Sometimes it’s to meet file size limits (hello, Discord!), or just to make uploads a bit quicker.

I used to either upload them to an online compression tool or use ffmpeg locally. I’d go the ffmpeg route mostly because of privacy concerns, or if the files were huge and uploading would take too long.

When I go through ffmpeg route, I’d find myself Googling the same command from StackOverflow. Every. Single. Time.

Pretty sure it was this one: https://unix.stackexchange.com/q/28803/420712.

I just wanted a simple, no-fuss way to compress videos. Drag, drop, done. And I figured, hey, I can’t be the only one who wants this, right?

So, I built Reducio. It’s a nifty little app that does exactly two things, and does them well:

  • 📦 Compress Audio/Video files.
  • 🔇 Removes Audio track from video files.

That’s it.

No command line gymnastics. No “your file is too big” from online tools. No weirdness. Just pure, offline, drag-and-drop satisfaction.

Tech

Alright, so how did I actually build this thing? I needed a GUI, obviously. I evaluated a few options.

  • Electron: First thought, but nah. It’s just too beefy & bloated. Who wants to download a 100MB app just to compress a video? Plus, it eats like 80MB of RAM even when idle. Pass.

  • Tauri: This one got me excited! Super interesting, and I almost went with it. I got a basic Tauri project up and running. Then… BAM! Error message:

    TAURI g_uri_get_scheme: assertion 'uri != NULL' failed

    Turns out, there’s an open issue for this on Tauri’s GitHub. Tried all the workarounds in the comments, but no luck. And with no official word from the maintainers on that thread, I sadly had to move on. Bummer, because I was this 🤏 close to finally diving into Rust for it!

  • So, what’s left? Well, there’s Python’s Tkinter and Flutter for desktop. Tkinter is… functional. But the UI feels old. Like, 1990s old.

    Tkinter Sample App
    Tkinter Sample App

So, Flutter it was!

And, of course, I used ffmpeg for the actual video compression.

I’m basically using this command:

ffmpeg -i input.mp4 -vcodec libx264 -crf 20 output.mp4

and conditionally controlling crf based on user’s desired compression level. Higher the CRF, Higher the compression, lower the quality.

“But wait,” I hear you, why not libx265 for even better compression?

Good question! I thought about it. libx265 can indeed compress files better (smaller). But it has compatibility issues. It’s invented in 2013 so, older hardware struggles with it, and surprisingly, even some newer platforms like iOS and HTML5 video players can be picky.

don’t use libx265 if you plan to use it on html5 video, see https://gist.github.com/Vestride/278e13915894821e1d6f#support.

> ~Lluís commented on Apr 13, 2020

Hi. After ffmpeg -i input.mp4 -vcodec libx265 -crf 28 output.mp4 ios devices cannot seem to play the video. AFAIK after ios11 HEVC is supported. Any idea on this?

> ~Arka Prava Basu commented on Jun 26, 2020

So, I sticked with libx264. And rest of it is, calling ffmpeg through dart and passing apppropriate arguments.

// Video Settings
if (_keepOriginalVideo) {
    ffmpegArgs.addAll(['-c:v', 'copy']);
} else {
    ffmpegArgs.addAll(['-c:v', 'libx264']);
    ffmpegArgs.addAll(['-preset', 'medium']);
    int videoSliderIdx = _videoQualitySliderValue.round();
    if (videoSliderIdx == 0) ffmpegArgs.addAll(['-crf', '28']); // Low
    else if (videoSliderIdx == 1) ffmpegArgs.addAll(['-crf', '23']); // Medium
    else if (videoSliderIdx == 2) ffmpegArgs.addAll(['-crf', '20']); // Good
    else if (videoSliderIdx == 3) ffmpegArgs.addAll(['-crf', '18']); // High
}

// Audio Settings
if (_keepOriginalAudio) {
    ffmpegArgs.addAll(['-c:a', 'copy']);
} else {
    ffmpegArgs.addAll(['-c:a', 'aac']);
    int audioSliderIdx = _audioQualitySliderValue.round();
    final bitrates = ['64k', '96k', '128k', '192k', '256k', '320k'];
    if (audioSliderIdx >= 0 && audioSliderIdx < bitrates.length) {
        ffmpegArgs.addAll(['-b:a', bitrates[audioSliderIdx]]);
    } else {
        ffmpegArgs.addAll(['-b:a', '128k']); // Fallback, should not happen with slider
    }
}

ffmpegArgs.addAll(['-y', _outputFilePath!]);

The “Oops, My Music Was On” Feature

So, story time, One day, I’m working with headphones on, and I needed to record a quick screen capture to report a bug. I opened OBS, recorded and dropped the video into Discord.

and Guess what?

My background music was perfectly captured in the screen recording. Unfortunate thing is, I rewatched the video and didn’t even notice because, well, I was still listening to music! I just assumed it was my live audio.

Thankfully, no harm done. Everyone on Discord just politely ignored my accidental soundtrack.

BUT! It got me thinking. What if that happens and you don’t want to re-record the entire process again? So, I’ve added a feature to Reducio to remove the audio track from the video.

With these two features, Reducio is complete. (or not? I don’t know.)

Building for other platforms

Flutter is cross-platform, meaning, same code can run on Linux, Windows and MacOS. I just need to build it for each platform. You know, like a nice executable. .exe for Windows, .app for MacOS, .deb/.AppImage for Linux.

I’ve already built and released the Linux version as an AppImage. You can grab it here: Reducio v1.0.0 Release on GitHub.

One little hiccup I ran into: if you build a Flutter app on the new Ubuntu 24.04 LTS, it might not work on older Ubuntu 22.04 LTS. There’s an open Flutter issue about it that doesn’t seem to be getting much traction. The workaround for now is to develop and debug on 24.04, but then do the final production build on a 22.04 system.

Screenshots

Compress Tab

Remove Audio Tab