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
Dependencies:
pip install ttkbootstrap tkinterdnd2
1️⃣ Imports & Feature Detection
We start by importing everything we need.
import os, sys, threadi...
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
Dependencies:
pip install ttkbootstrap tkinterdnd2
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
Optional Drag & Drop Support
try:
from tkinterdnd2 import TkinterDnD, DND_FILES
DND_ENABLED = True
except ImportError:
DND_ENABLED = False
💡 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"
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")
🎨 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)
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"
)
🧠 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
Thread-Safe UI Updates
self.ui_queue = queue.Queue()
💡 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")
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")
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
✔ 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()
File List + Scrollbar
self.listbox = tk.Listbox(box, selectmode=tk.EXTENDED)
self.listbox.pack(side="left", fill="both", expand=True)
Drag & Drop Support
if DND_ENABLED:
self.listbox.drop_target_register(DND_FILES)
self.listbox.dnd_bind("<<Drop>>", self.on_drop)
8️⃣ Adding Files & Folders Add Files
def add_files(self):
for f in filedialog.askopenfilenames():
self.listbox.insert(tk.END, f)
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))
9️⃣ Preview Before Renaming (Critical Safety Feature)
def build_preview(self):
self.rename_map.clear()
ts = self.timestamp()
Generate New Names
new = (
f"{ts}_{base}{ext}"
if self.mode_var.get() == "prepend"
else f"{base}_{ts}{ext}"
)
Show Preview
self.preview.insert(
tk.END, f"{name} → {os.path.basename(dst)}\n"
)
👀 Users see exactly what will happen before committing.
🔟 Renaming Files (Threaded)
threading.Thread(
target=self.rename_files,
daemon=True
).start()
Rename Logic
os.rename(src, dst)
undo[dst] = src
Progress + ETA
speed = i / elapsed
eta = (total - i) / speed
1️⃣1️⃣ Pause / Resume Support self.pause_event.wait()
def toggle_pause(self):
self.is_paused = not self.is_paused
✔ 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)
🔥 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