Complete photo deletion on disk, snappy lightbox, keyboard shortcuts, prefetching, and perfected documentation

This commit is contained in:
drjones
2026-07-20 21:08:07 -07:00
parent 5208c5ecef
commit c07fb9d971
3 changed files with 286 additions and 90 deletions

157
README.md
View File

@@ -1,116 +1,93 @@
# PHOTON — local photo intelligence console
A local web app that walks through a photo folder, has an Ollama vision model
describe + categorize each photo, and embeds the result as standard metadata
inside the photo file so everything becomes searchable (Spotlight, Photos,
Lightroom, etc.). Nothing is ever deleted, moved, or renamed.
A local web application that scans your photo collection, uses local Ollama vision models to describe + categorize each photo, and embeds standard metadata inside photo files so everything becomes searchable across Spotlight, Finder, Photos, Lightroom, etc.
## Run it — zero config
Original photos remain completely untouched unless you explicitly choose to edit or delete them.
Drop this whole folder *inside* the photo collection you want to organize,
then just run it:
---
## Quick Start (Zero-Config)
Drop this directory inside the photo collection you want to organize, then run:
```bash
cd "your-photo-library/photon"
python3 server.py
# then open http://localhost:8765
# Open http://localhost:8765 in your browser
```
It auto-detects the folder to scan as **the parent of wherever this app
lives** — so if you dropped it into `~/Pictures/Vacation2026/photon`, it
defaults to scanning `~/Pictures/Vacation2026`. The folder it last scanned is
also remembered automatically (`photon_folder.json`), so on every future
launch it just picks up where you left off — no retyping paths.
### Automatic Folder Detection & Overrides
- **Auto-Detection**: Scans the parent directory of wherever `server.py` lives.
- **Persistence**: Remembers your last scanned folder (`photon_folder.json`).
- **CLI & Environment Overrides**:
```bash
python3 server.py /path/to/photos # CLI argument override
PHOTON_FOLDER=/path/to/photos python3 server.py # Environment variable override
PHOTON_PORT=8766 python3 server.py # Custom port
```
Want to point it somewhere else? Any of these work, in priority order:
```bash
python3 server.py /path/to/photos # one-off CLI override
PHOTON_FOLDER=/path/to/photos python3 server.py # env var override
```
Or just type a new path into the folder field in the Console tab and hit
**Scan Directory** — that becomes the new remembered default too. Running
two libraries at once? `PHOTON_PORT=8766 python3 server.py` avoids a port clash.
### Prerequisites
- **Ollama** running locally with a vision model (e.g. `ollama pull qwen3.5:9b` or `ollama pull qwen3.5:4b`).
- **`exiftool`** (installed via Homebrew: `brew install exiftool`).
- **macOS** (`sips` built-in for fast downscaling & pixel integrity verification).
- **`ffmpeg` / `ffprobe`** (optional: for video frame extraction & video thumbnails).
Requires: Ollama running with a vision model, `exiftool` (installed via
brew), macOS (`sips` for fast downscaling), and `ffmpeg`/`ffprobe` (for video
thumbnails and tagging — optional, everything else works without it).
---
## How it works
## Key Features & Capabilities
1. **Scan** — recursively finds images (`jpg/jpeg/png/heic/tiff/webp/bmp`).
Videos are counted but skipped. Hidden files and `._*` AppleDouble sidecars
are never touched.
2. **Analyze** — each photo is downscaled with `sips` to a temp copy (original
is only ever *read*), sent to the chosen Ollama vision model with a JSON
schema that forces `{description, category}` output.
3. **Write** — `exiftool` embeds:
- `EXIF:ImageDescription`, `IPTC:Caption-Abstract`, `XMP-dc:Description` — the description
- `XMP-dc:Subject` + `IPTC:Keywords` — the category, plus a `photon-tagged` marker
- Writes use exiftool's temp-file + atomic-rename mode; file dates preserved with `-P`.
4. **Journal** — every processed photo is appended to `photon_journal.jsonl`
(path, description, category, model, timing). Restarting the app resumes
where it left off ("skip already tagged").
### 1. Multithreaded 3-Stage Pipeline
1. **Downscale Stage**: Uses macOS `sips` to create fast temp copies (original files are read-only).
2. **Inference Stage**: Calls local Ollama vision models to determine `{description, category}`.
3. **Write Stage**: Uses `exiftool` to embed metadata with atomic renames and date preservation (`-P`).
## The 10 categories
### 2. Search & Interactive Library Browser
- **Instant Search**: Full-text keyword search across descriptions, categories, filenames, and paths.
- **Filter Chips**: 1-click taxonomy pills (`People`, `Animals`, `Screenshots`, `Vehicles`, `Objects`, etc.).
- **Sub-Views**:
- **Tagged**: Explore and filter all processed photos.
- **Untagged**: Browse photos awaiting tagging.
- **Failed**: View and retry failed operations.
- **Videos**: Frame extraction, AI tagging, and native video player.
- **Duplicates**: Perceptual hash index (pHash) visual duplicate grouping.
People · Animals · Food & Drink · Nature & Outdoors · City & Buildings ·
Vehicles · Screenshots & Documents · Events & Parties · Objects & Stuff ·
Art & Miscellaneous
### 3. Full-Screen Lightbox & Organic Browsing
- **Snappy Viewer**: Full-resolution image/video lightbox with metadata inspector.
- **0ms Image Prefetching**: Pre-caches adjacent images in memory for instant switching.
- **Keyboard Shortcuts**:
- `←` / `→` : Navigate previous / next photo.
- `Esc` : Close Lightbox.
- `Delete` / `Backspace` : Delete current photo on disk.
## Smart router (recommended)
### 4. Disk Photo Deletion & Management
- **Single & Bulk Deletion**: Click "Delete Photo" or select multiple photos to permanently delete them on disk (or send to macOS Trash).
- **Automated Cleanup**: Deleting a photo purges its entry from `photon_journal.jsonl`, removes `_organized/` symlinks, and clears thumbnail & view caches.
With the **smart router** toggle on, a fast scout model (glm-ocr, 1.1B) first
classifies each image as *screenshot* or *photo*, then hands it to the right
describer with a specialized prompt. Screenshots also get an **OCR text embed**:
glm-ocr transcribes the visible words and they're appended to the description
(`… | text: …`), so you can find a screenshot by searching the exact words in it.
### 5. Bulk Operations & Export
- **Select Mode**: Range selection via `Shift`-Click or "Select All Matching".
- **Exporting**: Export catalog metadata to CSV or JSON.
- **ZIP Downloads**: Stream original-quality files into a single ZIP archive.
Tested defaults: scout `glm-ocr` (6/6 routing accuracy) → describer
`qwen3.5:4b` for both branches (8/8 accuracy, reads product labels correctly).
### 6. Trust & Safety Safeguards
- **Verify Pixel Integrity**: Option to double-hash image pixels via raw BMP conversions before and after writes. Guarantees 100% zero image corruption.
- **Organized Symlinks**: Generates relative portable Finder aliases in `[folder]/_organized/[category]/[name]`.
- **Undo All Tags**: One-click exiftool pass to cleanly remove all `photon-tagged` metadata and categories.
- **Persistent Failure Retries**: Saves errored paths to `photon_failures.jsonl` with 1-click retry.
## Settings that affect speed
### 7. Quality Control & Custom Taxonomies
- **Blind Accuracy Grader**: Interactive 100-photo audit mode to score description quality.
- **Custom Categories**: Edit, add, or customize category schemas (`photon_categories.json`).
| Setting | Effect |
|---|---|
| Vision model | `glm-ocr` (1.1B) ≈ 7 s/photo; `qwen3.5:9b` slower but smarter |
| Image feed resolution | 512 px is fastest; originals are untouched either way |
| Description length | brief/standard/detailed — caps the model's output tokens |
| Keep-alive | "forever" keeps the model in RAM between photos (fastest) |
| Dry run | full pipeline but no metadata written |
| Keep `_original` backups | exiftool keeps a backup copy of every file (doubles disk usage) |
---
## Searching afterwards
## Standard Metadata Specifications
Spotlight: just type a word from a description in Finder search.
Or from terminal: `mdfind -onlyin "/path/to/your/photos" "scooter"`
Or grep the journal: `grep -i scooter photon_journal.jsonl`
`exiftool` embeds the following tags into image files:
- `EXIF:ImageDescription`, `IPTC:Caption-Abstract`, `XMP-dc:Description` — Description string
- `XMP-dc:Subject`, `IPTC:Keywords` — Category string + `photon-tagged` keyword
Or use the app itself — the **Search & Edit** tab is a full photo library browser:
---
- **Tagged** — instant multi-word search across description/category/filename/path,
filter by category (click a chip), date range, or file type (photos/videos),
sort by newest/name/category. Click any result for a full-resolution
viewer + editor with Save / AI Redo / Reveal-in-Finder.
- **Untagged** / **Failed** — see what's left to do or what errored, tag or
retry one at a time without leaving the grid.
- **Videos** — thumbnails via `ffmpeg` frame-grab, native playback, AI tagging
off an extracted frame.
- **Duplicates** — one-time background scan builds a perceptual-hash index and
groups exact visual matches (re-saves, burst duplicates). No auto-delete —
just Reveal-in-Finder per copy so you decide.
## License & Safety Notice
**Selecting & downloading photos in bulk:** click **Select** to enter select
mode, then:
- click a photo to toggle it, **shift-click** to select a range
- **Select all loaded** / **Select all N matching** (grabs everything matching
your current search, not just what's rendered) / **Clear selection**
- **Download selected (ZIP)** or **Download all matches (ZIP)** — streams a
real zip of the original files, byte-for-byte, no re-compression (capped at
3000 files per zip)
- **Apply category to selected**, or **Export** the set as CSV/JSON
## Appearance
Settings & Safety → **Appearance**: pick an accent color (native color
picker), grid density (compact/comfortable/large), and results-per-page.
Saved per-browser, doesn't touch anything on disk.
Original photo files are never deleted or modified unless you explicitly trigger **Delete Photo**, **Metadata Writing**, or **Undo All Tags**.