diff options
| author | Paul Buetow <paul@buetow.org> | 2026-06-16 18:19:51 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-06-16 18:19:51 +0300 |
| commit | fe59a701586972793ea110b465f65d47b825fb8e (patch) | |
| tree | 87ca3f64b695a0a8fa63d578be1aa4937bb59e01 /src/lib/album.source.sh | |
| parent | cd7cf797bc633700c5ee7e986077f366c000f0b9 (diff) | |
in0 split album.source.sh into cohesive modules
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>
Diffstat (limited to 'src/lib/album.source.sh')
| -rw-r--r-- | src/lib/album.source.sh | 1324 |
1 files changed, 6 insertions, 1318 deletions
diff --git a/src/lib/album.source.sh b/src/lib/album.source.sh index 6c78ffa..e4b5e9d 100644 --- a/src/lib/album.source.sh +++ b/src/lib/album.source.sh @@ -1,1182 +1,9 @@ -# Maps each album photo filename to its view-page basename ("<page>-<preview>") -# as assigned during render_album_pages. The stats filter mini-albums read this -# so each view page can link "Details" to the album's own details page for the -# photo. Declared globally so it always exists for callers even when no album -# was rendered. -declare -gA ALBUM_VIEW_PAGE_BY_PHOTO=() - -album_photo_files() { - local -r photos_dir="$1"; shift - - find "$DIST_DIR/$photos_dir" -maxdepth 1 -type f -printf '%f\n' \ - | maybe_shuffle -} - -start_preview_page() { - local -r photos_dir="$1"; shift - local -r html_dir="$1"; shift - local -r blurs_dir="$1"; shift - local -r backhref="$1"; shift - local -r page_name="$1"; shift - local -r header_bar="$1"; shift - local background_image - - background_image=$(randomphoto "$photos_dir" "$page_name") - template header "$page_name.html" \ - html_dir "$html_dir" \ - backhref "$backhref" \ - blurs_dir "$blurs_dir" \ - background_image "$background_image" \ - show_header_bar "$header_bar" -} - -finish_preview_page() { - local -r page_name="$1"; shift - local -r html_dir="$1"; shift - local -r backhref="$1"; shift - local -r tarball_name="$1"; shift - - template footer "$page_name.html" \ - html_dir "$html_dir" \ - backhref "$backhref" \ - tarball_name "$tarball_name" -} - -finish_preview_page_with_next() { - local -r page_name="$1"; shift - local -r html_dir="$1"; shift - local -r backhref="$1"; shift - local -r tarball_name="$1"; shift - local -r next_page="$1"; shift - local -r prev_page="$1"; shift - - template next "$page_name.html" \ - html_dir "$html_dir" \ - next "$next_page" \ - prev "$prev_page" - finish_preview_page "$page_name" "$html_dir" "$backhref" "$tarball_name" -} - -render_previous_page_link() { - local -r page_name="$1"; shift - local -r html_dir="$1"; shift - local -r prev_page="$1"; shift - - template prev "$page_name.html" \ - html_dir "$html_dir" \ - prev "$prev_page" -} - -# Assemble one complete preview page (page-N.html) in a single call: header, -# optional previous-page link, every thumbnail in order, then the footer (with -# a next-page link unless this is the last page). The appends to the one page -# file happen here, in sequence, so the per-page ordering is preserved even when -# this whole function runs as one backgrounded render job (parallelism is only -# ACROSS pages, never within a page). The page's photos are the trailing -# positional arguments; each photo's preview index is its 1-based position. -render_full_preview_page() { - local -r photos_dir="$1"; shift - local -r html_dir="$1"; shift - local -r thumbs_dir="$1"; shift - local -r blurs_dir="$1"; shift - local -r backhref="$1"; shift - local -r tarball_name="$1"; shift - local -r page_name="$1"; shift - local -r page_num="$1"; shift - local -r header_bar="$1"; shift - local -r prev_page="$1"; shift - local -r next_page="$1"; shift - local -i preview_num=0 - local photo - - start_preview_page \ - "$photos_dir" "$html_dir" "$blurs_dir" "$backhref" "$page_name" \ - "$header_bar" - if [ -n "$prev_page" ]; then - render_previous_page_link "$page_name" "$html_dir" "$prev_page" - fi - - for photo in "$@"; do - (( ++preview_num )) - render_preview_thumbnail \ - "$page_name" "$html_dir" "$thumbs_dir" "$backhref" \ - "$page_num" "$preview_num" "$photo" - done - - if [ -n "$next_page" ]; then - finish_preview_page_with_next \ - "$page_name" "$html_dir" "$backhref" "$tarball_name" \ - "$next_page" "$prev_page" - else - finish_preview_page "$page_name" "$html_dir" "$backhref" "$tarball_name" - fi -} - -# Enqueue one complete preview page as a background render job, throttled to -# IMAGE_JOBS via the shared template render job pool. Mirrors -# queue_album_view_render_job: wait for a free slot, background the whole-page -# assembly, then track its pid/label so a failed job flips render_failed and -# makes generation fail loudly. The page's photos are passed as trailing -# positional args; the background subshell forks a private copy of them, so the -# caller is free to reuse its per-page accumulator for the next page. -queue_preview_page_render_job() { - local -r photos_dir="$1"; shift - local -r html_dir="$1"; shift - local -r thumbs_dir="$1"; shift - local -r blurs_dir="$1"; shift - local -r backhref="$1"; shift - local -r tarball_name="$1"; shift - local -r page_name="$1"; shift - local -r page_num="$1"; shift - local -r header_bar="$1"; shift - local -r prev_page="$1"; shift - local -r next_page="$1"; shift - # shellcheck disable=SC2178 - local -n render_job_pids_ref="$1"; shift - # Passed by name to wait_for_template_render_job_slot. - # shellcheck disable=SC2034,SC2178 - local -n render_job_statuses_ref="$1"; shift - # Passed by name to wait_for_template_render_job_slot and assigned below. - # shellcheck disable=SC2034,SC2178 - local -n render_job_labels_ref="$1"; shift - # shellcheck disable=SC2178 - local -n render_failed_ref="$1"; shift - # Remaining positional args ("$@") are this page's photos in order. - - wait_for_template_render_job_slot \ - render_job_pids_ref \ - render_job_statuses_ref \ - render_job_labels_ref \ - render_failed_ref - render_full_preview_page \ - "$photos_dir" \ - "$html_dir" \ - "$thumbs_dir" \ - "$blurs_dir" \ - "$backhref" \ - "$tarball_name" \ - "$page_name" \ - "$page_num" \ - "$header_bar" \ - "$prev_page" \ - "$next_page" \ - "$@" & - render_job_pids_ref+=("$!") - render_job_labels_ref["$!"]="template render job for preview $page_name" -} - -render_preview_thumbnail() { - local -r page_name="$1"; shift - local -r html_dir="$1"; shift - local -r thumbs_dir="$1"; shift - local -r backhref="$1"; shift - local -r page_num="$1"; shift - local -r preview_num="$1"; shift - local -r photo_file="$1"; shift - local animation_class - - animation_class=$(random_animation_css_class slow "$photo_file") - template preview "$page_name.html" \ - html_dir "$html_dir" \ - backhref "$backhref" \ - thumbs_dir "$thumbs_dir" \ - page_num "$page_num" \ - preview_num "$preview_num" \ - photo "$photo_file" \ - animation_class "$animation_class" -} - -render_view_page() { - local -r html_dir="$1"; shift - local -r photos_dir="$1"; shift - local -r blurs_dir="$1"; shift - local -r backhref="$1"; shift - local -r tarball_name="$1"; shift - local -r page_num="$1"; shift - local -r preview_num="$1"; shift - local -r photo_file="$1"; shift - local animation_class - local exif_tooltip_text - - template header "$page_num-$preview_num.html" \ - html_dir "$html_dir" \ - backhref "$backhref" \ - blurs_dir "$blurs_dir" \ - background_image "$photo_file" \ - show_header_bar 'no' - - animation_class=$(random_animation_css_class fast "$photo_file") - # Reuse the same EXIF tooltip as the details page so hovering the image in - # the normal view shows the camera/exposure summary (reads the shared - # identify cache, so no extra ImageMagick work). - exif_tooltip_text=$( - photo_exif_tooltip_text "$photo_file" "$INCOMING_DIR/$photo_file" - ) - template view "$page_num-$preview_num.html" \ - html_dir "$html_dir" \ - backhref "$backhref" \ - photos_dir "$photos_dir" \ - page_num "$page_num" \ - preview_num "$preview_num" \ - photo "$photo_file" \ - animation_class "$animation_class" \ - exif_tooltip "$exif_tooltip_text" - template footer "$page_num-$preview_num.html" \ - html_dir "$html_dir" \ - backhref "$backhref" \ - tarball_name "$tarball_name" -} - -photo_cache_signature() { - local -r photo="$1"; shift - local -r photo_path="$1"; shift - local stat_output - - stat_output=$(stat -c '%s:%Y' "$photo_path") - printf '%s:%s\n' "$photo" "$stat_output" -} - -print_cached_photo_identify_output() { - local -r cache_file="$1"; shift - local line - local skipped_signature=no - - while IFS= read -r line || [ -n "$line" ]; do - if [ "$skipped_signature" = no ]; then - skipped_signature=yes - continue - fi - printf '%s\n' "$line" - done < "$cache_file" -} - -cached_photo_identify_output() { - local -r photo="$1"; shift - local -r photo_path="$1"; shift - local cache_dir - local cache_file - local cached_signature='' - local current_signature - - # Persist the EXIF cache in a volatile ./cache directory parallel to ./dist - # (the staging dir is a sibling of the final dist, so dirname "$DIST_DIR" is - # the working dir in both staging and direct contexts). Keeping it outside - # dist means it survives a fresh/cleared dist and is never deployed, so an - # unchanged photo skips the slow `identify -verbose` on every regenerate. - cache_dir="$(dirname "$DIST_DIR")/cache/exif" - cache_file="$cache_dir/$photo.txt" - current_signature=$(photo_cache_signature "$photo" "$photo_path") - - # Reuse the cache when its signature still matches the source file. --force - # is handled once up front by clear_exif_cache (which empties this directory), - # so the first call per photo then rebuilds it and the rest of the run reuses - # it -- exactly one identify per photo even under force. - if [ -f "$cache_file" ]; then - IFS= read -r cached_signature < "$cache_file" || true - if [ "$cached_signature" = "$current_signature" ]; then - print_cached_photo_identify_output "$cache_file" - return - fi - fi - - mkdir -p "$cache_dir" - printf '%s\n' "$current_signature" > "$cache_file" - imagemagick_identify -verbose "$photo_path" >> "$cache_file" 2>/dev/null \ - || true - print_cached_photo_identify_output "$cache_file" -} - -photo_exif_details_html() { - local -r photo="$1"; shift - local -r photo_path="$1"; shift - local key - local key_html - local line - local value - local value_html - local -i exif_count=0 - - while IFS= read -r line; do - if [[ "$line" =~ ^[[:space:]]*exif:([^:]+):[[:space:]]*(.*)$ ]]; then - key="exif:${BASH_REMATCH[1]}" - value="${BASH_REMATCH[2]}" - key_html=$(_html_escape "$key") - value_html=$(_html_escape "$value") - - if (( exif_count == 0 )); then - printf '<table class="details">\n' - printf '<tbody>\n' - fi - - printf '<tr><th>%s</th><td>%s</td></tr>\n' \ - "$key_html" \ - "$value_html" - (( ++exif_count )) - fi - done < <(cached_photo_identify_output "$photo" "$photo_path") - - if (( exif_count == 0 )); then - printf '<p class="details-empty">No EXIF details available.</p>\n' - return - fi - - printf '</tbody>\n' - printf '</table>\n' -} - -_first_exif_value_to() { - local -n output_ref="$1"; shift - local -n exif_ref="$1"; shift - local key - - output_ref='' - for key in "$@"; do - if [ -n "${exif_ref[$key]:-}" ]; then - # shellcheck disable=SC2034 - output_ref="${exif_ref[$key]}" - return - fi - done -} - -_photo_exif_values_to() { - local -n output_ref="$1"; shift - local -r photo="$1"; shift - local -r photo_path="$1"; shift - local line - - output_ref=() - while IFS= read -r line; do - if [[ "$line" =~ ^[[:space:]]*exif:([^:]+):[[:space:]]*(.*)$ ]]; then - # output_ref writes to the caller-provided associative array. - # shellcheck disable=SC2034 - output_ref["${BASH_REMATCH[1]}"]="${BASH_REMATCH[2]}" - fi - done < <(cached_photo_identify_output "$photo" "$photo_path") -} - -_photo_exif_tooltip_text_from_values() { - local -r exif_name="$1"; shift - local -n values_ref="$exif_name" - local aperture - local camera - local date_time - local iso - local key - local make - local model - local separator='' - local shutter_speed - local -a tooltip_parts=() - - make="${values_ref[Make]:-}" - model="${values_ref[Model]:-}" - camera="$make" - if [ -n "$model" ]; then - if [ -n "$make" ]; then - case "$model" in - "$make"|"$make "*) - camera="$model" - ;; - *) - camera="$make $model" - ;; - esac - else - camera="$model" - fi - fi - - _first_exif_value_to aperture "$exif_name" FNumber ApertureValue - _first_exif_value_to iso "$exif_name" \ - ISOSpeedRatings PhotographicSensitivity ISO - _first_exif_value_to shutter_speed "$exif_name" \ - ExposureTime ShutterSpeedValue - _first_exif_value_to date_time "$exif_name" \ - DateTimeOriginal DateTimeDigitized DateTime - - if [ -n "$camera" ]; then - tooltip_parts+=("Camera: $camera") - fi - if [ -n "$aperture" ]; then - tooltip_parts+=("Aperture: $aperture") - fi - if [ -n "$iso" ]; then - tooltip_parts+=("ISO: $iso") - fi - if [ -n "$shutter_speed" ]; then - tooltip_parts+=("Shutter speed: $shutter_speed") - fi - if [ -n "$date_time" ]; then - tooltip_parts+=("Taken: $date_time") - fi - - for key in "${tooltip_parts[@]}"; do - printf '%s%s' "$separator" "$key" - separator='; ' - done - if (( ${#tooltip_parts[@]} > 0 )); then - printf '\n' - fi -} - -photo_exif_tooltip_text() { - local -r photo="$1"; shift - local -r photo_path="$1"; shift - # exif_values is populated and read through nameref helpers. - # shellcheck disable=SC2034 - local -A exif_values=() - - _photo_exif_values_to exif_values "$photo" "$photo_path" - _photo_exif_tooltip_text_from_values exif_values -} - -render_details_page() { - local -r html_dir="$1"; shift - local -r photos_dir="$1"; shift - local -r blurs_dir="$1"; shift - local -r backhref="$1"; shift - local -r tarball_name="$1"; shift - local -r page_num="$1"; shift - local -r preview_num="$1"; shift - local -r photo_file="$1"; shift - local animation_class - local exif_details_html - local exif_tooltip_text - - template header "$page_num-$preview_num-details.html" \ - html_dir "$html_dir" \ - backhref "$backhref" \ - blurs_dir "$blurs_dir" \ - background_image "$photo_file" \ - show_header_bar 'no' - - animation_class=$(random_animation_css_class fast "$photo_file") - exif_details_html=$( - photo_exif_details_html "$photo_file" "$INCOMING_DIR/$photo_file" - ) - exif_tooltip_text=$( - photo_exif_tooltip_text "$photo_file" "$INCOMING_DIR/$photo_file" - ) - template details "$page_num-$preview_num-details.html" \ - html_dir "$html_dir" \ - backhref "$backhref" \ - photos_dir "$photos_dir" \ - page_num "$page_num" \ - preview_num "$preview_num" \ - photo "$photo_file" \ - animation_class "$animation_class" \ - exif_details "$exif_details_html" \ - exif_tooltip "$exif_tooltip_text" - template footer "$page_num-$preview_num-details.html" \ - html_dir "$html_dir" \ - backhref "$backhref" \ - tarball_name "$tarball_name" -} - -create_photo_derivatives() { - local -r photos_dir="$1"; shift - local -r thumbs_dir="$1"; shift - local -r blurs_dir="$1"; shift - local -r photo="$1"; shift - local dirname - local height - - if [[ -f "$DIST_DIR/$thumbs_dir/$photo" \ - && -f "$DIST_DIR/$blurs_dir/$photo" ]]; then - log_verbose "Skipped existing thumb and blur" \ - "$(_display_path "$DIST_DIR/$thumbs_dir/$photo") and" \ - "$(_display_path "$DIST_DIR/$blurs_dir/$photo")" - return - fi - - dirname="$DIST_DIR/$thumbs_dir" - mkdir -p "$dirname" - log_info "Creating thumb $(_display_path "$DIST_DIR/$thumbs_dir/$photo")" - # Double the height, as CSS scales images based on boxing too. - height=$(( THUMBHEIGHT * 2 )) - imagemagick \ - "$DIST_DIR/$photos_dir/$photo" \ - -geometry "x$height" \ - "$DIST_DIR/$thumbs_dir/$photo" - - dirname="$DIST_DIR/$blurs_dir" - mkdir -p "$dirname" - log_info "Creating blur $(_display_path "$DIST_DIR/$blurs_dir/$photo")" - imagemagick \ - "$DIST_DIR/$thumbs_dir/$photo" \ - -flip \ - -blur 0x8 \ - "$DIST_DIR/$blurs_dir/$photo" -} - -create_all_photo_derivatives() { - local -r photos_dir="$1"; shift - local -r thumbs_dir="$1"; shift - local -r blurs_dir="$1"; shift - local -i failed=0 - local -a image_job_pids=() - # Passed by name to wait_for_image_job_slot and wait_for_image_jobs. - # shellcheck disable=SC2034 - local -A image_job_labels=() - # Passed by name to wait_for_image_job_slot and wait_for_image_jobs. - # shellcheck disable=SC2034 - local -A image_job_statuses=() - local photo - - while IFS= read -r photo; do - wait_for_image_job_slot \ - image_job_pids \ - image_job_statuses \ - image_job_labels \ - failed - create_photo_derivatives "$photos_dir" "$thumbs_dir" "$blurs_dir" \ - "$photo" & - image_job_pids+=("$!") - # Read through a nameref in the job-pool helpers. - # shellcheck disable=SC2034 - image_job_labels["$!"]="image derivative job for photo $photo" - done < <( - find "$DIST_DIR/$photos_dir" -maxdepth 1 -type f -printf '%f\n' \ - | sort - ) - - wait_for_image_jobs \ - image_job_pids \ - image_job_statuses \ - image_job_labels \ - failed - if (( failed != 0 )); then - return 1 - fi -} - -render_photo_view_and_details() { - local -r photos_dir="$1"; shift - local -r blurs_dir="$1"; shift - local -r html_dir="$1"; shift - local -r backhref="$1"; shift - local -r tarball_name="$1"; shift - local -r page_num="$1"; shift - local -r preview_num="$1"; shift - local -r photo="$1"; shift - - render_view_page \ - "$html_dir" \ - "$photos_dir" \ - "$blurs_dir" \ - "$backhref" \ - "$tarball_name" \ - "$page_num" \ - "$preview_num" \ - "$photo" - render_details_page \ - "$html_dir" \ - "$photos_dir" \ - "$blurs_dir" \ - "$backhref" \ - "$tarball_name" \ - "$page_num" \ - "$preview_num" \ - "$photo" -} - -record_rendered_view_page() { - # shellcheck disable=SC2178 - local -n view_pages_ref="$1"; shift - # shellcheck disable=SC2178 - local -n last_views_ref="$1"; shift - local -r page="$1"; shift - local -r preview="$1"; shift - - if [ -z "${last_views_ref[$page]+set}" ]; then - view_pages_ref+=("$page") - fi - last_views_ref["$page"]="$preview" -} - -# Render every navigation redirect for a single view page (the prev/next -# wrap-around stubs that bounce N-0 / N-(last+1) to the neighbouring page). -# Each redirect is its own self-contained file (the template overwrites it), so -# this whole group is safe to run as one independent background job; only the -# files for distinct pages are produced here. The wrap-around redirects for the -# very last page (0-MAXPREVIEWS and the loop-to-1 links) are emitted as part of -# that page's group. -render_page_view_redirects() { - local -r html_dir="$1"; shift - local -ri page="$1"; shift - local -ri lastview="$1"; shift - local -ri max_page="$1"; shift - local -r prevredirect="${page}-0" - local -r nextredirect="${page}-$(( lastview + 1 ))" - - template redirect "$prevredirect.html" \ - html_dir "$html_dir" \ - redirect_page "$(( page - 1 ))-${MAXPREVIEWS}" - template redirect "$prevredirect-details.html" \ - html_dir "$html_dir" \ - redirect_page "$(( page - 1 ))-${MAXPREVIEWS}-details" - - if (( page == max_page )); then - template redirect "0-$MAXPREVIEWS.html" \ - html_dir "$html_dir" \ - redirect_page "${page}-$lastview" - template redirect "0-$MAXPREVIEWS-details.html" \ - html_dir "$html_dir" \ - redirect_page "${page}-$lastview-details" - template redirect "$nextredirect.html" \ - html_dir "$html_dir" \ - redirect_page '1-1' - template redirect "$nextredirect-details.html" \ - html_dir "$html_dir" \ - redirect_page '1-1-details' - else - template redirect "$nextredirect.html" \ - html_dir "$html_dir" \ - redirect_page "$(( page + 1 ))-1" - template redirect "$nextredirect-details.html" \ - html_dir "$html_dir" \ - redirect_page "$(( page + 1 ))-1-details" - fi -} - -# Render all navigation redirects in parallel, one background job per view page, -# throttled to IMAGE_JOBS via the shared template render job pool. This blocks -# until every redirect is on disk (and fails loudly if any job failed), so -# callers can rely on the redirects existing once it returns -- the redirect -# groups are mutually independent, so only timing changes, not output. -render_view_redirects() { - local -r html_dir="$1"; shift - # shellcheck disable=SC2178 - local -n view_pages_ref="$1"; shift - # shellcheck disable=SC2178 - local -n redirect_last_views_ref="$1"; shift - local max_page - local page - # Render job pool, throttled to IMAGE_JOBS by the job-pool helpers. - # shellcheck disable=SC2034 - local -a render_job_pids=() - # shellcheck disable=SC2034 - local -A render_job_statuses=() - # shellcheck disable=SC2034 - local -A render_job_labels=() - local -i render_failed=0 - - if (( ${#view_pages_ref[@]} == 0 )); then - return - fi - - max_page=${view_pages_ref[$(( ${#view_pages_ref[@]} - 1 ))]} - - for page in "${view_pages_ref[@]}"; do - wait_for_template_render_job_slot \ - render_job_pids render_job_statuses render_job_labels render_failed - render_page_view_redirects \ - "$html_dir" "$page" "${redirect_last_views_ref[$page]}" \ - "$max_page" & - render_job_pids+=("$!") - # shellcheck disable=SC2034 - render_job_labels["$!"]="template render job for redirect page $page" - done - - wait_for_template_render_jobs \ - render_job_pids render_job_statuses render_job_labels render_failed - if (( render_failed != 0 )); then - return 1 - fi -} - -render_album_index_redirect() { - local -r html_dir="$1"; shift - - template 'redirect' 'index.html' \ - html_dir "$html_dir" \ - redirect_page 'page-1' -} - -render_album_splash_page() { - local -r photos_dir="$1"; shift - local -r html_dir="$1"; shift - local -r blurs_dir="$1"; shift - local -r backhref="$1"; shift - local html='index.html' - local photo - - if (( $# > 0 )); then - html="$1" - shift - fi - - photo=$(random_splash_photo "$photos_dir" "$blurs_dir") - template 'splash' "$html" \ - html_dir "$html_dir" \ - backhref "$backhref" \ - blurs_dir "$blurs_dir" \ - background_image "$photo" \ - photos_dir "$photos_dir" \ - photo "$photo" \ - enter_page 'page-1' -} - -render_album_index() { - local -r photos_dir="$1"; shift - local -r html_dir="$1"; shift - local -r blurs_dir="$1"; shift - local -r backhref="$1"; shift - - if [ "$SPLASH_PAGE" = yes ]; then - render_album_splash_page "$photos_dir" "$html_dir" "$blurs_dir" \ - "$backhref" - else - render_album_index_redirect "$html_dir" - fi -} - -album_page_name() { - local -r page_num="$1"; shift - - printf 'page-%s\n' "$page_num" -} - -queue_album_view_render_job() { - local -r photos_dir="$1"; shift - local -r blurs_dir="$1"; shift - local -r html_dir="$1"; shift - local -r backhref="$1"; shift - local -r tarball_name="$1"; shift - local -r page_num="$1"; shift - local -r preview_num="$1"; shift - local -r photo="$1"; shift - # shellcheck disable=SC2178 - local -n render_job_pids_ref="$1"; shift - # Passed by name to wait_for_template_render_job_slot. - # shellcheck disable=SC2034,SC2178 - local -n render_job_statuses_ref="$1"; shift - # Passed by name to wait_for_template_render_job_slot and assigned below. - # shellcheck disable=SC2034,SC2178 - local -n render_job_labels_ref="$1"; shift - # shellcheck disable=SC2178 - local -n render_failed_ref="$1"; shift - - wait_for_template_render_job_slot \ - render_job_pids_ref \ - render_job_statuses_ref \ - render_job_labels_ref \ - render_failed_ref - render_photo_view_and_details \ - "$photos_dir" \ - "$blurs_dir" \ - "$html_dir" \ - "$backhref" \ - "$tarball_name" \ - "$page_num" \ - "$preview_num" \ - "$photo" & - render_job_pids_ref+=("$!") - render_job_labels_ref["$!"]="template render job for photo $photo" -} - -wait_for_album_view_render_jobs() { - # shellcheck disable=SC2178 - local -n render_job_pids_ref="$1"; shift - # Passed by name to wait_for_template_render_jobs. - # shellcheck disable=SC2034,SC2178 - local -n render_job_statuses_ref="$1"; shift - # Passed by name to wait_for_template_render_jobs. - # shellcheck disable=SC2034,SC2178 - local -n render_job_labels_ref="$1"; shift - # shellcheck disable=SC2178 - local -n render_failed_ref="$1"; shift - - wait_for_template_render_jobs \ - render_job_pids_ref \ - render_job_statuses_ref \ - render_job_labels_ref \ - render_failed_ref - if (( render_failed_ref != 0 )); then - return 1 - fi -} - -# Group the album's photos into pages of at most MAXPREVIEWS, in their final -# (shuffled/sorted) order. The result is emitted one line per page as a -# tab-separated record "<page_num>\t<photo>\t<photo>..." so the caller can walk -# pages without keeping every page in memory at once. Order is fully -# deterministic (album_photo_files already applies the seeded shuffle), so the -# downstream parallelism only changes timing, never which photo lands where. -album_page_records() { - local -r photos_dir="$1"; shift - local photo - local -i num=1 - local -i count=0 - local line='' - - while IFS= read -r photo; do - if (( count == MAXPREVIEWS )); then - printf '%d\t%s\n' "$num" "$line" - (( ++num )) - count=0 - line='' - fi - if (( count == 0 )); then - line="$photo" - else - line="$line"$'\t'"$photo" - fi - (( ++count )) - done < <(album_photo_files "$photos_dir") - - if (( count > 0 )); then - printf '%d\t%s\n' "$num" "$line" - fi -} - -# Per-photo bookkeeping shared by the album loop: queue the view+details render -# job, record the page/preview as a rendered view page (for redirect generation) -# and remember the photo -> view-page mapping for the stats mini-albums. Kept -# separate from the preview-page assembly so each concern stays small. -_album_record_view_photo() { - local -r photos_dir="$1"; shift - local -r blurs_dir="$1"; shift - local -r html_dir="$1"; shift - local -r backhref="$1"; shift - local -r tarball_name="$1"; shift - local -ri page_num="$1"; shift - local -ri preview_num="$1"; shift - local -r photo="$1"; shift - local -r pids_name="$1"; shift - local -r statuses_name="$1"; shift - local -r labels_name="$1"; shift - local -r failed_name="$1"; shift - local -r view_pages_name="$1"; shift - local -r last_views_name="$1"; shift - - queue_album_view_render_job \ - "$photos_dir" "$blurs_dir" "$html_dir" "$backhref" "$tarball_name" \ - "$page_num" "$preview_num" "$photo" \ - "$pids_name" "$statuses_name" "$labels_name" "$failed_name" - record_rendered_view_page "$view_pages_name" "$last_views_name" \ - "$page_num" "$preview_num" - # Read later by the stats filter mini-albums (render_filter_pages) for - # their Details links; shellcheck cannot see that cross-function use. - # shellcheck disable=SC2034 - ALBUM_VIEW_PAGE_BY_PHOTO["$photo"]="$page_num-$preview_num" -} - -render_album_pages() { - local -r photos_dir="$1"; shift - local -r html_dir="$1"; shift - local -r thumbs_dir="$1"; shift - local -r blurs_dir="$1"; shift - local -r backhref="$1"; shift - local -r tarball_name="$1"; shift - - local header_bar - local name - local next_name - local page_num - local photo - local prev_name='' - local record - local -i preview_num - # Passed by name to the render-pool helpers below. - # shellcheck disable=SC2034 - local -i render_failed=0 - # shellcheck disable=SC2034 - local -a render_job_pids=() - # shellcheck disable=SC2034 - local -A render_job_labels=() - # shellcheck disable=SC2034 - local -A render_job_statuses=() - # Passed by name to record_rendered_view_page and render_view_redirects. - # shellcheck disable=SC2034 - local -A rendered_last_views=() - # shellcheck disable=SC2034 - local -a rendered_view_pages=() - local -a page_photos=() - local -a page_records=() - - # Rebuild the photo -> view-page map for this album from scratch so a - # re-generate (or a smaller incoming set) does not keep stale entries. The - # per-photo entries are written in _album_record_view_photo, which shellcheck - # cannot see from here. - # shellcheck disable=SC2034 - ALBUM_VIEW_PAGE_BY_PHOTO=() - - # Materialise the full deterministic page layout first so each preview page - # knows up front whether it has a following page (and thus a "next" link). - mapfile -t page_records < <(album_page_records "$photos_dir") - - for record in "${page_records[@]}"; do - page_num=${record%%$'\t'*} - name=$(album_page_name "$page_num") - # The first page shows the header bar; later pages do not (matches the - # previous start_preview_page calls). - if [ -z "$prev_name" ]; then - header_bar='yes' - else - header_bar='no' - fi - - # Split the tab-separated photo list for this page into an array. - IFS=$'\t' read -r -a page_photos <<< "${record#*$'\t'}" - - preview_num=0 - for photo in "${page_photos[@]}"; do - (( ++preview_num )) - _album_record_view_photo \ - "$photos_dir" "$blurs_dir" "$html_dir" "$backhref" \ - "$tarball_name" "$page_num" "$preview_num" "$photo" \ - render_job_pids render_job_statuses render_job_labels \ - render_failed rendered_view_pages rendered_last_views - done - - # A page has a "next" link unless it is the last record. - next_name='' - if (( page_num < ${#page_records[@]} )); then - next_name=$(album_page_name "$(( page_num + 1 ))") - fi - queue_preview_page_render_job \ - "$photos_dir" "$html_dir" "$thumbs_dir" "$blurs_dir" "$backhref" \ - "$tarball_name" "$name" "$page_num" "$header_bar" \ - "$prev_name" "$next_name" \ - render_job_pids render_job_statuses render_job_labels \ - render_failed \ - "${page_photos[@]}" - - prev_name="$name" - done - - if ! wait_for_album_view_render_jobs \ - render_job_pids \ - render_job_statuses \ - render_job_labels \ - render_failed; then - return 1 - fi - render_view_redirects "$html_dir" rendered_view_pages rendered_last_views - render_album_index "$photos_dir" "$html_dir" "$blurs_dir" "$backhref" -} - -splash_photo_files() { - local -r photos_dir="$1"; shift - local -r blurs_dir="$1"; shift - local photo - - while IFS= read -r photo; do - if [ -f "$DIST_DIR/$blurs_dir/$photo" ]; then - printf '%s\n' "$photo" - fi - done < <( - find "$DIST_DIR/$photos_dir" -maxdepth 1 -type f -printf '%f\n' \ - | sort - ) -} - -random_splash_photo() { - local -r photos_dir="$1"; shift - local -r blurs_dir="$1"; shift - local -i index - local photo - local -a photos=() - - while IFS= read -r photo; do - photos+=("$photo") - done < <(splash_photo_files "$photos_dir" "$blurs_dir") - - if (( ${#photos[@]} == 0 )); then - printf 'ERROR: No splash photos found in %s with matching blurs in %s\n' \ - "$(_display_path "$DIST_DIR/$photos_dir")" \ - "$(_display_path "$DIST_DIR/$blurs_dir")" >&2 - return 1 - fi - - index=$(random_index "photo:$photos_dir:splash" "${#photos[@]}") - printf '%s\n' "${photos[index]}" -} - -randomphoto() { - local -r photos_dir="$1"; shift - local -r context="${1:-$photos_dir}" - local -i index - local photo - local -a photos=() - - while IFS= read -r photo; do - photos+=("$photo") - done < <( - find "$DIST_DIR/$photos_dir" -maxdepth 1 -type f -printf '%f\n' \ - | sort - ) - - if (( ${#photos[@]} == 0 )); then - printf 'ERROR: No photos found in %s\n' \ - "$(_display_path "$DIST_DIR/$photos_dir")" >&2 - return 1 - fi - - index=$(random_index "photo:$photos_dir:$context" "${#photos[@]}") - printf '%s\n' "${photos[index]}" -} - -count_files() { - local -r dir="$1"; shift - local -r name="${1:-}"; shift || true - - if [ |
