DEV Community

Mate Technologies
Mate Technologies

Posted on

Build a Smart Video Converter in Python with FFmpeg

In this tutorial, we’ll create a desktop video converter using Python, Tkinter, and FFmpeg. This app can compress videos while keeping quality, track progress, and even estimate output file size in real-time. Perfect for beginners who want to learn Python GUI programming and video processing!

We’ll break this into small sections with clear explanations.

  1. Setup: Install Required Libraries

We need a few Python libraries:

pip install ttkbootstrap
Enter fullscreen mode Exit fullscreen mode

ttkbootstrap – gives a modern, themed look to Tkinter apps.

tkinter – built-in Python library for GUI.

subprocess – used to run FFmpeg commands.

Also, download FFmpeg from ffmpeg.org
and note its path (e.g., C:\ffmpeg\bin\ffmpeg.exe).

  1. Basic Imports and Config

Start by importing libraries and setting the path to FFmpeg:

import sys
import os
import subprocess
import threading
from pathlib import Path
import re
import tkinter as tk
from tkinter import filedialog, messagebox
import ttkbootstrap as tb

# Path to FFmpeg executable
FFMPEG_PATH = r"C:\ffmpeg\bin\ffmpeg.exe"
Enter fullscreen mode Exit fullscreen mode

We’ll also define some helper functions for cross-platform compatibility.

  1. FFmpeg Utility Functions

We need functions to hide FFmpeg windows on Windows, check if FFmpeg exists, and read video duration:

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

def check_ffmpeg():
    if not os.path.exists(FFMPEG_PATH):
        messagebox.showerror("FFmpeg Missing", f"FFmpeg not found:\n{FFMPEG_PATH}")
        return False
    return True

def get_video_duration(input_file):
    try:
        result = subprocess.run(
            [FFMPEG_PATH, "-i", input_file],
            stderr=subprocess.PIPE,
            stdout=subprocess.PIPE,
            universal_newlines=True,
            startupinfo=hidden_ffmpeg_startupinfo()
        )
        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

✅ Explanation:

hidden_ffmpeg_startupinfo() hides the console window when running FFmpeg.

check_ffmpeg() ensures FFmpeg exists.

get_video_duration() parses the video duration using FFmpeg’s output.

  1. Video Compression Helpers

We can calculate CRF for quality compression and estimated file size:

def estimate_crf(reduction):
    return int(23 + (reduction / 100) * 12)

def update_estimated_size(video_path, reduction):
    if not video_path:
        return "Estimated size: —"
    size = os.path.getsize(video_path)
    est = size * (1 - reduction / 100)
    return f"Estimated size: {est/1024/1024:.2f} MB"
Enter fullscreen mode Exit fullscreen mode

✅ Explanation:

estimate_crf() calculates a CRF value based on your reduction percentage.

update_estimated_size() previews the compressed file size without running FFmpeg.

  1. Creating the Tkinter GUI

We’ll start building the GUI window:

app = tk.Tk()
style_obj = tb.Style(theme="superhero")
app.title("VID Converter + Smart Compression")
app.geometry("800x560")
Enter fullscreen mode Exit fullscreen mode

ttkbootstrap.Style gives a modern theme.

Set title and size of the app window.

5a. Input and Output Selection

Add input/output file selection:

video_path = tk.StringVar()
output_path = tk.StringVar()

def select_video():
    path = filedialog.askopenfilename(filetypes=[("Video Files", "*.mp4 *.avi *.mov *.mkv")])
    if path:
        video_path.set(path)

def select_output():
    path = filedialog.asksaveasfilename(defaultextension=".mp4", filetypes=[("MP4", "*.mp4"), ("MKV", "*.mkv")])
    if path:
        output_path.set(path)

# GUI
input_frame = tb.Labelframe(app, text="Video Input", padding=10)
input_frame.pack(fill="x", padx=10, pady=5)
tb.Entry(input_frame, textvariable=video_path).pack(side="left", fill="x", expand=True, padx=5)
tb.Button(input_frame, text="Browse", command=select_video).pack(side="left")

output_frame = tb.Labelframe(app, text="Output File", padding=10)
output_frame.pack(fill="x", padx=10, pady=5)
tb.Entry(output_frame, textvariable=output_path).pack(side="left", fill="x", expand=True, padx=5)
tb.Button(output_frame, text="Browse", command=select_output).pack(side="left")
Enter fullscreen mode Exit fullscreen mode

✅ Explanation:

video_path and output_path store file paths.

Buttons open file dialogs to select input and output files.

5b. File Size Reduction Slider

Add a slider for reduction percentage:

reduce_var = tk.IntVar(value=20)
estimated_size_var = tk.StringVar(value="Estimated size: —")

def update_slider(val):
    estimated_size_var.set(update_estimated_size(video_path.get(), int(val)))

size_frame = tb.Labelframe(app, text="Reduce File Size", padding=10)
size_frame.pack(fill="x", padx=10, pady=5)
tb.Scale(size_frame, from_=5, to=80, orient="horizontal",
         variable=reduce_var, command=update_slider).pack(fill="x", padx=10)
tb.Label(size_frame, textvariable=estimated_size_var).pack(anchor="w", pady=3)
Enter fullscreen mode Exit fullscreen mode

✅ Explanation:

Slider ranges from 5–80% reduction.

Updates estimated size in real-time.

  1. Start Video Conversion

We can run FFmpeg in a background thread to avoid freezing the UI:

def run_ffmpeg():
    if not check_ffmpeg():
        return
    if not video_path.get() or not output_path.get():
        messagebox.showerror("Error", "Select input and output files.")
        return

    duration = get_video_duration(video_path.get())
    reduction = reduce_var.get()

    cmd = [
        FFMPEG_PATH, "-i", video_path.get(),
        "-c:v", "libx264",
        "-preset", "fast",
        "-crf", str(estimate_crf(reduction)),
        "-c:a", "aac",
        "-b:a", "128k",
        output_path.get()
    ]

    subprocess.run(cmd, startupinfo=hidden_ffmpeg_startupinfo())
    messagebox.showinfo("Done", "Conversion finished!")

def start_conversion():
    threading.Thread(target=run_ffmpeg, daemon=True).start()
Enter fullscreen mode Exit fullscreen mode

✅ Explanation:

Runs FFmpeg without blocking the GUI.

Uses crf for quality-based compression.

6a. Add Start Button

start_btn = tb.Button(app, text="Start ▶", bootstyle="success", command=start_conversion)
start_btn.pack(pady=10)
Enter fullscreen mode Exit fullscreen mode

Clicking this starts conversion in the background.

  1. Final Touches

You can add a progress bar, stop button, and “About” info to make it more user-friendly. Then run the main loop:

app.mainloop()
Enter fullscreen mode Exit fullscreen mode
  1. Result

Now you have a fully functional offline video converter!

Supports MP4, AVI, MOV, MKV

Smart compression (Target Size / Quality)

Live estimated size

Simple modern GUI

You can clone the full project here:
https://github.com/rogers-cyber/VIDConverter

✅ Tips for Beginners

Experiment with the CRF value to balance size vs quality.

Use threading to avoid freezing GUI during long conversions.

Explore ttkbootstrap themes to customize UI appearance.

Top comments (0)