Files
shpm/TODO.md
T
2026-08-17 12:59:20 -04:00

7.1 KiB
Raw Blame History

Plex for Photos — Self-Hosted Photo Library Architecture

A self-built media server for your photo collection, running as another container in your homelab. Follows the same pattern as your existing study app: React/Vite frontend + Express/SQLite backend, files stay on disk, DB just indexes them.


High-Level Flow

┌───────────────────┐        watches         ┌──────────────────────┐
│  Photo Library    │ ─────────────────────▶ │  Ingestion Engine     │
│  (disk / NAS)     │                        │  (chokidar file watch)│
└───────────────────┘                        └───────────┬───────────┘
                                                         │ extract EXIF,
                                                         │ generate poster,
                                                         │ compute pHash
                                                         ▼
                                                ┌──────────────────────┐
                                                │   SQLite metadata DB  │
                                                │  (photos, collections,│
                                                │   hashes, artwork)    │
                                                └───────────┬───────────┘
                                                            │
                                                            ▼
                                                ┌──────────────────────┐
                                                │  Express API server   │
                                                └───────────┬───────────┘
                                                            │ REST/WS
                                                            ▼
                                                ┌──────────────────────┐
                                                │  React/Vite frontend  │
                                                │  (browse, collections,│
                                                │   discover, map)      │
                                                └──────────────────────┘

Original files never move or duplicate — the DB just points to paths on disk. Posters (thumbnails) are the only generated/derived asset stored separately (for instant browsing and discovery views).


Components

Ingestion Engine

Stack: Node + chokidar (file watcher) or a scheduled scan job

On new file detected:

  • Extract EXIF (date taken, GPS, camera model, orientation) — exifr or exiftool wrapper
  • Generate posters (e.g., sharp) at multiple sizes — grid poster + hero/preview size
  • Compute a perceptual hash (pHash via sharp + blockhash-core) for smart duplicate detection
  • Insert a row into SQLite: path, EXIF fields, poster path, hash, import timestamp

Runs as a background process inside the same container, or a separate lightweight worker container if you want to isolate it from the API.


Database (SQLite)

Core tables to start with:

Table Purpose
photos id, filepath, poster_path, date_taken, date_imported, camera_model, gps_lat, gps_lon, phash, width, height, filesize
collections id, name, created_at, description, poster_path
photo_collections (join) photo_id, collection_id
tags id, name
photo_tags (join) photo_id, tag_id

Matches the SQLite pattern you're already using in the study app — no new infra to stand up.


API Server (Express)

  • GET /photos — paginated library query (filter by date range, tag, collection)
  • GET /photos/:id — full detail + EXIF metadata
  • GET /photos/similar — grouped by similar pHash (within a distance threshold)
  • POST /collections, POST /photos/:id/tags — manual organization endpoints
  • GET /photos/map — GPS-tagged photos for map view
  • Poster serving — directly via endpoint or Nginx for static file efficiency

Frontend (React/Vite)

  • Browse view — infinite scroll, grouped by day/month, lazy-loaded posters (like Plex's continuous feed)
  • Collection/tag views — same grid, filtered (like Plex collections for photos)
  • Detail view — full image, EXIF panel, tag editor, related photos
  • Duplicate review — side-by-side similar photos with keep/flag workflow (like Plex's duplicate management)
  • Map view — Leaflet/MapLibre pins from GPS EXIF, click to open photo
  • Discovery/search — date range, camera, tag filters, full-text search

Smart Duplicate Detection (High Value — Do This Early)

At import time, compute pHash for every photo. On a scheduled job (or on-demand from a "Find Similar" button), compare hashes pairwise within a similarity threshold (Hamming distance) to group near-duplicates — burst shots, slightly different crops, etc.

Surface groups in a review UI rather than auto-deleting — you make the call, app just surfaces the candidates. This is much cheaper computationally than face recognition and gives you the real "cleanup" value people want from a photo manager.


Face Grouping (Optional, Scope Carefully)

If you want this eventually:

Options: face-api.js (runs in Node, ONNX-based, no Python needed) or a Python microservice using face_recognition/dlib if you're OK adding a Python component.

Store face embeddings + bounding boxes in a faces table, cluster embeddings to group "this looks like the same person" without needing named identities upfront — you label clusters after the fact.

This is the single biggest scope jump — worth treating as a v2/v3 feature, not part of the initial build.


Deployment Fit

New container (photo-library), same pattern as your other homelab services:

  • Mounts your existing photo backup folder(s) read-only (ingestion engine doesn't need write access to originals) plus a writable volume for posters/DB
  • Nginx Proxy Manager route (photos.necloud.us), behind Authelia like your other private services
  • No external dependencies — everything runs locally against files already on disk

Suggested Build Order

  1. Ingestion engine + SQLite schema — watch folder, extract EXIF, generate posters, populate DB (get this solid before touching UI)
  2. Basic API + browse UI — paginated grid, chronological browsing — this alone is already usable
  3. Detail view + manual tagging/collections
  4. Smart duplicate detection + review UI — high value, moderate effort
  5. Search + filters
  6. Map view (if your library has meaningful GPS data)
  7. Face grouping (only if you still want it after living with V1–V6 for a while)