Ever shared a photo online without realizing it contains hidden data like GPS location, camera model, or timestamps?
In this tutorial, weβll build MetaClean β a Python desktop app that removes EXIF metadata from images while keeping visual quality intact.
π Try MetaClean here:
https://gum.new/gum/cmki9hb0s000204l402y17n9c
By the end, youβll have:
β
A drag-and-drop desktop app
β
Batch image processing
β
Metadata preview
β
Safe overwrite with backups
β
Exportable cleanup reports
Letβs get started.
π Requirements
Install Python 3.9+ and then:
pip install pillow ttkbootstrap tkinterdnd2
What these do:
pillow β image handling
tkinter β GUI (built into Python)
ttkbootstrap β modern UI styling
tkinterdnd2 β drag & drop support
π Project Structure
Create a new folder:
metaclean/
βββ app.py
βββ logo.ico (optional)
All code goes inside app.py.
Step 1 β Imports & Constants
Start with basic imports and app metadata.
import os, sys, threading, json, platform, shutil
import tkinter as tk
from tkinter import filedialog, messagebox
from datetime import datetime
from PIL import Image, ExifTags, ImageOps
import ttkbootstrap as tb
Optional drag & drop:
try:
from tkinterdnd2 import TkinterDnD, DND_FILES
DND_ENABLED = True
except ImportError:
DND_ENABLED = False
Why?
We gracefully fall back if drag & drop isnβt installed.
Step 2 β Create the Main App Class
Using a class keeps everything organized.
class MetaCleanApp:
APP_NAME = "MetaClean"
APP_VERSION = "2.0.0"
SUPPORTED_EXT = (".png", ".jpg", ".jpeg", ".webp", ".bmp", ".tiff")
Step 3 β Initialize the Window
def __init__(self):
if DND_ENABLED:
self.root = TkinterDnD.Tk()
else:
self.root = tk.Tk()
self.style = tb.Style(theme="darkly")
self.root.title("MetaClean v2.0")
self.root.geometry("1000x620")
Here we:
Create the root window
Apply dark theme
Set size and title
Step 4 β Logging & App Directories
We store logs and backups safely in the userβs home folder.
self.home_dir = os.path.expanduser("~")
self.log_dir = os.path.join(self.home_dir, ".metaclean")
os.makedirs(self.log_dir, exist_ok=True)
Logging helper:
def log_error(self, msg):
with open(self.log_file, "a") as f:
f.write(f"{datetime.now()} {msg}\n")
Step 5 β Reading Image Metadata
def read_metadata(self, path):
img = Image.open(path)
exif = img.getexif()
if not exif:
return "No metadata found."
lines = []
for tag, val in exif.items():
name = ExifTags.TAGS.get(tag, tag)
lines.append(f"{name}: {val}")
return "\n".join(lines)
Step 6 β Removing Metadata Safely
def strip_metadata(self, src, dst):
img = Image.open(src)
img = ImageOps.exif_transpose(img)
data = list(img.getdata())
clean = Image.new(img.mode, img.size)
clean.putdata(data)
clean.save(dst, icc_profile=img.info.get("icc_profile"))
This guarantees all metadata is removed.
Step 7 β Image Selection
def add_images(self):
files = filedialog.askopenfilenames()
for f in files:
self.image_listbox.insert(tk.END, f)
Step 8 β Batch Cleanup Logic
def run_cleanup(self):
files = self.image_listbox.get(0, tk.END)
for src in files:
dst = src + "_clean.jpg"
self.strip_metadata(src, dst)
Step 9 β Export Reports
JSON:
json.dump(self.cleanup_results, f, indent=2)
TXT:
f.write(f"{k}: {v}\n")
Step 10 β Run the App
if __name__ == "__main__":
app = MetaCleanApp()
app.run()
β Final Result
You now have a full desktop metadata cleaner with batch support, previews, backups, and exportable reports.
If youβd like to try the polished build of MetaClean v2:

Top comments (0)