Local Photo Browser
Index a photo folder onto a USB drive, then browse it in an Apple-Photos-style grid that runs entirely in the browser.
- Status:
- Active
- Stack
- 6
- Architecture notes
- 6
Internal enterprise or private repository
Ask me about this projectOverview
A two-part photo browser that never touches the internet at runtime. A Node.js CLI scans a source folder, reads EXIF, builds WebP thumbnails, optionally captions and tags each image with a local Ollama vision model, and writes everything to a portable .photoapp/ folder — SQLite plus thumbnails — on a USB drive. A dependency-free static viewer copied to the drive root reads that SQLite file in the browser through sql.js and gives an Apple-Photos-style grid with filters, sorting and a lightbox. Everything on the drive uses relative paths, so it works mounted at any drive letter.
Architecture Breakdown
How it works
Core mechanics, failure recovery paths, and system design decisions.
- 01
Indexer and viewer, no server
The CLI produces the data; the viewer is HTML, CSS, JS and a sql.js WASM blob. The viewer makes zero network requests after load — queries run locally in WASM — and works from file:// or a bundled zero-dependency serve.js built on Node stdlib alone.
- 02
Built-in SQLite, no build step
Storage is the node:sqlite module, so there is no native compile: sharp ships prebuilt binaries and heic-convert is a pure-JS HEIF fallback. Requires Node 22.5 or newer.
- 03
Resumable and content-addressed
Every file is committed to SQLite before the next starts, so Ctrl+C and re-run is safe. Unchanged files are skipped on path plus mtime plus size without re-hashing; moved or duplicated content is matched on SHA-256 and reuses the existing thumbnails and row.
- 04
Tagging is optional and local
With a model configured, each image gets a caption and tags from Ollama over localhost; --skip-tagging does EXIF and thumbnails only, which is the recommended first pass. The only step that ever needs the internet is the one-time npm install.
- 05
Robust scan
Corrupt files are logged to a scan_errors table and skipped rather than crashing the batch. Formats cover JPEG, PNG, HEIC, WebP, TIFF and RAW, with RAW decoded from its embedded preview.
- 06
Viewer that scales
A windowed grid stays smooth past 50k items, with date-range, format and tag filters read from the database, sortable by date taken, date added or filename, and a lightbox showing the 1200px preview with caption, tags and EXIF.
Key Takeaways
Engineering Highlights
- No backend anywhere — the viewer runs from file:// and queries SQLite in WASM
- SQLite is the built-in node:sqlite module, so there is no native build step
- Indexing is resumable and deduplicates moved or copied photos on SHA-256
- AI captions and tags are optional and come from a local Ollama model over localhost
- Everything on the USB uses relative paths, so it works at any mount point
- The only online step is the one-time npm install; nothing else touches the network
Stack & Tools
- Node.js
- node:sqlite
- sharp
- sql.js (WASM)
- Ollama
- WebP