DEV Community

Mate Technologies
Mate Technologies

Posted on

Building a Professional Image Converter in Python – Step by Step

You can find the full script here: Image_Convert_BUSINESS_PRO_v2.py

In this tutorial, we’ll build SnapConvert BUSINESS PRO, a professional image converter with a GUI, drag-and-drop support, license validation, history logging, and CSV export.

1️⃣ Importing Libraries

We start by importing all the necessary modules. This includes Python built-ins, GUI libraries, image processing, encryption, and threading.

import os
import sys
import csv
import time
import sqlite3   # for database storage
import hashlib
import base64
from threading import Thread
from PIL import Image, ImageTk
Enter fullscreen mode Exit fullscreen mode

We also handle the resampling library, which is used for resizing images with high quality:

try:
    from PIL import ImageResampling
    RESAMPLE = ImageResampling.LANCZOS
except:
    RESAMPLE = Image.LANCZOS
Enter fullscreen mode Exit fullscreen mode

Finally, we import GUI frameworks and encryption tools:

import ttkbootstrap as tb
from ttkbootstrap.constants import *
from tkinter import filedialog, messagebox, Listbox, Canvas, Scrollbar, simpledialog
from tkinterdnd2 import TkinterDnD, DND_FILES
from cryptography.fernet import Fernet
Enter fullscreen mode Exit fullscreen mode

Tip: ttkbootstrap is like a modern theme for Tkinter, and tkinterdnd2 enables drag-and-drop support.

2️⃣ Application Info and Paths

Define basic app info and important paths:

APP_NAME = "SnapConvert BUSINESS PRO"
APP_VERSION = "2.1"

BASE_DIR = os.path.dirname(sys.argv[0])
DB_NAME = os.path.join(BASE_DIR, "snapconvert.db")
OUTPUT_DIR = os.path.join(BASE_DIR, "converted")
LOG_FILE = os.path.join(BASE_DIR, "conversion.log")
LICENSE_FILE = os.path.join(BASE_DIR, "license.key")
Enter fullscreen mode Exit fullscreen mode

Why: Keeping paths centralized makes it easier to manage files and database locations.

3️⃣ License Management

We protect the app with a license system. Valid licenses are encrypted with Fernet.

SECRET_KEY = base64.urlsafe_b64encode(hashlib.sha256(b"MySuperSecretBusinessKey2026").digest())
fernet = Fernet(SECRET_KEY)
VALID_LICENSES = ["PRO-2026-XYZ123", "PRO-2026-ABC789"]

def encrypt_license(key: str) -> str:
    return fernet.encrypt(key.encode()).decode()

def decrypt_license(enc: str) -> str:
    try:
        return fernet.decrypt(enc.encode()).decode()
    except:
        return None
Enter fullscreen mode Exit fullscreen mode

The app checks for a valid license file or prompts the user to enter one:

def check_license():
    if os.path.exists(LICENSE_FILE):
        with open(LICENSE_FILE,"r") as f:
            encrypted = f.read().strip()
        key = decrypt_license(encrypted)
        if key in VALID_LICENSES:
            return True
    key = simpledialog.askstring("License Required","Enter your license key:")
    if key in VALID_LICENSES:
        with open(LICENSE_FILE,"w") as f:
            f.write(encrypt_license(key))
        messagebox.showinfo("License Activated","License activated successfully!")
        return True
    else:
        messagebox.showerror("Invalid License","License key is invalid. Exiting application.")
        return False
Enter fullscreen mode Exit fullscreen mode

4️⃣ Database for Conversion History

We use SQLite to store the history of converted images:

def init_db():
    conn = sqlite3.connect(DB_NAME)
    c = conn.cursor()
    c.execute("""CREATE TABLE IF NOT EXISTS history(
        id INTEGER PRIMARY KEY,
        name TEXT,
        original TEXT,
        converted TEXT)""")
    conn.commit()
    conn.close()
Enter fullscreen mode Exit fullscreen mode

Insert, fetch, and clear history functions:

