summaryrefslogtreecommitdiffstats
AgeCommit message (Collapse)AuthorFilesLines
2026-06-18Add per-photo rotate-left/right widget to admin edit pageHEADmainKen D'Ambrosio3-0/+51
Many older photos (especially pre-EXIF-orientation cameras) are off by a multiple of 90 degrees with no metadata to auto-correct. Each image row in the admin file table now has rotate buttons that physically rotate the file (auto-orienting first to normalize any existing EXIF tag), swap the stored width/height, and clear the cached thumbnail so it regenerates from the corrected image. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18Detect and fix non-4:2:0 chroma in H.264 MP4s during transcodeKen D'Ambrosio1-2/+15
Browsers only decode H.264 4:2:0 (Baseline/Main/High). Files reported as codec_name=h264 could still be High 4:2:2/4:4:4 profile if an earlier transcode pass (or the source) used non-standard chroma — ffprobe/ffmpeg read these fine, but real browsers reject them as unsupported. update.rb now also checks pix_fmt and re-transcodes if it isn't yuv420p/yuvj420p, and forces -pix_fmt yuv420p on output so this can't recur. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18Rename global search to "Album Search" and group results by albumKen D'Ambrosio4-33/+30
Photo search was returning every individual matching photo, which was overkill for what's meant to be a quick way to find an album. /search now groups matches by album (cover thumbnail + match count) instead of listing each photo, and is relabeled "Album Search" throughout the UI. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18Fix transcoded video playback in lightbox and slideshowKen D'Ambrosio3-4/+6
The HEVC-to-H.264 transcode kept the original file as a hidden (non-admin) entry with transcoded_to set, but the lightbox <video> element and the slideshow always pointed at the original file's URL. Opening a transcoded video (e.g. via a direct ?photo= link) tried to play the unsupported original codec instead of the browser-compatible copy. - album.erb/album.js: lightbox video now plays the transcoded file when present, while the "Download Original" link still serves the true original. - all_media_entries: slideshow skips transcoded originals entirely since it has no separate download path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18Add "move to new person" bulk actionKen D'Ambrosio2-1/+13
People bulk-move previously required moving members into an existing person, the pool, or the blacklist; this adds a "new person" target that creates a fresh cluster from the selected faces. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16Exclude transcoded originals from video count for non-adminsKen D'Ambrosio1-2/+4
Mirrors the browse page filter: files with transcoded_to set are hidden from unprivileged users, so the album video badge now matches what they actually see. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16Split album count into separate photo and video countsKen D'Ambrosio3-18/+29
Replace media_count helper with media_counts returning {photos, videos}. Album overview shows photo count as before, plus a coloured ▶ N badge only when an album (or its sub-albums) contains videos. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16Transcode HEVC/non-H264 MP4 files to H.264 for browser compatibilityKen D'Ambrosio1-9/+35
Firefox has no HEVC support. Pixel 9a (and newer phones) record video in HEVC by default. update.rb now probes the codec of every .mp4/.m4v file via ffprobe and transcodes non-web-friendly codecs (anything not h264/vp8/vp9/av1) to H.264 MP4 with a .h264.mp4 suffix, same as the existing MOV→MP4 flow. The original is kept and hidden from non-admins. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16Fix OG image for photo links: use thumbnail, drop false dimension tagsKen D'Ambrosio2-2/+4
When ?photo= is in the URL, @og_use_media was forcing the full-res image (2-3MB) as the og:image. WhatsApp fetched it but showed no preview, likely due to size or timeout. Now uses the thumbnail. Also removed hardcoded og:image:width/height (1200x630) from individual photo/album pages — those dimensions only apply to the default fallback image and were causing WhatsApp to reject previews when the actual image dimensions didn't match the declared ones. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16Fix WhatsApp/OG link previews: https URLs and correct image dimensionsKen D'Ambrosio3-2/+8
nginx was sending X-Forwarded-Proto: http (its own scheme) rather than https (the edge scheme), so og:url and og:image were http:// URLs. Fixed by hardcoding https in the nginx proxy_set_header since HTTPS is always terminated at the upstream Apache router. Added a 1200x630 landscape og-default.png (WhatsApp requires ~1.91:1 ratio) and explicit og:image:width/height meta tags. Also tracks the nginx site config in the repo. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12Fix bulk action 400: Rack maps entries[] to params['entries'] not ↵Ken D'Ambrosio1-3/+3
params['entries[]'] Rack::Utils.parse_nested_query maps form fields named 'entries[]' to params['entries'] (without brackets). The route was reading params['entries[]'] which was always nil, making entries always empty and triggering the halt 400 guard. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12Fix face selection: move checkbox below thumbnail, remove pointer-events hackKen D'Ambrosio2-29/+41
The overlaid checkbox had pointer-events:none so clicks fell through to the photo link. Replaced with a real <input type=checkbox> rendered below each face crop. Checkbox change events drive selection state; clicking anywhere on the card except the photo link also toggles it. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12Move bulk selection to admin cluster detail page (correct page)Ken D'Ambrosio3-156/+243
The bulk selection panel now lives on /admin/people/:uuid — the face crop grid page — which is what was actually requested. A sticky left panel shows the cluster name, the name form, a selection counter, and bulk action controls. Clicking a face crop toggles selection; clicking the photo link still opens the album. Bulk actions: move selected faces to a named person, move to pool, or blacklist. The per-face individual dropdowns are replaced by the panel. Merge-entire-cluster and Blacklist-cluster moved to collapsible/button in the panel too. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12Add bulk photo selection panel to person page (admin)Ken D'Ambrosio3-8/+280
On /people/:slug, admins see a sticky left panel and selectable photo tiles. Clicking a tile (or its checkbox overlay) toggles selection; clicking without modifier still opens the photo in a new tab. The panel shows the selection count and two actions: - Reassign to person: moves the selected photos' face entries from the current person's cluster to the chosen person. - Move to album: moves the photo files on disk and updates album.json, faces.json, and people.json rel paths accordingly. Album paths are offered via a datalist autocomplete. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09Fix New Person flow: redirect to new cluster, show hero face, detect ↵Ken D'Ambrosio3-4/+73
duplicate names After moving a face to "New Person", the user is now taken directly to that cluster's detail page. If it's a single unnamed cluster, the face is shown prominently at the top. Typing an existing name on the name form triggers a confirm dialog: OK merges into the existing person's cluster, Cancel saves as a new separate person with the same name. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09Add face pool, blacklisting, and action explanations to people adminKen D'Ambrosio5-27/+140
Removed faces now go to an "Unidentified pool" cluster rather than disappearing. Deleting a cluster blacklists all its members so they are skipped by future re-clustering runs. Pool faces can be assigned to a named person or individually blacklisted. A plain-English info box on the detail page explains what each action does and that no photo files are ever modified. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08Add face and cluster deletion to people adminKen D'Ambrosio3-1/+25
On the cluster detail page: "Remove face" option in each face's move dropdown removes it from the cluster entirely; "Delete cluster" button (red, with confirmation) removes the whole cluster from people.json. Moving the last face out of a cluster also auto-deletes it. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08Add people cluster detail page with face move/merge and hover previewKen D'Ambrosio4-2/+245
Each cluster in /admin/people now links to a detail page showing all faces in a grid. From there you can rename the cluster, move individual faces to another named person (or spin off a new cluster), or merge the entire cluster into another. Hovering any face crop shows the original full photo for context. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08Add photo search with Boolean operatorsKen D'Ambrosio4-0/+242
- Server-side search index built from all album.json files + people.json, cached in memory for 5 minutes. Each photo document includes filename, album path words, title, caption, camera, date parts (year/month-name/ full date), and person names. - Recursive-descent Boolean parser: AND (explicit or implicit between consecutive terms), OR, NOT, with standard precedence. - GET /search?q=... returns a photo grid (max 300 results) linking each photo back to its album lightbox. - Search box added to the site header; hidden on mobile. - Results show filename, date, person names, and album path per photo. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08Add people/face clustering featureKen D'Ambrosio7-0/+607
- scripts/cluster_faces.py: greedy centroid clustering (numpy) with 3 refinement passes; preserves existing UUID/name mappings across re-runs; writes MEDIA_ROOT/people.json atomically. - app.rb: GET /face/* serves cropped+padded face thumbnails (100x100, cached under cache/faces/); GET|POST /admin/people for cluster management; POST /admin/people/recluster runs cluster_faces.py as a background job; POST /admin/people/:uuid saves names+slugs; GET /people public grid of named people; GET /people/:slug photos for one person. - views/admin/people.erb: lists all clusters (named first, then by size), face crop samples, inline name form, re-cluster button with live log. - views/people.erb: public grid of named people. - views/person.erb: photo grid for one person, linking back to album lightbox for each photo. - views/layout.erb: People link in nav (conditional on FACES_ENABLED). - public/css/style.css: styles for people admin list and public tiles. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08Separate face detection into standalone daemonKen D'Ambrosio6-126/+355
- Strip all face code from update.rb; add shared log helper writing to /opt/albumen/log/albumen.log with [update] prefix. update.rb now owns only album.json; face_daemon.rb owns faces.json. - New scripts/face_daemon.rb: polls MEDIA_ROOT for unprocessed images, calls faces.py in batches, writes per-directory faces.json sidecars atomically. Graceful SIGTERM/SIGINT shutdown between directories. - New config/face_daemon.service: systemd unit running as albumen user, Restart=on-failure, logs via SyslogIdentifier=albumen-faces. - app.rb: add FACES_ENABLED constant; load_faces() helper reads faces.json; album_files() merges face data into each entry as :faces field. - Update README.md and DESIGN.md to document the new daemon architecture, faces.json schema, and service management commands. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08Add progress counter and faces-pending sentinel bypass to update.rbKen D'Ambrosio1-3/+18
- process_dir() takes idx/total args and prints [N/total] prefix on every Scanning/Skipping line so long runs are easy to monitor via tail -f - faces_pending?() checks album.json for any image with faces: null when faces.enabled is true; if found, the sentinel skip is bypassed so those images get processed even though the directory mtime hasn't changed - This handles the case where face detection is newly enabled on a library that was previously indexed without it — no --force needed on subsequent runs after the initial catch-up Resume after abort: sentinel is only touched after atomic_write_json, so an aborted directory reruns cleanly. Already-completed directories skip normally; partially-detected batches rerun from scratch (safe/idempotent). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08Switch face detection to CNN model with parallel batch processingKen D'Ambrosio2-49/+93
- faces.py: use model="cnn" (more accurate, better at angles/small faces/poor lighting) instead of HOG; model comment explains the trade-off clearly - faces.py: accept multiple image paths; process with ThreadPoolExecutor (dlib releases GIL during C++ inference → genuine thread parallelism); output JSON dict {path: [faces]} for batch calls - update.rb: batch_detect_faces() collects all unprocessed images per directory and calls faces.py once per directory rather than once per image, avoiding repeated model load overhead - update.rb: FACES_WORKERS read from config.yml faces.workers (default 4; set to 20 in this install's config.yml on a 64-core Xeon) - update.rb: process_dir() now takes idx/total and prints [N/total] prefix on every Scanning/Skipping line for progress monitoring To monitor a long run: nohup ruby /opt/albumen/scripts/update.rb > /tmp/faces_update.log 2>&1 & tail -f /tmp/faces_update.log Resume/restart is fully safe: sentinel files are only written after atomic_write_json, so an aborted directory reruns cleanly from scratch. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08Add opt-in facial recognition: detection and embedding storageKen D'Ambrosio4-3/+202
- scripts/faces.py: Python helper using face_recognition (dlib/HOG) to detect faces and return 128-D encodings as JSON; called by update.rb - scripts/update.rb: enrich_faces() stores face boxes and encodings in album.json per image (null = not yet processed, [] = processed/none found); skips files already processed; gated on faces.enabled in config.yml - Reads CONFIG_PATH (same env var as app.rb) to check faces.enabled flag - Feature is off by default; enabled in this install via config.yml - README.md, DESIGN.md: document installation, opt-in config, data model, and planned clustering/people-management pipeline People management UI and clustering script are the next milestone. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22Add hover zoom preview for admin thumbnailsv1.1Ken D'Ambrosio2-0/+51
Hovering over a thumbnail in the admin file table pops up the full 300×300 cached version near the cursor, making it easy to confirm identity before deleting or editing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22Speed up update.rb and fix UI always forcing full rescanKen D'Ambrosio6-13/+266
- update.rb: skip exiftool on images marked exif_absent (set after first failed attempt); prevents repeated slow scans of old photos with no EXIF - update.rb: explicit directory argument now implies force — passing a path always rescans that subtree regardless of sentinel mtime - app.rb: /admin/update no longer hardcodes --force; sentinel-based skipping is used by default, making UI updates finish in seconds instead of minutes - admin/album.erb: add "Force rescan all" checkbox to Run Update button; checked state passes force=1 to the server and restores --force behavior - README.md, DESIGN.md: document sentinel skipping, exif_absent flag, and explicit-directory force behavior Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14Fill viewport with media in lightbox and slideshowKen D'Ambrosio2-11/+7
Lightbox: .lb-media now fills the full stage (flex, 100%x100%) and #lb-img/#lb-video use width/height:100% + object-fit:contain so at least one axis always reaches the edge. Click-to-close updated to check event.target===stage instead of stopPropagation. Slideshow: #ss-img/#ss-video likewise changed from max-width/max-height to width/height:100% so small or portrait media fills the stage. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14Hide transcoded originals from non-admins; mark them visually for adminsKen D'Ambrosio5-20/+39
update.rb records transcoded_to in album.json (even on re-runs where the MP4 already exists) so the marker survives across scans. app.rb filters files with transcoded_to from non-admin views. album.erb renders them greyed-out with an amber "⚠ original" badge in admin mode. admin/album.erb marks the edit-table row and shows the target filename under the original. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14Auto-transcode non-browser-playable videos to MP4 in update.rbKen D'Ambrosio1-4/+39
On each run, any .avi, .mkv, or .mov file without a same-named .mp4 sibling is transcoded with ffmpeg (H.264/AAC, CRF 23, faststart). The original is kept on disk and hidden in album.json so only the playable MP4 appears in the UI; admins can un-hide the original if needed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14Add per-photo delete checkbox to admin edit formKen D'Ambrosio3-5/+39
Checking Delete and saving permanently removes the file and its thumbnail; a JS confirm dialog gates the submit. Deleted files are stripped from params before save_edits so they don't linger in album.json. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13Add folder rename and background update trigger to admin UIKen D'Ambrosio3-0/+145
- Admin edit form: "Folder name" field renames the directory on save; also moves the thumbnail cache subtree to match the new path - Admin edit page: "Run Update" button spawns update.rb in a background thread, streams output into a terminal-style log panel via 1.5s polling; shows Done/Error status when complete Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12Use albumen.png as OG image for root URLKen D'Ambrosio1-3/+3
Root album leaves @og_image_rel nil so layout.erb falls back to /img/albumen.png; photo-based OG image only applies to sub-albums. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12Add albumen.png as app icon, favicon, and OG fallback imageKen D'Ambrosio4-1/+3
- public/img/albumen.png: app logo - public/favicon.ico: 32x32 + 16x16 multi-size - public/apple-touch-icon.png: 180x180 for iOS home screen - layout.erb: favicon link tags; og:image falls back to albumen.png when no specific photo is selected Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12Show slideshow button for folders with media anywhere in hierarchyKen D'Ambrosio2-4/+2
- slideshow_view now uses all_media_entries for non-root dirs so the slideshow traverses sub-albums instead of only direct files - album.erb condition simplified: show launcher when albums OR media entries exist (previously hidden for folders with only sub-albums) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12Add organize_by_day.rb script for GPS-based day/location splittingKen D'Ambrosio1-0/+135
Groups media files in an album directory by EXIF date, reverse-geocodes the first GPS fix of each day via Nominatim, and moves files into "Day N - Location" subdirectories. Dry-run by default; pass --go to execute. Supports a LOCATION_OVERRIDES map for cleaning up Nominatim names that are overly granular. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12Add photo counts, EXIF details, video duration badges, slideshow launcher UIKen D'Ambrosio5-13/+67
- Album cards show recursive photo count (bubbles up through sub-albums). - Lightbox info panel shows camera, aperture, shutter speed, and ISO; update.rb now extracts and stores these EXIF fields. - Video thumbnail cards show a duration badge (e.g. "1:23"). - Slideshow launcher redesigned: button on its own line, with Shuffle / Full screen / Interval options on a second line, all inside a rounded border to make the grouping clear. - Fixed album-actions alignment so Interval sits level with the checkboxes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12Reverse default slideshow order to show newest photos firstKen D'Ambrosio1-0/+2
When not shuffled, reverse SS_ENTRIES so the most recently added photos play first instead of last. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12Move slideshow interval control to album page; fix mobile viewport clippingKen D'Ambrosio5-9/+13
- Interval input (default 5 s) now lives beside Shuffle/Full screen on the album page; passed as ?interval= param to the slideshow and seeded into the hidden ss-interval input on load. - Added "Interval" label text next to the input. - Fixed slideshow controls being pushed off-screen on mobile by using 100dvh (dynamic viewport height) with 100vh as a fallback, so the layout accounts for mobile browser chrome. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12Prune orphaned thumbnails when update.rb removes deleted filesKen D'Ambrosio1-4/+10
When a media file is deleted from disk, also delete its cached thumbnail from cache/thumbs/ so stale .th.jpg files don't accumulate. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12Fix slideshow photos showing too briefly due to preload timingKen D'Ambrosio1-5/+8
The next timer was starting as soon as ssShow() was called, so preload time + 500ms crossfade ate into the visible interval. Now the timer starts only after applyEntry() fires (photo is actually on screen), guaranteeing each photo gets a full interval of visibility. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12Keep screen awake during slideshow via Wake Lock APIKen D'Ambrosio1-1/+16
Acquire a screen wake lock when the slideshow is playing; release on pause or page hide; re-acquire when the tab returns to the foreground. Failures are silently ignored so older browsers degrade gracefully. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11Fix slideshow timer burst when returning from background tabKen D'Ambrosio1-1/+10
Use Page Visibility API to cancel the timer when the tab hides and reschedule fresh on return, so no queued ticks fire in a burst. Also fix stale #photo= → ?photo= in stage click-through. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11v1.01: replace bcrypt with PBKDF2-SHA256; update README and DESIGN docsv1.01Ken D'Ambrosio7-36/+149
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11Fix OG image previews: use ?photo= param so server can set correct og:imagev1.0Ken D'Ambrosio3-19/+30
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11Add Open Graph meta tags for social media link previewsKen D'Ambrosio2-0/+19
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11Add HTTP vs HTTPS setup guide with Certbot instructions to INSTALL.mdKen D'Ambrosio1-0/+67
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11Replace cover dropdown with Random checkbox and per-file cover radioKen D'Ambrosio2-13/+25
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11Always show filename in lightbox info panelKen D'Ambrosio2-2/+2
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11Limit slideshow to filtered albums when search filter is activeKen D'Ambrosio3-26/+43
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11Fix set_password.rb to chown config.yml to albumen user when run as rootKen D'Ambrosio1-0/+9
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>