An Electron desktop app that uses face recognition to automatically tag people in your SmugMug photo library.
SmugMug Meta Tagger connects to your SmugMug account, downloads photos at a manageable resolution, and lets you train a face recognition model by labeling people. Once trained, it automatically identifies those people across all your galleries and uploads Person:Name keywords back to SmugMug β making your entire library searchable by face.
- π OAuth 1.0a authentication with SmugMug's API v2 (credentials encrypted at rest via OS keychain)
- πΈ Tiered photo download β thumbnails for browsing, medium-res (~800px) for face detection; full-res never required
- π Face detection powered by
face-api.js(SSD MobileNet v1 + 68-point landmarks + 128-dim embeddings) - π Interactive face trainer β click on detected faces and assign a name; 3+ samples per person recommended
- π€ Auto-tagger β runs recognition across all scanned photos, shows confidence scores, lets you approve before uploading
- π·οΈ Keyword upload β writes
Person:Nametags to SmugMug, preserving all existing non-person keywords - πΎ Local SQLite database tracking albums, images, face descriptors, and tag upload status
- β‘ Performance β virtualised result lists, parallel downloads (5 concurrent), batch face processing with event-loop yields
| Layer | Technology |
|---|---|
| Desktop Framework | Electron 41 + Electron Forge |
| Build Tool | Vite 5 + TypeScript 5.4 |
| Frontend | React 19 |
| Styling | Vanilla CSS (dark theme, glassmorphism) |
| SmugMug Auth | OAuth 1.0a (oauth-1.0a) |
| Face Detection | @vladmandic/face-api + TensorFlow.js Node |
| Canvas (Node.js) | @napi-rs/canvas |
| Local Database | better-sqlite3 (SQLite) |
| Credential Storage | Electron safeStorage (OS keychain) |
- Node.js β₯ 18 (tested on 25.x)
- npm β₯ 9
- A SmugMug account with API access
- A SmugMug API application β register at api.smugmug.com
# Clone the repository
git clone https://github.com/your-username/Smugmug_Meta_Tagger.git
cd Smugmug_Meta_Tagger
# Install dependencies
npm installNote: The
models/directory containing face-api.js model weights (~12 MB) is included in the repository. No additional download is needed.
npm startThis launches the app in development mode with:
- Vite dev server for the React renderer (hot module replacement)
- Electron DevTools open automatically
# Package the app (no installer)
npm run package
# Create platform-specific installers
npm run makeOutput is placed in the out/ directory.
| Platform | Output |
|---|---|
| macOS | .zip archive |
| Windows | Squirrel installer (.exe) |
| Linux | .deb and .rpm packages |
- Go to api.smugmug.com and create an application
- Note your API Key (Consumer Key) and API Secret (Consumer Secret)
- Launch the app (
npm start) - On the Connect page, enter your API Key and API Secret
- Click Connect to SmugMug β your browser will open the SmugMug authorization page
- Authorize the app, copy the 6-digit verifier code shown by SmugMug
- Paste the code back into the app and click Complete Authorization
Your credentials are encrypted and stored locally β you won't need to repeat this.
Sync Albums β Download Thumbnails β Scan for Faces β Label Faces β Run Auto-Tagger β Upload Tags
On the Galleries page, click Sync Albums to fetch your full SmugMug library. Album metadata (title, image count) is stored in the local database.
Click Download on any album card. Small thumbnails (~150px) are saved locally for display β fast and lightweight.
Click Scan Faces on an album. The app will:
- Download medium-resolution images (~800px) for that album
- Run face detection on every image (batches of 5)
- Store bounding boxes and 128-dimensional face embeddings in the database
Progress is shown in real time.
Click Train on a scanned album to open the Face Trainer:
- Navigate through photos that contain detected faces
- The face bounding box area shows where faces were found
- Assign a name to each face using the label form
- Aim for 3β5 labeled samples per person for good accuracy; more is better
Go to the Auto-Tagger page and click Run Auto-Tagger. The engine:
- Builds a
FaceMatcherfrom all stored training descriptors - Runs recognition against every face-detected photo that hasn't been tagged yet
- Shows results with colour-coded confidence: π’ β₯70%, π‘ 50β70%, π΄ <50%
Review the matches, use β High Confidence to auto-select reliable results, then click Upload Tags.
The app sends a PATCH request for each selected photo, adding Person:Name keywords while preserving all existing tags. A 200ms delay is applied between requests to respect SmugMug's rate limits.
All local data is stored in Electron's userData directory:
| macOS | ~/Library/Application Support/smugmug_meta_tagger/smugmug-data/ |
|---|---|
| Windows | %APPDATA%\smugmug_meta_tagger\smugmug-data\ |
| Linux | ~/.config/smugmug_meta_tagger/smugmug-data/ |
smugmug-data/
βββ smugmug_tagger.db # SQLite database (albums, images, face data)
βββ credentials.json # Encrypted OAuth tokens
βββ settings.json # App preferences (threshold, concurrency, etc.)
βββ thumbnails/ # ~150px thumbnails, organised by albumKey
β βββ {albumKey}/
βββ medium/ # ~800px images for face detection
βββ {albumKey}/
Storage estimate: ~1β5 MB per 1,000 thumbnails; ~50β200 MB per 1,000 medium-res images.
Open the Settings page to adjust:
| Setting | Default | Description |
|---|---|---|
| Recognition Threshold | 0.6 |
Face match distance threshold (lower = stricter, range 0.4β0.8) |
| Concurrent Downloads | 5 |
Parallel download connections |
| Data Directory | (userData) | Where photos and the database are stored |
- Clear Training Data β removes all face descriptors and people (albums/images preserved)
- Reset Database β drops and recreates all tables (full wipe)
Smugmug_Meta_Tagger/
βββ src/
β βββ main/ # Electron main process (Node.js)
β β βββ index.ts # App entry, window creation, service init
β β βββ preload.ts # contextBridge IPC exposure
β β βββ ipc-handlers.ts # IPC handler registration
β β βββ services/
β β βββ oauth.ts # OAuth 1.0a flow + safeStorage
β β βββ smugmug-api.ts # SmugMug API v2 client (with retry)
β β βββ downloader.ts # Tiered photo downloader (with retry)
β β βββ face-engine.ts # face-api.js detection + recognition
β β βββ database.ts # SQLite schema + queries
β βββ renderer/ # React renderer process
β β βββ index.tsx # React 19 root mount
β β βββ App.tsx # Sidebar layout + router + providers
β β βββ index.css # Design system (dark theme)
β β βββ pages/
β β β βββ LoginPage.tsx # OAuth connect flow
β β β βββ GalleryBrowser.tsx # Album grid + download/scan
β β β βββ FaceTrainer.tsx # Per-photo face labeling
β β β βββ AutoTagger.tsx # Review matches + upload
β β β βββ SettingsPage.tsx # Preferences + stats
β β βββ components/
β β βββ Toast.tsx # Toast notification system
β β βββ ErrorBoundary.tsx # React error boundary
β β βββ VirtualList.tsx # Windowed list for large datasets
β βββ shared/
β βββ types.ts # Shared TypeScript interfaces + helpers
βββ tests/ # Spec-Driven Development (BDD)
β βββ features/ # Gherkin specification files (.feature)
β βββ step_defs/ # Cucumber step definitions
βββ models/ # face-api.js model weights (~12 MB)
β βββ ssd_mobilenetv1_model*
β βββ face_landmark_68_model*
β βββ face_recognition_model*
βββ forge.config.ts # Electron Forge build config
βββ vite.main.config.ts
βββ vite.preload.config.ts
βββ vite.renderer.config.ts
βββ tsconfig.json
βββ package.json
Person tags written to SmugMug use the prefix Person::
Person:Jane Smith; Person:John Doe; vacation; hawaii
- Existing non-person keywords are always preserved
- Old
Person:tags for the same image are replaced (not duplicated) - Tags are semicolon-delimited, matching SmugMug's format
This project uses Spec-Driven Development (SDD) to formalize requirements. Testing is powered by Cucumber.js.
The tests are defined in human-readable Gherkin specifications (.feature files) and implemented using TypeScript step definitions.
# Run the BDD test suite
npm run test:bddThis will execute the Cucumber test runner and display the results of all scenarios.
App won't connect to SmugMug
- Verify your API Key and Secret are correct
- Make sure you authorized the app with Full Access + Modify permissions
- Check that your SmugMug account has API access enabled
Face detection finds no faces
- Medium-res images must be downloaded first (click "Scan Faces", not just "Download")
- Ensure photos contain clearly visible faces (not too small, not at extreme angles)
- Very small face bounding boxes (<80px) may not be detected reliably
Auto-tagger shows low confidence
- Add more training samples per person (aim for 5β10 varied photos)
- Try lowering the recognition threshold in Settings (e.g. 0.5)
- Include samples with different lighting, angles, and expressions
npm start fails with native module errors
- Run
npm rebuildto recompile native dependencies (better-sqlite3,@napi-rs/canvas) for your current Electron version - Ensure your Node.js version matches the Electron-packaged Node version
MIT β see LICENSE for details.