Commit Graph
20 Commits
Author SHA1 Message Date
nmemmertandClaude Sonnet 4.6 eeab631ac9 feat: video sidebar with year/city filters like library view
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-18 11:38:44 -04:00
nmemmertandClaude Sonnet 4.6 770b4ea797 feat: group videos by year/month like photo library
Same groupByMonth + month-header pattern as Timeline/MonthGroup.
Grid uses auto-fill 200px columns with 16:9 aspect-ratio cells.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-18 11:24:54 -04:00
nmemmertandClaude Sonnet 4.6 6b4bd88943 feat: video support + Memories music choices
Videos:
- Worker ingests .mp4 .mov .m4v .avi .mkv .webm .3gp via ffmpeg/ffprobe
  (frame extract → sharp resize, same thumbnail pipeline as photos)
- DB: media_type + duration columns with migration
- GET /api/videos returns all indexed videos
- GET /api/photos/:id/video streams original file with Range support
  for browser seek/scrub
- VideosView: 16:9 thumbnail grid, play-button overlay, duration badge
- Full-screen player: native <video controls>, ESC to close
- Library tab now excludes videos (dedicated Videos tab)
- Dockerfile: adds ffmpeg to runtime stage

Memories music choices:
- Three styles: ⚡ Upbeat (G-major, default), 🌙 Dreamy (A-minor),
  🎬 Cinematic (D-minor, dual-oscillator richness)
- Shared Web Audio engine (reverb, LPF, pad + melody)
- Style picker in header; active style icon shown in slideshow bar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-18 11:01:30 -04:00
nmemmertandClaude Sonnet 4.6 7ed18f616f chore: sync docker-compose with actual run config
Port 3700:3000, explicit luma_net network.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-18 10:47:07 -04:00
nmemmertandClaude Sonnet 4.6 0cba67e02e feat: Memories tab — slideshow of photos on this day with ambient music
- New /api/memories/today endpoint queries photos by month+day (client
  sends its local date to avoid UTC offset issues in Docker)
- MemoriesView shows photos grouped by year with a "Play Memories"
  slideshow that crossfades between photos every 5 seconds
- Ambient music generated via Web Audio API — A-minor pad with slow
  vibrato shimmer and a pentatonic melody with convolver reverb
