Files
photoanalyzer/webapp

webapp — Photo Analyzer web UI

A local, browser-based UI for the photo_analyzer.py pipeline. Browse and full-text-search the analyzed library, watch analysis runs live, and start/stop runs — reusing the nsfwtag review-app design (dark OLED, stdlib HTTP server, one token-injected HTML page). Nothing leaves the machine.

python -m webapp                       # DB + library default from photo_analyzer.env
python -m webapp --db photo_analysis.db --library pictures/
python -m webapp --no-open             # don't auto-open a browser

See ../WEBAPP_CONCEPT.md for the full UX concept and rationale.

Views

  • Library — FTS5 search over description/tags/mood/location + facet filters (setting, time of day, season, people, year range, has-place, status), a folder sidebar (album = leaf folder), an infinite-scroll grid, and a lightbox with an Analysis panel (all DB fields) and a raw EXIF panel (incl. Google Maps link for geotagged photos).
  • Analyze — run controls mapping every CLI flag (dry-run / reanalyze / no-exif / exif-only / group-variants), a live overall progress bar, per-album progress, and a recent feed. Start/Stop drive photo_analyzer.py as a subprocess; Stop is the analyzer's own SIGINT drain. Balance / quota-check buttons. A Duplicates & maintenance row runs the content-hash actions (backfill hashes / find duplicates / mark duplicates) — no API calls; output streams to the Activity log and marked duplicates appear under the duplicate status filter. (Move/rename reconciliation and perceptual hashing also run automatically at the start of every normal run.)
  • Stats — KPI cards + CSS breakdown bars (setting/time/season/people, by-year histogram, top tags) + an error list. No chart library.

Architecture

  • server.py — stdlib ThreadingHTTPServer on 127.0.0.1:<free-port>; routes below.
  • query.py — read-only DB access (FTS5 search, facets, stats, one-photo).
  • runner.py — drives photo_analyzer.py as a subprocess; DB-derived progress.
  • page.py / analyzer.html — the single frontend page (inherits nsfwtag's tokens).
  • SQLite stays the source of truth; the server only reads it (+ a busy_timeout so a concurrent analysis write never errors a read). /img and /exif paths are validated against the DB — the server can't be pointed at arbitrary files.
Route Purpose
GET / the page
GET /search?q=&setting=&tod=&season=&people=&year_min=&year_max=&has_location=&status=&album=&sort=&offset= paged results (FTS5 when q)
GET /facets · /stats filter options · aggregates
GET /photo?path= · /exif?path= · /img?path= one record · raw EXIF · thumbnail
GET /progress · /log live run state · activity log
POST /run · /stop start / stop a run — body {library, ...flags} or {library, action} where action ∈ backfill-phash | list-dupes | dedupe
GET /balance · /quota-check provider balance · one-shot quota probe

Known ceilings (ponytail)

  • /img serves full-resolution originals, not resized thumbnails — a 120-card page can move a lot of bytes. Fine locally; add a Pillow thumbnail+cache endpoint if the grid feels slow. (Same limitation as nsfwtag.)
  • HEIC files won't render in the browser (broken thumb); metadata/search still work.
  • The Analyze tokens/ETA readout is omitted — those live only in the terminal dashboard's memory; the subprocess model surfaces DB-derived progress instead. Parse run stdout for token counts if you want them back.