It’s been over 10 years since I last identified as a professional photographer. I used to license a fair amount of imagery commercially: magazines, prints, murals, TV documentaries, etc., and also put on a few shows at galleries. I did a lot of teaching and speaking.
Eventually, I left my identity as a “professional” photographer, although I continue to feel at the core like a “photographer”; all imaging was becoming computational, and that draw led me back to the tech industry, although I have the most passion about the space between creatives and technology. Pam came into my life at around that time, which led to marriage and then a family, so it was good timing to settle down a bit.
Since then, my portfolio, which has always been a random collection of images I happened to pick out one day, has remained unchanged. My Lightroom library reports that it contains 667,377 images; virtually all have only ever existed locked up in my many layers of storage and backups.
And so, I decided it was time to re-invent my online photography presence. The irony of doing this now when we’re in middle of what will sure to be generational change due to new generative technologies is not lost on me, but it is precisely this new technology that has allowed me to build a web presence that exceeds my ability to manually develop. I spent at least 50 evening hours in the last 2 weeks building, and now I have a local catalog organizer that I can use to publish collections of images to a modern cloud application.
I’m proud of it, and it embodies the following principles:
- Imagery first. Big and beautiful at full screen on a desktop browser. Display P3 color space to get beyond the historical tyranny of sRGB.
- Blazingly fast, everywhere. Faster than a local image viewer. Instant search. Map view. Keyboard shortcuts. Mobile optimized.
- Fully described. If I haven’t had time to manually caption something or tag its location, figure out what’s in the picture and where it was taken. Pull in all known metadata and use it in the frame analysis. Tag and group faces for naming.
- Video support. Show videos using a modern codec (AV1) at high quality (10 bit, high bitrates) with a suitable fallback (h.264). Analyze and understand what is in every scene in a video. Extend Lightroom to export metadata for videos.
- Open-ish. Allow image downloads for personal use, but apply both visible and invisible watermarking. Be clear about terms of service.
- Support batch operations. Processing and publishing 1,000 images should be the same amount of work (but not compute) as publishing 1 image. Re-analyzing and re-publishing should be idempotent.
- ✦ Transparently tag AI-generated content. Use a consistent mark with tooltips. Currently, some captions and detailed descriptions are generated based on all the known information about an image.
- Search and AI crawler friendly. Make it easy for search engines. Let AI train on specific parts of the site and limit its access to others.
On day 1, I had an informative conversation with Claude about how to build a local web app and deploy a remote web service; I learned a lot about how the web has changed while I’ve been away from building websites. The site is hosted on Cloudflare with workers and objects effectively deployed at the edge, making the site very fast everywhere. This differs from the single-host model I have been using for decades (although even that uses edge caching). I’ve been impressed by Cloudflare’s simplicity and performance.

Stop here if you’re sick of reading about AI! Go look at some pretty pictures!
95% of the build of the local catalog viewer and web app were done in conversational chat with Claude Code. The other 5% was spent organizing the first 4,000 images to upload, going back to original files to make sure I had the highest-quality versions as source. Aside from a set of about 1,500 selects, I added a few trips’ worth of images from Indonesia, Iceland, and Greenland along with smaller collections featuring music and friends.
The first prototype was done by asking Claude to look for photo sharing sites and, taking into consideration what it knows about me, build something it thought I would like. I was at a hotel in Austin for SXSW and didn’t have time to sit in conversation with Claude for hours at a time (this was before the agentic changes that Anthropic has built into Claude during the last two weeks). To hack this, asked Claude to “wake up every 10 minutes and KEEP GOING” (almost verbatim) which amazingly, worked. Claude ran overnight and created the first version of the catalog viewer, and another night to create the web app. It built way more than I needed, and I had to spend time later asking it to remove a lot of the features it built. Did I need 12 ways to look at a group of images? No, but 2 of them were pretty good. This wasn’t the best way to start, but it was a great way to prototype and see a bunch of ideas I might not have considered. Fairly late in the process I was still accidentally finding new areas of functionality that it had built early on.
After prototype was built I had a much better idea of what I actually wanted, and I started down the task of constantly exerting my will, which has been both fascinating and exhausting. “You had an extra 3 minutes between task A and B? You could have kicked off a task!” This is a new 24/7 reality. I used tmux to keep Terminal sessions open and Tailscale to access home machines remotely via mobile phone and laptop. I had conversations with Claude (via the apps) to explore ideas while Claude Code was chugging away.
I’m using Claude at work. I’m using Claude for this project, and others. Claude has became my life.

