PhotoEditor

macOS Tkinter app for bulk product-photo processing. Background removal via rembg, smart 1000×1000 crop, SKU banner stamping, webhook export. Point at a folder, hit Process — 200 photos in 5 minutes.

github.com/jaded423/photoEditor · Python 3.13 · Tkinter · MIT

Why it exists

Product photography for a multi-SKU catalog. Dozens of new SKUs per week. Each photo needs background removed, square-cropped, scaled to 1000×1000, optionally stamped with the SKU filename. Doing this by hand in Photoshop is death.

Built originally for an internal team that ships hundreds of new product photos per week. Open-sourced because the pattern is generic — anyone running a catalog with manual photo-prep grind can use it as-is or adapt it.

The pipeline

  1. Background removalrembg with the birefnet-general model. Filename prefix smalls skips this step (pile shots, bulk product fills the frame already).
  2. Bulk detection — if rembg kept >85% coverage (no clear subject) OR <5% coverage (over-removed), falls back to the original. Saves the "pile of nugs" case.
  3. Component cleanup — keeps everything connected to the main subject. Strips only small distant fragments (alpha > 30 threshold).
  4. Smart resize — crops to subject bounding box, scales to 900×900, centers on a 1000×1000 transparent canvas with a 50px border.
  5. Outputs three folderspendingProducts/ (clean square PNG, catalog-ready), edited/ (same image + SKU filename banner), original/ (source files moved here, nothing destroyed).
  6. Webhook ping (optional) — POST to a configured URL with API-key header on batch completion. Drives downstream Odoo / Shopify / CMS sync.

What makes it different

The hard part wasn't calling rembg — it was the heuristic for "did the background-removal step actually work, or did it make things worse?" That came out of pairing with Claude on real failure cases. Two patterns broke naive bg-removal:

The coverage-percentage fallback turned a manual triage step into one heuristic. The component-cleanup threshold preserved scatter while still stripping JPEG artifacts.

Photo type matrix

TypeExample filenameBehavior
Single subjectjar-001.jpgFull pipeline: bg removal + resize + banner
Bulk / pilesmalls-batch-3.jpgSkip bg removal (filename prefix), resize only
Scattered subjectjar-with-scatter.jpgFull pipeline, keeps scattered pieces (cleanup tuned)
Over-removed by rembg(auto-detected)Falls back to original

Stack

Python 3.13 Tkinter rembg birefnet-general Pillow PyInstaller requests Inter Bold

Native macOS Tkinter UI — no Electron, no web frontend, no 200MB Chromium runtime. Standalone .app built with PyInstaller (spec bundles font, model paths, icon). Settings live in ~/Library/Application Support/CombinedProcessor/settings.json, outside the repo by design.

Architecture

photoEditor/
├── combined_processor.py    # Core pipeline: rembg → cleanup → resize → banner
├── network_utils.py         # Webhook HTTP client
├── tk_app/
│   ├── app.py               # Tkinter GUI
│   └── settings.py          # JSON settings persistence
├── build_app.sh             # PyInstaller build script (→ dist/PhotoEditor.app)
├── PhotoEditor.icns         # App icon
├── PhotoEditor.spec         # PyInstaller spec (bundled font, model)
├── Inter-Bold.ttf           # Banner font
└── requirements.txt         # 37 deps

Configuration

Default folder

Where the file picker opens by default.

Webhook URL

POSTed to on batch completion — e.g. https://hooks.example.com/photos-done.

API key + header

Sent as the value of a configurable header (default: Authorization).

Logs

Rolling debug logs under ~/Library/Logs/CombinedProcessor/.

Install

End users (Mac, no Python required)

Download the standalone .app bundle and follow the macOS Gatekeeper walkthrough in INSTALL.md. The app is unsigned (no Apple Developer Program enrollment), so first launch needs Privacy & Security → Open Anyway. One-time setup; subsequent launches are normal.

Developers (build from source)

git clone https://github.com/jaded423/photoEditor.git
cd photoEditor

# Requires Python 3.13
./build_app.sh           # → dist/PhotoEditor.app
./build_app.sh --clean   # clean build artifacts first

# Or run directly without packaging
./run_python.sh

AI-assisted, end-to-end

Built through iterative dialogue with Claude. Same approach behind everything on this site — see the philosophy for more, or what I'm building right now.