DEV Community

Mate Technologies
Mate Technologies

Posted on

πŸ•’ Build a Professional Batch File Renamer with Python (Tkinter + ttkbootstrap)

AutoDateInserter is a desktop app that batch-renames files by prepending or appending timestamps.
It supports preview, undo, dry-run, pause/resume, drag & drop, and progress tracking.

This tutorial walks through the app step by step, making it beginner-friendly and easy to understand.

🧠 What You’ll Learn

How to structure a class-based Tkinter app

How to safely batch-rename files

How to add previews, undo, and dry-run

How to keep the UI responsive with threads + queues

How to design a clean UI using ttkbootstrap

πŸ“¦ Project Structure

Everything lives in one Python file for simplicity:

AutoDateInserter.py
Enter fullscreen mode Exit fullscreen mode

Dependencies:

pip install ttkbootstrap tkinterdnd2
Enter fullscreen mode Exit fullscreen mode

1️⃣ Imports & Feature Detection

We start by importing everything we need.

import os, sys, threading, platform, json, time
import tkinter as tk
from tkinter import filedialog
from datetime import datetime
import ttkbootstrap as tb
from ttkbootstrap.constants import *
import queue
import logging
Enter fullscreen mode Exit fullscreen mode

Optional Drag & Drop Support

try:
    from tkinterdnd2 import TkinterDnD, DND_FILES
    DND_ENABLED = True
except ImportError:
    DND_ENABLED = False
Enter fullscreen mode Exit fullscreen mode

πŸ’‘ Why?
If tkinterdnd2 isn’t installed, the app still works β€” just without drag & drop.

2️⃣ App Class & Window Setup

We wrap the entire app in a class to keep things clean and scalable.

class AutoDateInserterApp:
    APP_NAME = "AutoDateInserter"
    APP_VERSION = "1.2.0"
Enter fullscreen mode Exit fullscreen mode

Create the Window

self.root = TkinterDnD.Tk() if DND_ENABLED else tk.Tk()
tb.Style(theme="darkly")
self.root.title(f"{self.APP_NAME} v{self.APP_VERSION}")
self.root.geometry("1120x700")
Enter fullscreen mode Exit fullscreen mode

🎨 ttkbootstrap instantly gives us a modern dark theme.

3️⃣ App Storage & Logging

We store logs and undo history in a user-safe directory.

self.app_dir = os.path.join(
    os.path.expanduser("~"),
    "AppData", "Local", self.APP_NAME
)
os.makedirs(self.app_dir, exist_ok=True)
Enter fullscreen mode Exit fullscreen mode

Enable Logging

log_file = os.path.join(self.app_dir, "rename.log")
logging.basicConfig(
    filename=log_file,
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s"
)
Enter fullscreen mode Exit fullscreen mode

🧠 This lets us debug rename failures later.

4️⃣ App State & Thread Safety

self.rename_map = {}
self.start_time = None
self.pause_event = threading.Event()
self.pause_event.set()
self.is_paused = False
Enter fullscreen mode Exit fullscreen mode

Thread-Safe UI Updates

self.ui_queue = queue.Queue()
Enter fullscreen mode Exit fullscreen mode

πŸ’‘ Important:
Tkinter is not thread-safe, so background threads send updates through a queue.

5️⃣ Tkinter Variables (Reactive UI)

self.mode_var = tk.StringVar(value="prepend")
self.recursive_var = tk.BooleanVar(value=False)
self.dry_run_var = tk.BooleanVar(value=False)

self.progress_var = tk.IntVar(value=0)
self.eta_var = tk.StringVar(value="ETA: --:--")
self.speed_var = tk.StringVar(value="0.00 files/sec")
Enter fullscreen mode Exit fullscreen mode

These automatically update UI widgets when changed.

6️⃣ Utility Functions
Timestamp Generator

def timestamp(self):
    return datetime.now().strftime("%Y%m%d_%H%M%S")
