summaryrefslogtreecommitdiff
path: root/src/lib/album.source.sh
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-06-16 18:19:51 +0300
committerPaul Buetow <paul@buetow.org>2026-06-16 18:19:51 +0300
commitfe59a701586972793ea110b465f65d47b825fb8e (patch)
tree87ca3f64b695a0a8fa63d578be1aa4937bb59e01 /src/lib/album.source.sh
parentcd7cf797bc633700c5ee7e986077f366c000f0b9 (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.sh1324
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 [