diff options
Diffstat (limited to 'src/lib/album-photo-select.source.sh')
| -rw-r--r-- | src/lib/album-photo-select.source.sh | 90 |
1 files changed, 84 insertions, 6 deletions
diff --git a/src/lib/album-photo-select.source.sh b/src/lib/album-photo-select.source.sh index ea661b4..283e17b 100644 --- a/src/lib/album-photo-select.source.sh +++ b/src/lib/album-photo-select.source.sh @@ -2,24 +2,102 @@ # album-render.source.sh (task ar0) so the "which photos are in this album, in # what order, and which one do we pick for a background/splash" concern lives # apart from the page orchestration, the tile-layout deciders and the thumbnail -# HTML. This is selection POLICY (shuffle/sort, splash-requires-a-blur, seeded -# random pick) and changes for different reasons than the rendering plumbing. +# HTML. This is selection POLICY (shuffle/sort/chronological, splash-requires-a- +# blur, seeded random pick) and changes for different reasons than the +# rendering plumbing. # # These helpers are called by the orchestrator (album-render.source.sh) and by # the per-page render jobs at runtime; all libs are sourced before any code runs, # so availability does not depend on source order. -# Unlike the other photo listings this one keeps its own find rather than using -# list_photos (photo-list.source.sh): it pipes through maybe_shuffle, not sort, -# because the album's display order is the configurable (seeded) shuffle, not a -# plain sort. Uses $FIND (compat.source.sh) since -printf is a GNU-only action. +# Main album display order, in precedence order (task 8v0): +# 1. CHRONOLOGICAL_ORDER=yes -> chronological_photo_files (EXIF date taken, +# ascending, falling back to mtime for photos without one). +# 2. otherwise -> the historical maybe_shuffle path (seeded/random SHUFFLE, or +# plain filename sort when SHUFFLE=no). +# CHRONOLOGICAL_ORDER therefore takes precedence over SHUFFLE when both are +# set: a chronological album is meant to read as a timeline, so an enabled +# shuffle must not silently re-scramble it. This is deliberately a config-level +# choice rather than an error, so flipping SHUFFLE on/off (e.g. via the CLI +# flags) while experimenting does not require also touching +# CHRONOLOGICAL_ORDER. Unlike the other photo listings this one keeps its own +# find rather than using list_photos (photo-list.source.sh): both order modes +# need the raw filename list before applying their own ordering, not a plain +# sort. Uses $FIND (compat.source.sh) since -printf is a GNU-only action. album_photo_files() { local -r photos_dir="$1"; shift + if [ "$CHRONOLOGICAL_ORDER" = yes ]; then + chronological_photo_files "$photos_dir" + return + fi + "$FIND" "$DIST_DIR/$photos_dir" -maxdepth 1 -type f -printf '%f\n' \ | maybe_shuffle } +# Build the sort key chronological_photo_files uses to order one photo: a +# tab-separated "<group>\t<time>\t<photo>" line consumed by a plain lexicographic +# sort (see chronological_photo_files). EXIF reads always target the INCOMING_DIR +# original (matching photo_exif_tooltip_text/photo_exif_details_html), not the +# resized DIST_DIR copy, so ordering and tooltip/details agree about a photo's +# taken time and both share the same identify cache entry. +# +# group 0 when photo_date_taken (album-metadata.source.sh) found a real EXIF +# date, 1 otherwise. Group 0 always sorts before group 1, so photos +# with a genuine timestamp are never displaced by an approximate +# fallback for photos that lack one. +# time the EXIF date normalized from "YYYY:MM:DD HH:MM:SS" to a 14-digit +# "YYYYMMDDHHMMSS" string (colons/space just stripped -- the calendar +# substrings are untouched, so this stays safe even though the EXIF +# string is not `date -d`-parseable, see docs/stats-exif-audit.md) for +# group 0, or the INCOMING_DIR file's mtime (compat.source.sh $STAT, +# zero-padded so it sorts lexicographically) for group 1. Fixed width +# within each group keeps a plain sort numerically correct. +# photo final tiebreaker so photos sharing a timestamp (e.g. burst shots) or +# missing both an EXIF date and a readable mtime still sort in a +# stable, reproducible order across regenerations of the same +# incoming set. +chronological_sort_key_for_photo() { + local -r photo="$1"; shift + local date_time + local mtime + + date_time=$(photo_date_taken "$photo" "$INCOMING_DIR/$photo") + if [[ "$date_time" =~ ^([0-9]{4}):([0-9]{2}):([0-9]{2})\ ([0-9]{2}):([0-9]{2}):([0-9]{2})$ ]]; then + printf '0\t%s%s%s%s%s%s\t%s\n' \ + "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" "${BASH_REMATCH[3]}" \ + "${BASH_REMATCH[4]}" "${BASH_REMATCH[5]}" "${BASH_REMATCH[6]}" \ + "$photo" + return + fi + + # No usable EXIF date-taken (missing tag or a malformed value): fall back to + # the source file's mtime so ordering still reflects "roughly when this + # photo appeared" rather than an arbitrary readdir order, and stays fully + # deterministic across runs. A missing/unreadable source file (should not + # happen; INCOMING_DIR is validated before generation) reads as mtime 0 so + # this never aborts the render. + mtime=$("$STAT" -c '%Y' "$INCOMING_DIR/$photo" 2>/dev/null) || mtime=0 + printf '1\t%020d\t%s\n' "$mtime" "$photo" +} + +# Chronological ordering for CHRONOLOGICAL_ORDER=yes: every photo in +# DIST_DIR/photos_dir, ordered ascending by chronological_sort_key_for_photo. +# Explicit tab delimiter (rather than plain whitespace splitting) so a filename +# containing a space (e.g. the "04 filename with spaces.jpg" test fixture) stays +# one field instead of fracturing the sort/cut boundaries. +chronological_photo_files() { + local -r photos_dir="$1"; shift + + "$FIND" "$DIST_DIR/$photos_dir" -maxdepth 1 -type f -printf '%f\n' \ + | while IFS= read -r photo; do + chronological_sort_key_for_photo "$photo" + done \ + | sort -t $'\t' -k1,1 -k2,2 \ + | cut -f3- +} + # Pagination single source of truth (task nr0): how many preview pages a given # number of album photos splits into, with at most MAXPREVIEWS photos per page. # album_page_records below realises exactly this many records by grouping the |