Enter fullscreen mode Exit fullscreen mode

Safe Rename (Avoid Overwrites)

def safe_name(self, path):
    base, ext = os.path.splitext(path)
    i = 1
    new = path
    while os.path.exists(new):
        new = f"{base} ({i}){ext}"
        i += 1
    return new
Enter fullscreen mode Exit fullscreen mode

βœ” Prevents accidental file loss.

7️⃣ Building the UI (High-Level)

The UI is broken into logical sections:

Title

File list

Options

Preview

Controls

def _build_ui(self):
    tb.Label(self.root, text=self.APP_NAME,
             font=("Segoe UI", 22, "bold")).pack()
Enter fullscreen mode Exit fullscreen mode

File List + Scrollbar

self.listbox = tk.Listbox(box, selectmode=tk.EXTENDED)
self.listbox.pack(side="left", fill="both", expand=True)
Enter fullscreen mode Exit fullscreen mode

Drag & Drop Support

if DND_ENABLED:
    self.listbox.drop_target_register(DND_FILES)
    self.listbox.dnd_bind("<<Drop>>", self.on_drop)
Enter fullscreen mode Exit fullscreen mode

8️⃣ Adding Files & Folders
Add Files

def add_files(self):
    for f in filedialog.askopenfilenames():
        self.listbox.insert(tk.END, f)
Enter fullscreen mode Exit fullscreen mode

Add Folder (Recursive Optional)

def collect_files(self, folder):
    if self.recursive_var.get():
        for r, _, files in os.walk(folder):
            for f in files:
                self.listbox.insert(tk.END, os.path.join(r, f))
Enter fullscreen mode Exit fullscreen mode

9️⃣ Preview Before Renaming (Critical Safety Feature)

def build_preview(self):
    self.rename_map.clear()
    ts = self.timestamp()
Enter fullscreen mode Exit fullscreen mode

Generate New Names

new = (
    f"{ts}_{base}{ext}"
    if self.mode_var.get() == "prepend"
    else f"{base}_{ts}{ext}"
)
Enter fullscreen mode Exit fullscreen mode

Show Preview

self.preview.insert(
    tk.END, f"{name} β†’ {os.path.basename(dst)}\n"
)
Enter fullscreen mode Exit fullscreen mode

πŸ‘€ Users see exactly what will happen before committing.

πŸ”Ÿ Renaming Files (Threaded)

threading.Thread(
    target=self.rename_files,
    daemon=True
).start()
Enter fullscreen mode Exit fullscreen mode

Rename Logic

os.rename(src, dst)
undo[dst] = src
Enter fullscreen mode Exit fullscreen mode

Progress + ETA

speed = i / elapsed
eta = (total - i) / speed
Enter fullscreen mode Exit fullscreen mode

1️⃣1️⃣ Pause / Resume Support
self.pause_event.wait()

def toggle_pause(self):
    self.is_paused = not self.is_paused
Enter fullscreen mode Exit fullscreen mode

βœ” Useful for large batches.

1️⃣2️⃣ Undo System (JSON-Based)

undo_file = f"undo_{timestamp}.json"
json.dump(undo, f, indent=2)

Restore Files
os.rename(dst, src)
Enter fullscreen mode Exit fullscreen mode

πŸ”₯ This makes the app safe for production use.

1️⃣3️⃣ Why This Architecture Works

βœ… Class-based
βœ… Thread-safe
βœ… Undoable
βœ… Preview-first
βœ… Beginner-readable
βœ… Production-ready

πŸš€ Final Thoughts

AutoDateInserter shows how far Tkinter can go when combined with:

Good structure

Safety features

Modern styling

Thoughtful UX

If you’re learning Python desktop apps β€” this is a perfect real-world project.

πŸ”— Source Code

GitHub:
πŸ‘‰ https://github.com/rogers-cyber/AutoDateInserter

Top comments (0)