def insert_db(name, orig, conv):
    conn = sqlite3.connect(DB_NAME)
    c = conn.cursor()
    c.execute("INSERT INTO history(name, original, converted) VALUES(?,?,?)",(name,orig,conv))
    conn.commit()
    conn.close()
def fetch_db():
    conn = sqlite3.connect(DB_NAME)
    c = conn.cursor()
    c.execute("SELECT name, original, converted FROM history ORDER BY id DESC")
    rows = c.fetchall()
    conn.close()
    return rows
def clear_history():
    conn = sqlite3.connect(DB_NAME)
    c = conn.cursor()
    c.execute("DELETE FROM history")
    conn.commit()
    conn.close()
Enter fullscreen mode Exit fullscreen mode

Tip: Logging all conversions helps users track what they’ve done and export history to CSV.

5️⃣ Logging and Exporting History

We log events to a file:

def log_event(msg):
    with open(LOG_FILE,"a") as f:
        f.write(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] {msg}\n")
Enter fullscreen mode Exit fullscreen mode

And provide a way to export conversion history:

def export_history_csv():
    rows = fetch_db()
    if not rows:
        messagebox.showinfo("Export History","No history to export")
        return
    path = filedialog.asksaveasfilename(defaultextension=".csv", filetypes=[("CSV file","*.csv")])
    if path:
        with open(path,"w", newline="", encoding="utf-8") as f:
            writer = csv.writer(f)
            writer.writerow(["Name","Original Path","Converted Path"])
            writer.writerows(rows)
        messagebox.showinfo("Export History", f"Exported {len(rows)} records")
Enter fullscreen mode Exit fullscreen mode

6️⃣ Image Conversion Worker

We process images in a separate thread for smooth UI responsiveness:

def worker(images, fmt, out, quality, resize, keep, progress, finish):
    os.makedirs(out, exist_ok=True)
    total = len(images)
    count = 0

    for i, path in enumerate(images):
        try:
            with Image.open(path) as img:
                if resize>0:
                    img = img.resize((resize, resize), RESAMPLE)
                if fmt=="JPEG" and img.mode in ("RGBA","P"):
                    img=img.convert("RGB")
                name=os.path.splitext(os.path.basename(path))[0]
                if not keep:
                    name+=f"_{i+1}"
                out_path=os.path.join(out,f"{name}.{fmt.lower()}")
                c=1
                while os.path.exists(out_path):
                    out_path=os.path.join(out,f"{name}_{c}.{fmt.lower()}")
                    c+=1
                params={"quality":quality} if fmt=="JPEG" else {}
                img.save(out_path, fmt, **params)
                insert_db(name,path,out_path)
                count+=1
        except Exception as e:
            log_event(f"Error converting {path}: {e}")
        progress(int((i+1)/total*100))
    finish(count)
Enter fullscreen mode Exit fullscreen mode

Tip: This ensures the UI doesn’t freeze when converting large batches.

7️⃣ Building the GUI

We use ttkbootstrap for a modern look and TkinterDnD for drag-and-drop:

Left panel: File list and buttons to add/remove images

Center panel: Thumbnail gallery of images

Right panel: Settings like format, quality, resize, and conversion button

Bottom: Conversion history table

class App:
    def __init__(self):
        if not check_license(): sys.exit()
        self.root = TkinterDnD.Tk()
        self.root.title(APP_NAME)
        self.root.geometry("1250x750")
        self.style = tb.Style("darkly")

        self.images=[]
        self.thumbs=[]

        self.create_menu()
        self.build_ui()
        self.load_history()
Enter fullscreen mode Exit fullscreen mode

We register the drop target:

self.root.drop_target_register(DND_FILES)
self.root.dnd_bind("<<Drop>>", self.drop)
Enter fullscreen mode Exit fullscreen mode

8️⃣ Running the App

Finally, initialize the database and start the app:

if __name__=="__main__":
    init_db()
    App().run()
Enter fullscreen mode Exit fullscreen mode

✅ That’s it! You now have a fully functional image converter with GUI, drag-and-drop, database history, and licensing.

You can explore the full working version here: Image_Convert_BUSINESS_PRO_v2.py

Top comments (0)