DEV Community

Mate Technologies
Mate Technologies

Posted on

Building a Smart Video Compressor in Python (Step-by-Step)

In this tutorial, we’ll build VideoOptimizer — a desktop video converter and compressor using Python, FFmpeg, Tkinter, and ttkbootstrap.

You’ll learn:

How to detect FFmpeg automatically

How to calculate video duration & compression targets

How to run FFmpeg safely in background threads

How to build a modern GUI with progress tracking

How to package everything into a Windows EXE

🔗 Clone the source code here:
https://github.com/rogers-cyber/VideoOptimizer

📦 Download the ready-to-use Windows EXE:
https://github.com/rogers-cyber/VideoOptimizer/releases

Step 1: Import Required Modules

We start by importing standard libraries, GUI tools, and ttkbootstrap for styling.

import sys
import os
import re
import threading
import subprocess
from pathlib import Path
from queue import Queue, Empty
import tkinter as tk
from tkinter import filedialog, messagebox
import ttkbootstrap as tb
from ttkbootstrap.constants import *
Enter fullscreen mode Exit fullscreen mode

Why this matters

subprocess → run FFmpeg

threading → keep UI responsive

Queue → safely update progress from background threads

ttkbootstrap → modern UI without extra CSS

Step 2: Detect FFmpeg Automatically

FFmpeg is required, but we don’t want users configuring paths manually.

def detect_ffmpeg():
    import shutil
    ff = shutil.which("ffmpeg")
    if ff:
        return ff
    fallback = r"C:\ffmpeg\bin\ffmpeg.exe"
    if os.path.exists(fallback):
        return fallback
    return None

FFMPEG_PATH = detect_ffmpeg()
Enter fullscreen mode Exit fullscreen mode

✅ Checks system PATH
✅ Falls back to a common Windows location
✅ Prevents crashes if FFmpeg is missing

Step 3: App Configuration

Centralized configuration keeps the project clean and maintainable.

APP_NAME = "VideoOptimizer"
APP_VERSION = "2.0.1"
APP_AUTHOR = "Mate Technologies"
APP_WEBSITE = "https://matetools.gumroad.com"

SNAPS = [10, 20, 30, 40, 50]
Enter fullscreen mode Exit fullscreen mode

Tip: SNAPS powers the preset compression buttons in the UI.

Step 4: Hide FFmpeg Console Windows (Windows Fix)

Without this, FFmpeg spawns annoying black console windows.

def win_no_window_flags():
    return subprocess.CREATE_NO_WINDOW if sys.platform.startswith("win") else 0

def hidden_ffmpeg_startupinfo():
    if sys.platform.startswith("win"):
        si = subprocess.STARTUPINFO()
        si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
        si.wShowWindow = subprocess.SW_HIDE
        return si
    return None
Enter fullscreen mode Exit fullscreen mode

Step 5: Read Video Duration

We parse FFmpeg output to calculate accurate progress percentages.

def get_video_duration(input_file):
    try:
        result = subprocess.run(
            [FFMPEG_PATH, "-i", input_file],
            stderr=subprocess.PIPE,
            stdout=subprocess.PIPE,
            text=True,
            errors="replace"
        )
        m = re.search(r"Duration: (\d+):(\d+):(\d+\.?\d*)", result.stderr)
        if m:
            h, m_, s = int(m.group(1)), int(m.group(2)), float(m.group(3))
            return h * 3600 + m_ * 60 + s
    except:
        pass
    return None
Enter fullscreen mode Exit fullscreen mode

🎯 This enables real-time progress bars instead of fake timers.

Step 6: Smart Compression Logic

We dynamically calculate CRF based on the desired reduction.

def estimate_crf(reduction):
    return int(23 + (reduction / 100) * 12)
Enter fullscreen mode Exit fullscreen mode

Lower CRF → higher quality
Higher CRF → smaller file

Step 7: VideoConverter (Background Worker)

This class runs FFmpeg safely in a separate thread.

class VideoConverter:
    def __init__(self, input_file, output_file, reduction, mode, codec="libx264", progress_queue=None):
        self.input_file = input_file
        self.output_file = output_file
        self.reduction = reduction
        self.mode = mode
        self.codec = codec
        self.progress_queue = progress_queue
        self.stop_event = threading.Event()
Enter fullscreen mode Exit fullscreen mode

Why this matters

UI never freezes

Conversion can be stopped safely

Progress updates are thread-safe

Step 8: Quality Mode vs Target Size Mode
Quality Priority (CRF-based)

cmd = [
    FFMPEG_PATH, "-y", "-i", self.input_file,
    "-c:v", self.codec,
    "-preset", "fast",
    "-crf", str(estimate_crf(self.reduction)),
    "-c:a", "aac", "-b:a", "128k",
    self.output_file
]
Enter fullscreen mode Exit fullscreen mode

Target Size (2-Pass Encoding)

pass1_cmd = [
    FFMPEG_PATH, "-y", "-i", self.input_file,
    "-c:v", self.codec, "-preset", "fast",
    "-b:v", f"{video_kbps}k", "-pass", "1",
    "-an", "-f", "null", "-"
]
Enter fullscreen mode Exit fullscreen mode

2-pass encoding gives much more accurate file sizes.

Step 9: Parsing FFmpeg Progress

We read FFmpeg’s time= output to calculate percent complete.

m = re.search(r'time=(\d+):(\d+):(\d+\.?\d*)', line)
if m:
    cur_time = h*3600 + m_*60 + s
    percent = (cur_time / self.duration) * 100
Enter fullscreen mode Exit fullscreen mode

🔥 This is the secret behind real progress bars.

Step 10: Building the GUI

Create the main window with ttkbootstrap styling.

self.app = tk.Tk()
self.app.geometry("800x520")
self.app.title(f"{APP_NAME} {APP_VERSION}")
self.style = tb.Style(theme="superhero")
Enter fullscreen mode Exit fullscreen mode

Add input/output selectors:

tb.Entry(input_frame, textvariable=self.video_path).pack(fill="x", expand=True)
tb.Button(input_frame, text="Browse", command=self._select_video)

Step 11: Progress Bar & Controls
self.progress_val = tk.DoubleVar(value=0)
tb.Progressbar(self.app, variable=self.progress_val, maximum=100)
Enter fullscreen mode Exit fullscreen mode

Buttons are automatically enabled/disabled during conversion.

Step 12: Thread-Safe UI Updates

def _update_ui(self):
    while True:
        try:
            msg_type, val = self.progress_queue.get_nowait()
            if msg_type == "progress":
                self.progress_val.set(val)
            elif msg_type == "status":
                self.status_var.set(val)
        except Empty:
            break
    self.app.after(50, self._update_ui)
Enter fullscreen mode Exit fullscreen mode

This keeps everything smooth and crash-free.

Step 13: Run the App

if __name__ == "__main__":
    app = VIDConverterApp()
    app.run()
Enter fullscreen mode Exit fullscreen mode

Final Result

🎉 You now have:

A real desktop video compressor

Accurate progress tracking

Modern UI

Offline FFmpeg processing

Windows EXE support

🔗 Source Code:
https://github.com/rogers-cyber/VideoOptimizer

📦 Download EXE:
https://github.com/rogers-cyber/VideoOptimizer/releases

Top comments (0)