How I rebuilt finescenery.com on an AI pipeline
PhotoMind: a Python pipeline that turns 20 years of Lightroom into a fast static site
Two months ago I rebuilt finescenery.com, my landscape photography portfolio, on a different foundation. The new site is generated end to end from a Python pipeline I call PhotoMind. The same archive, the same Lightroom catalog, but now the curating happens where I want it to happen - in Lightroom - and the site catches up automatically.
This is the build story. I wrote the original on finescenery.com itself for visitors there; this is the same engineering content reframed for anyone interested in what an AI-augmented end-to-end build looks like in practice.
The starting mess
The starting state is the one most long-running personal archives end up in: 117,166 files spread across half a dozen drives, ranging from RAW captures off a Nikon D700 in 2008 to last month's mirrorless ARWs. The Lightroom catalog has rough star ratings and roughly 26,500 keyword assignments. The previous version of the portfolio site was hand-picked images with hand-typed captions; it never grew past a few hundred pieces because updating it was a chore.
I wanted a site that:
- pulled directly from the Lightroom catalog, so curating in Lightroom curated the site
- generated titles, alt-text, and descriptions automatically with quality good enough to ship
- grouped photos by where and what (country, region, place, scene, mood) without me hand-tagging each one
- stayed fast: static pages, no CMS, no database calls at request time
That is what PhotoMind became.
How it works
PhotoMind is a Python project that owns one SQLite database (~268 MB) and writes content into the Astro repo for finescenery.com. Three layers, roughly.
Indexing
Scans every photo on disk. Extracts EXIF via ExifTool, parses Lightroom XMP sidecars, computes CLIP embeddings via Hugging Face Transformers on PyTorch. Links each file 1:1 to the Lightroom catalog. The output is the SQLite ground truth.
Understanding
Runs vision LLMs over the Lightroom-rendered JPEGs to generate descriptions, alt-text, scene tags, mood, composition, and genre. Three providers: Qwen-VL at two sizes (running locally on a personal GPU) plus Claude. Stored per-piece-per-provider so I can compare.
Site export
Selects the portfolio. Deduplicates burst frames. Maps every piece to a canonical URL. Encodes responsive AVIF and WebP at five sizes via Pillow and pillow-avif-plugin. Writes Astro markdown. Astro builds it; Cloudflare Pages serves it globally.
The interesting parts
A few of the design choices are unusual enough to be worth calling out.
Lightroom is the source of truth
Every winner image on the site is a JPEG I personally exported from Lightroom at rating 1+. PhotoMind links each of those back to its source RAW via basename and EXIF matching. Curating in Lightroom is curating the site. There is no double-bookkeeping between catalog and site, and no admin panel to maintain. 5,789 winners, 100% mapped.
Bucket-batched captions
Letting a vision LLM generate captions one by one produces formulaic slop - every other title becomes "Golden Light Over the Ocean". Photos are grouped instead into buckets by (narrative, place, subregion, region, country, year-month) of up to ten. Claude Sonnet sees the whole bucket at once with a prompt that bans formula leading words and demands variety.
Output goes into a site_metadata table keyed by (piece_id, prompt_version), making re-runs idempotent. Two tiers in the same pass: a short description for meta tags and RSS, a longer one for the body of the piece page.
Gazetteer-driven URLs
Lightroom keywords are messy: "Lazovsky", "Lazovsky Reserve", "lazovsky reserve", "Lazo". A hand-written gazetteer resolves them all to canonical (kind, slug) tags at every hierarchy tier (country, region, subregion, place). The geographic tree builds itself from that. Tail countries with under 10 pieces collapse into /world/. Sub-galleries with under five pieces collapse into the parent.
Three theme axes
Alongside the geographic tree, three taxonomies sit in parallel: feature (30 entries), composition (13), mood (19). Hand-curated lists drawn from raw vision-tag frequencies across the corpus. The same tokenizer pattern feeds the raw vision output in and gets back the canonical site tags. That is how /mood/golden-hour/ and /feature/storm-clouds/ exist without me ever sitting down to tag photos by hand.
Client-side CLIP search
Every piece has a 512-dim CLIP embedding. When you type "misty forest" or "sea stacks at dawn" into the search box, the query is encoded in the browser via a small precomputed text encoder and ranked by cosine similarity over those vectors. There is no server doing the matching, just static vector files and a few hundred lines of browser JavaScript. The same CLIP model that powered tag propagation now powers search at zero runtime cost.
What I learned
Four things stood out by the end.
01. AI captioning works, but bucket-batching is the difference
Per-piece prompts produce templated copy. Per-bucket prompts with explicit variety constraints produce something I would ship. The cost is the same.
02. Local vision LLMs already understand your photos
You just need to ask nicely. Qwen-VL running on a personal GPU described 5,500+ images before I called a single hosted API. A clear prompt and a downscaled image are most of the work. Hosted models still win on style and variety, but the local ones do the heavy lifting for free.
03. Lightroom-as-source-of-truth is liberating
Once it is set up, I just rate pictures in Lightroom and the site catches up on the next export run. Zero copy-paste between catalog and site.
04. Static is faster than everything else
The whole site is plain HTML, CSS, and AVIF. Cloudflare serves it for free. There is no admin panel because there is nothing to admin.
Try it on the live site
The pipeline produces something you can actually poke at.
Read more on finescenery.com
This writeup is the condensed version. The originals on finescenery.com go a bit deeper, particularly on the photography side.
- How this site is built - the long-form build writeup
- finescenery.com is live on a new home - the launch announcement