Luckily, I have a software engineering background, although it certainly doesn’t require one to do this sort of thing. Still, a software background has helped to keep this project growing while maintaining quality. Frequent use of /simplify helped, as well as asking it to do evaluations of the cloud deployment for security, efficiency, and performance.
I didn’t keep track of token counts, but my Claude Code setup does have a token cost estimator. I estimate that I probably spent $1.5K worth of tokens over the 2 weeks, but I’m on the $200/month plan so my costs are capped (it allows for way more than that per month).
Image analysis using paid LLMs would have been expensive, so I used local models running at home running for hours at a time. Frame analysis was done with Qwen3-VL-32B-Instruct-8bit, which fits into around 34GB of operating RAM (the “instruct” version doesn’t think too hard and is better for this sort of thing). Macs are amazing because they use a shared RAM architecture; they are slower than dedicated Windows-based GPUs, but you can run large models that can’t run on reasonably-affordable Windows machines.
Videos have frames extracted based on scene detection so there aren’t too many duplicate frames to analyze. Multiple scenes are summarized together using Qwen3.5-35B-A3B-8bit.
On the host Mac Studio, I use the MLX versions, which are optimized for Apple Silicon, and then I built a farm (dispatcher plus remote nodes) because I have other MacBook Pros and PC(s) with a decent GPU(s). I tried to have remote workers run MLX on the other Macs, but this proved to be annoyingly difficult so I ended up using Ollama, which was easy (at an efficiency cost) to scale.
Thoughts?
Check out the site, browse around, click on “Surprise” a few times. Send me your thoughts! I’ve been way too close to this project for the last two weeks and could use feedback. :)
This article was 100% written by a human.
Under the hood
- —Distributed inference pool spans multiple machines on the local network, mixing Apple-silicon nodes (MLX) with a dedicated-GPU node (Ollama); the primary host holds both the large vision model and the summarizer in memory at once, each worker runs a configured number of jobs concurrently, and failed work resubmits to the next worker
- —Publishing is content-addressed and diffed by three separate hashes (image content, metadata, source file), so re-captioning fifty images uploads no pixels, re-publishing a collection is idempotent, and the same image in five collections is stored once
- —Public URLs are keyed to a perceptual hash of the image, so a permalink survives re-exports, metadata edits, and database rebuilds, and changes only when the picture itself visibly changes
- —Display P3 color is managed end-to-end: every source profile (sRGB, AdobeRGB, ProPhoto, untagged) normalizes to P3, and an ICC profile is embedded in every published tier, down to individual deep-zoom tiles
- —Invisible watermarking survives heavy compression, resizing, and screenshots; gigapixel images are watermarked at a reduced size and the pixel-level difference is upscaled onto the full-resolution original, keeping the mark imperceptible at any size
- —Gigapixel images publish as deep-zoom tile pyramids (libvips) generated with sequential reads, so arbitrary resolution never requires holding the full image in memory; watermarks (visible and invisible) are applied before tiling, and the browser streams only the tiles in view, keeping pan and zoom instant at hundreds of megapixels
- —360 panoramas are detected at ingest from embedded panorama metadata, projected from equirectangular into six cubemap faces, and tiled into a multi-resolution pyramid; the in-browser viewer streams tiles as you look around, and partial panoramas render correctly via computed field-of-view
- —A WebXR gallery serves VR headsets from the same catalog: a virtualized infinite-scroll wall keeps only ~20 image panels live at once behind a texture cache, and 360 panoramas become immersive skyboxes with gaze-priority streaming, so the face you are looking at sharpens first while all faces upgrade together to avoid seams; controllers and bare-hand pinch both work
- —Video analysis is scene-first: keyframes are extracted at scene midpoints (with perceptual-dedup and fixed-interval fallbacks), then a second pass summarizes with temporal context and any speech transcript across the whole video
- —Changing a password bumps a version number carried in every token, instantly revoking all outstanding sessions with no revocation list to maintain; per-IP rate limits live in edge storage, so they survive server cold starts
- —The local catalog ingests ~700 files/sec, and full-text search answers in under 3 ms at 50,000-asset scale; face detection runs at ~7 images/sec and metadata extraction at ~10 files/sec