- Mute toggle, keyboard nav (← → Esc), progress dots, year badge
- fix: map popup images now use explicit px dimensions (100% was
  collapsing to 0 inside Leaflet's unsized popup content box)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-18 10:42:00 -04:00
nmemmertandClaude Sonnet 4.6 dd6fa803eb fix: improve text contrast throughout app
Replace near-invisible grays (#2a2a2a, #333, #3a3a3a, #444, #555) with
readable values (#555–#777) on the dark background.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-18 10:30:03 -04:00
nmemmertandClaude Sonnet 4.6 e14919fe17 feat: sync all iCloud libraries (personal + shared + shared albums)
Auto-discovers libraries via --list-libraries then runs icloudpd once
per library, each into its own subfolder under icloud_dest. Covers
PrimarySync, SharedSync-*, and SharedCollection-* automatically.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-18 10:24:29 -04:00
nmemmertandClaude Sonnet 4.6 a072090017 fix: make /photos writable on server so icloudpd can create download folders
Worker stays read-only; server needs write access to sync iCloud photos.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-18 10:18:53 -04:00
nmemmertandClaude Sonnet 4.6 586b783239 fix: remove invalid --no-progress flag from icloudpd command
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-18 10:11:27 -04:00
nmemmertandClaude Sonnet 4.6 4ff5a6c8ca feat: photo sync (iCloud + Amazon import), larger gear icon, Luma branding
- Settings: iCloud Photos section with icloudpd-backed sync — Apple ID,
  password, download path, cookie dir, live log output via SSE
- Settings: Amazon Photos one-time import — path to extracted data export,
  Add & Index button wires it directly into Watch Folders
- scripts/amazon_sync.py: stdlib-only downloader using Netscape cookie file
- server: POST /api/sync/icloud, /api/sync/amazon, GET /api/sync/stream
  (SSE log streaming matching ingest stream pattern)
- db: icloud/amazon setting keys; getSettings handles bool + string fields
- Gear icon: fontSize 16→20, button 38×38, color #555→#888
- favicon.svg: 6-blade aperture icon in Luma blue
- Browser tab title: shpm → Luma
- Dockerfile: installs icloudpd via pip3, copies scripts/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-18 09:30:43 -04:00
nmemmertandClaude Sonnet 4.6 5ff7f38b85 fix: correct volume types and photo path in docker-compose
- Use explicit type: volume for the luma_data named volume so compose
  managers (CasaOS etc.) don't misinterpret it as a bind mount
- Use type: bind / read_only: true for the photo directory
- Both server and worker now reference /media/Plex/Gallery consistently
- Worker was previously left with the placeholder path, causing it to
  watch a non-existent directory and index nothing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-18 08:31:49 -04:00
nmemmertandClaude Sonnet 4.6 3429ec1d80 feat: places (reverse geocoding) + folder browser
Places:
- worker/geocode.js: Nominatim reverse geocoder, 1 req/sec rate limit,
  in-memory cache keyed to 3-decimal-place coordinates
- Ingest calls reverseGeocode() for any photo with GPS data; stores
  place_city and place_country on the photo row
- DB migrations add place_city / place_country columns + index on place_city
- Stats API returns cities grouped by count
- buildFilter() supports ?city= parameter
- FilterSidebar shows a Places section with city list + country sublabel

Folder browser:
- GET /api/fs/browse?path=… lists subdirectories (path-normalized to prevent traversal)
- Settings panel: 📁 button opens inline directory browser below the add
  input — navigate with up arrow / directory clicks, Select fills the input

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-18 08:27:41 -04:00
nmemmertandClaude Sonnet 4.6 64f7906b22 fix: create starred index after column migration, not before
The CREATE INDEX on photos(starred) ran inside the main db.exec() block,
which fires before the ALTER TABLE migration that adds the starred column.
On databases created before the starred feature, this crashed the process
on startup with "no such column: starred" — causing the worker container
to exit silently with no logs.

Move the index creation to after the migration so the column always exists.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-18 08:11:14 -04:00
nmemmertandClaude Sonnet 4.6 d18c855571 feat: settings panel + multi-folder scanning
Settings panel (⚙ in header):
- Watch Folders: add/remove paths, toggle enabled; worker polls every 30s
  and calls watcher.add/unwatch without restart
- Ingestion: thumb/preview size and quality, applied per-file at ingest time
- Duplicate Detection: configurable Hamming distance threshold
- Danger Zone: clear all dismissed duplicate pairs

Multi-folder scanning:
- watch_folders table replaces single PHOTO_DIR env var
- PHOTO_DIR env var seeds the first entry on startup
- settings table with defaults for all tunable values
- Worker reads settings from DB on each drain cycle so changes are live

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-18 07:58:44 -04:00
nmemmertandClaude Sonnet 4.6 8c5c36857f perf: skip already-indexed files before queuing on worker startup
Load all known filepaths into an in-memory Set on startup (one bulk query).
Chokidar's initial 'add' burst now hits an O(1) Set lookup instead of going
through the serial drain queue with a per-file DB round-trip — a 100k-photo
library restarts in seconds rather than re-processing every file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-18 07:53:42 -04:00
nmemmertandClaude Sonnet 4.6 f0e237ab2b feat: filter sidebar, ingest progress, starred photos
- Left filter sidebar: All Photos / Favorites / Date by Year / Tags / Collections
  with counts, See more/less toggles, and blue active-row highlight
- SSE ingest badge in header: pulsing dot + filename + indexed count during scan;
  auto-refreshes the library when a scan finishes
- Starred/Favorites: starred column on photos, star button in lightbox toolbar,
  Favorites row in sidebar, ?starred=1 filter on /api/photos
- GET /api/library/stats endpoint (totals, years, tags, collections)
- GET /api/ingest/stream SSE endpoint (polls ingest_status table every 1.5s)
- Worker writes ingest_status (active, current file, scanned count) to SQLite

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-17 17:04:01 -04:00
nmemmertandClaude Sonnet 4.6 4df32f9f32 chore: pull image from ghcr.io instead of building locally
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-17 16:36:52 -04:00
nmemmertandClaude Sonnet 4.6 e0f4e8ff0d feat: initial Luma implementation
Full self-hosted photo manager with:
- Ingestion worker (chokidar + sharp + exifr, dHash perceptual hashing)
- SQLite schema with WAL mode (photos, tags, collections, duplicate_pairs)
- Express API with keyset pagination, tag/collection/search/date filters
- React + Vite UI: timeline, lightbox, detail panel, tag/collection editors
- Duplicate detection (Union-Find grouping) with review + dismiss UI
- Map view (Leaflet + OpenStreetMap, marker clustering)
- Docker multi-stage build + Compose with inline env config
- GitHub Actions: CI build check + ghcr.io image publish on main/tags

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-17 16:33:55 -04:00
nmemmert 03e6d6b628 Update TODO.md 2026-08-17 12:59:20 -04:00
nmemmertandClaude Sonnet 4.6 309a6723d7 Add architecture TODO and gitignore
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-17 11:01:47 -04:00