summaryrefslogtreecommitdiff
path: root/src/lib/album-metadata.source.sh
AgeCommit message (Collapse)Author
2026-07-18Add CHRONOLOGICAL_ORDER config option to order albums by EXIF date takenPaul Buetow
Adds a new yes/no config setting (default no, preserving current behavior) that orders the main album's photos by EXIF date taken (ascending) instead of the default filename/shuffle order. Reuses the existing EXIF cache and tag fallback chain (DateTimeOriginal -> DateTimeDigitized -> DateTime) already used for tooltips/details/stats, so ordering never disagrees with what those features show. Photos with no usable EXIF date fall back to their source file's mtime, staying fully deterministic and crash-free. CHRONOLOGICAL_ORDER takes precedence over SHUFFLE when both are enabled, documented in album-photo-select.source.sh and docs/configuration.md. Wired through the config registry (CONFIG_SPECS), validation, CLI flags (--chronological/--no-chronological), --print-config, --dry-run, --verbose logging, and shuriken.json generation metadata. Adds unit and end-to-end tests covering default-off behavior, EXIF-date ordering with shuffle precedence, and mtime fallback for EXIF-less photos. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-06-28Refactor over-length functions into ~30-line helpers (vr0)Paul Buetow
Pure, behavior-preserving refactor: extract _-prefixed helpers from six functions that exceeded the project's 50-line threshold, leaving each original as a thin orchestrator. Generated HTML, dry-run output, shuriken.json, EXIF cache behavior and the flush-grid layout are all byte-identical (full test suite green). Refactored: - _generation_metadata_json -> _generation_metadata_json_head + _generation_metadata_json_settings - print_dry_run_plan -> _print_dry_run_settings + _print_dry_run_files - _photo_exif_tooltip_text_from_values -> _collect_exif_tooltip_parts + _emit_exif_tooltip_parts - cached_photo_identify_output -> _rebuild_photo_identify_cache - render_album_pages -> _render_album_page (one page record) - append_preview_grid -> _roll_and_align_page_tiles + _emit_page_tiles (uniquely-named namerefs to avoid circular-nameref) Left intact (delicate errexit/trap management that must stay in one function scope, where a split would change semantics): source_template_file, refresh_splash, generate_staged, replace_dist_with_staging. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27Fix src/shuriken.sh lib source list missing 5 modulesPaul Buetow
Running `bash src/shuriken.sh --generate ...` directly from a source checkout printed "command not found" for camera_label_from_make_model, photo_exif_values_to and cached_photo_identify_output for every photo, silently emptying EXIF tooltips/details and, with STATS_PAGE=yes, omitting the whole stats/ tree. The hand-maintained source list inside the SHURIKEN_LIB_SOURCES_BEGIN/END marker block had drifted from the authoritative Justfile LIB_SOURCES, missing metadata-label, metadata-cache, stats-aggregate, stats-render and stats-filter-album. `just build` replaces the marker block with LIB_SOURCES when generating bin/shuriken, so the installed binary and the bin-based test suite never noticed; only direct src execution was affected. - Add the 5 missing `source` lines to the marker block in the same order as Justfile LIB_SOURCES, so the two lists now match exactly. - Add tests/cli.sh case test_lib_sources_match_justfile_lib_sources that extracts the marker-block module names and asserts they equal the Justfile LIB_SOURCES (same set and order) to prevent future drift. - shellcheck --check-sourced now follows the 5 newly-sourced libs; suppress the cross-module nameref false positives (SC2178/SC2128/ SC2154) with explained directives and genuinely fix SC2004 (counts_ref[$key] -> [key]) and quote the TITLE default (${TITLE:-}). bin/shuriken changes only by these propagated lib edits; the marker-block source list it generates is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24Unify escape/date helper API; fix current_date_text cachingPaul Buetow
Make the template.source.sh escape/date helper family follow one consistent shape: each escaper now has a nameref <name>_to form (hot path, writes a named variable) plus a thin printf wrapper <name> that delegates to it. The leading "_" now exclusively marks private helpers; the public escape API (called from sibling modules) is unprefixed. - current_date_text now delegates to current_date_text_to so both forms share the SHURIKEN_CURRENT_DATE_TEXT cache; the printf form no longer silently re-execs `date` on every direct call. Output unchanged. - Drop the misleading "_" prefix on the public escape API and update all callers: _html_escape->html_escape, _css_string_escape-> css_string_escape, _json_string->json_string, _json_bool->json_bool, _json_string_escape->json_string_escape. - Add the missing JSON nameref forms: json_string_escape_to, json_string_to, json_bool_to (printf wrappers delegate to them). - Add tests: JSON printf-vs-nameref parity and a current_date_text caching + nameref-parity check. No escaping/encoding or date output changes -- API-shape/perf only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24Split album-metadata.source.sh into focused modules (6r0)Paul Buetow
album-metadata.source.sh aggregated six unrelated concerns. Move each along its existing seam (pure mechanical move, no logic changes): - EXIF presentation (photo_exif_details_html, tooltip helpers, the _photo_exif_values_to wrapper) stays in album-metadata.source.sh, which is now EXIF-presentation only. - File counting (count_files, count_incoming_images, count_tree_files) -> image.source.sh, which already owns incoming_image_files; count_incoming_images is a direct wrapper of it. - Tarball naming (tarball_name_plan, generated_tarball_name) -> archive.source.sh, which already owns tarball()/resolve_tar_opts. - Generation metadata + JSON (_collect_generation_metadata, _generation_metadata_json, write_generation_metadata) -> new generation-metadata.source.sh. - Dry-run (dry_run, collect_dry_run_*, print_dry_run_plan) -> new dry-run.source.sh. - clear_exif_cache -> metadata-cache.source.sh, next to the cache primitive cached_photo_identify_output. LIB_SOURCES (Justfile + src/shuriken.sh): insert generation-metadata and dry-run right after album-metadata, before album-render/album. They depend on image, archive, template and metadata-cache (all earlier or runtime-only calls), and are consumed by the album coordinator and the dry-run CLI action, which come later. bin/shuriken regenerated via just build. File-header comments updated to reflect the new homes. just test, just shellcheck, just check-generated and git diff --check all pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24Promote single canonical identify-stream EXIF parser into metadata-cache (8r0)Paul Buetow
The `identify -verbose` EXIF-line regex and its array-fill loop were duplicated in three places that had already drifted: album-metadata's photo_exif_details_html and _photo_exif_values_to (exif: only) and stats-aggregate's _stats_parse_identify_stream (exif: plus a native Geometry -> __geometry path). Promote one canonical parser, photo_exif_values_to, into metadata-cache.source.sh next to its sibling cache primitive cached_photo_identify_output. It reads an identify stream from stdin and fills a nameref associative array; it is a strict superset of all three former sites (bare exif: tag keys plus the synthetic __geometry key). - stats accumulate_photo_stats now calls photo_exif_values_to (stdin); _stats_parse_identify_stream is removed. - album _photo_exif_values_to is a thin wrapper that pipes cached_photo_identify_output through the canonical parser. - album photo_exif_details_html consumes the same parser and skips the __geometry key it does not display. metadata-cache is sourced before both consumers in LIB_SOURCES, so the canonical parser is available at use time. Regenerated bin/shuriken via `just build`. Added test_shared_identify_parser_returns_exif_and_geometry asserting one parse yields both an exif: key and __geometry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22Add large 2x2 feature tiles and a CSS-grid overviewPaul Buetow
Allow a single photo to be blown up into a large "feature" tile that spans a 2x2 block of the album overview, controlled by a new THUMB_FEATURE_PERCENT (0-100, default 10; 0 disables). Each tile rolls for a feature first, then for a subdivision, otherwise stays a normal square. To pack mixed-size tiles (normal 1x1, subdivided 1x1, feature 2x2) without gaps, the overview is now a real CSS grid with grid-auto-flow: dense, so smaller tiles backfill the holes a 2x2 feature would leave. Tile spacing moved from per-image padding to the grid gap. Feature tiles reuse the img.thumb class and its dramatic hover. THUMB_FEATURE_PERCENT is wired through the same layers as THUMB_SUBDIVIDE_PERCENT: config defaults, 0..100 validation, --print-config, the --feature CLI flag, the usage text, and the shuriken.json / --dry-run metadata, with docs and tests updated. Generated HTML and CSS pass the W3C Nu HTML checker and CSS validator. Setting both percentages to 0 reproduces the previous all-1x1 grid. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22Add dynamic subdivided thumbnail tilesPaul Buetow
Make the album preview grid livelier: with a configurable probability (THUMB_SUBDIVIDE_PERCENT, default 30%) a square thumbnail tile is subdivided into several smaller thumbnails packed into the same square footprint, chosen at random from: - quad: 2x2 squares (4 photos) - two-wide: two stacked full-width strips (2 photos) - squares+wide: two squares plus one full-width strip, strip on the top or the bottom (3 photos) Each sub-thumbnail stays its own clickable photo with its own view page; subdivision only groups consecutive photos visually, so preview numbering and the view/details/redirect pages are unchanged. No new images are generated (CSS object-fit crops the existing aspect-correct thumbs into squares or wide strips). Sub-thumbnails get the same random entry animation and the same dramatic hover (flip/scale/rotate/filter) as full thumbs. The layout choice reuses the seeded random_index, so builds stay reproducible under RANDOM_SEED. THUMB_SUBDIVIDE_PERCENT=0 reproduces the previous output byte-for-byte. The new option is wired through the config defaults, validation (0..100), --print-config, the --subdivide CLI flag, the usage text, and the shuriken.json / --dry-run metadata, with docs and tests updated. Generated HTML and CSS pass the W3C Nu HTML checker and CSS validator. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17pn0 decouple stats from album internalsPaul Buetow
Stats reached directly into album-module internals: the private global ALBUM_VIEW_PAGE_BY_PHOTO and the EXIF cache reader cached_photo_identify_output. Introduce a clean boundary, behaviour and generated HTML byte-identical. - Promote the EXIF identify cache primitive (cached_photo_identify_output plus its private helpers photo_cache_signature and print_cached_photo_identify_output) out of album-metadata.source.sh into a new shared src/lib/metadata-cache.source.sh, sourced before both album and stats (right after metadata-label in LIB_SOURCES). It is a low-level metadata primitive used by both consumers, so it no longer belongs to album internals. Signature/behaviour unchanged. - Add album_view_page_for_photo accessor in album-render.source.sh as the documented public API; keep ALBUM_VIEW_PAGE_BY_PHOTO as the album's private backing store. stats-filter-album.source.sh now calls the accessor instead of indexing the global, so a change to album page naming/caching stays contained in the album module. - Add test_album_stats_decoupling_boundary asserting the accessor returns the backing-store value and that the assembled bin/shuriken keeps the cache primitive in the shared module and no longer indexes the global from the stats filter section. Existing stats/album tests unchanged. Verified: 3-image fixture (STATS_PAGE=yes, fixed seed) diff -r of stashed original vs new build is byte-identical across all 47 dist files (only the inherent generated_at timestamp normalized). just test, just shellcheck, just check-generated and git diff --check all pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17mn0 share camera Make+Model dedup helperPaul Buetow
The rule that joins a camera's EXIF Make + Model into one label while avoiding a duplicated manufacturer prefix (e.g. "Canon Canon EOS 5D" -> "Canon EOS 5D") was implemented independently in the album tooltip builder and the stats leaderboard tally. Extract it into a single shared helper camera_label_from_make_model in the new src/lib/metadata-label.source.sh, sourced before both callers. Both prior implementations were behavior-identical (empty model -> make, empty make -> model, exact/prefix dedup, case-sensitive), so this is a pure DRY refactor with no observable output change. Added a focused unit test covering dedup, plain concatenation and the empty-field edge cases. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-169n0 warn on identify failure instead of caching empty EXIFPaul Buetow
cached_photo_identify_output() swallowed all ImageMagick errors with 'imagemagick_identify ... || true', so a corrupt photo or identify failure left the cache with only the signature line and no EXIF. That signature-only file was a valid-looking cache hit, so the photo rendered with empty tooltip/stats and the failure was never retried or warned about again. Now capture identify's exit status; on failure, log_warning naming the photo and rm -f the cache file so the next run retries instead of reusing an empty result. The function still returns 0 so one unreadable photo does not abort generation (it runs in backgrounded render jobs under set -euo pipefail) -- the photo just renders without EXIF, now with a warning. Adds tests/cli.sh test_generate_warns_and_skips_cache_on_identify_failure and a TEST_IMAGEMAGICK_IDENTIFY_FAIL hook in the fake ImageMagick to drive a failing identify; asserts exit 0, a warning naming the photo, the photo still rendered, and that the failed photo's cache is absent while a successful photo's cache is present. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16in0 split album.source.sh into cohesive modulesPaul Buetow
Refactor the ~1442-line album.source.sh ([SRP] violation) into three cohesive modules, leaving album.source.sh a thin coordinator. Pure code move, no behavior change -- whole functions and their doc comments moved verbatim; the assembled bin/shuriken contains the same function set. - image-pipeline.source.sh: ImageMagick derivative generation (create_photo_derivatives, create_all_photo_derivatives) and photo asset prep (prepare_generation_photo_assets). - album-metadata.source.sh: EXIF identify caching, exif tooltip/details helpers, file counts, shuriken.json metadata, EXIF cache clearing and the dry-run plan. - album-render.source.sh: preview/view/details/splash/redirect page rendering, the parallel render-job orchestration and the ALBUM_VIEW_PAGE_BY_PHOTO global. - album.source.sh: thin coordinator (generate, refresh_splash, generate_stats_pages, site-asset/archive/html-clean helpers). Add the three modules to LIB_SOURCES (Justfile) and to the SHURIKEN_LIB_SOURCES markers in src/shuriken.sh, ordered after random and before album.source.sh / stats.source.sh. just build, just test, just shellcheck, just check-generated and git diff --check all pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>