summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-06-11 09:55:39 +0300
committerPaul Buetow <paul@buetow.org>2026-06-11 09:55:39 +0300
commitcbf67b99530aaddf52cb85c8edc80b3f13279ce3 (patch)
treea0158896af17ec905bbae87723a80744c67f4054
parent94f73164b460da559fcae98169eabfb9ab274bdd (diff)
Split photoalbum source into lib modules
-rw-r--r--Justfile87
-rwxr-xr-xbin/photoalbum16
-rw-r--r--src/lib/action.source.sh117
-rw-r--r--src/lib/album.source.sh900
-rw-r--r--src/lib/bootstrap.source.sh173
-rw-r--r--src/lib/config.source.sh679
-rw-r--r--src/lib/image.source.sh291
-rw-r--r--src/lib/system.source.sh172
-rw-r--r--src/lib/template.source.sh502
-rwxr-xr-xsrc/photoalbum.sh2859
10 files changed, 2951 insertions, 2845 deletions
diff --git a/Justfile b/Justfile
index 1f8f690..e89c783 100644
--- a/Justfile
+++ b/Justfile
@@ -7,6 +7,7 @@ PREFIX := env_var_or_default("PREFIX", "/usr")
BINDIR := env_var_or_default("BINDIR", PREFIX + "/bin")
DATADIR := env_var_or_default("DATADIR", PREFIX + "/share")
SYSCONFDIR := env_var_or_default("SYSCONFDIR", "/etc/default")
+LIB_SOURCES := "src/lib/bootstrap.source.sh src/lib/system.source.sh src/lib/template.source.sh src/lib/image.source.sh src/lib/album.source.sh src/lib/config.source.sh src/lib/action.source.sh"
default: build
@@ -20,8 +21,46 @@ build:
mkdir -p ./bin
generated=$(mktemp "./bin/.{{NAME}}.XXXXXX")
trap 'rm -f "$generated"' EXIT
- sed "s/PHOTOALBUMVERSION/{{VERSION}}/" \
- "src/{{NAME}}.sh" > "$generated"
+
+ render_lib_sources() {
+ local lib_source
+ local line
+
+ for lib_source in {{LIB_SOURCES}}; do
+ printf '\n# Inlined from %s\n' "$lib_source"
+ while IFS= read -r line; do
+ line=${line//PHOTOALBUMVERSION/{{VERSION}}}
+ printf '%s\n' "$line"
+ done < "$lib_source"
+ done
+ }
+
+ render_photoalbum() {
+ local in_lib_sources=no
+ local line
+
+ while IFS= read -r line; do
+ case "$line" in
+ '# PHOTOALBUM_LIB_SOURCES_BEGIN')
+ printf '%s\n' "$line"
+ render_lib_sources
+ in_lib_sources=yes
+ ;;
+ '# PHOTOALBUM_LIB_SOURCES_END')
+ in_lib_sources=no
+ printf '\n%s\n' "$line"
+ ;;
+ *)
+ if [ "$in_lib_sources" = no ]; then
+ line=${line//PHOTOALBUMVERSION/{{VERSION}}}
+ printf '%s\n' "$line"
+ fi
+ ;;
+ esac
+ done < "src/{{NAME}}.sh"
+ }
+
+ render_photoalbum > "$generated"
chmod 0755 "$generated"
if [ -f "./bin/{{NAME}}" ] && cmp -s "$generated" "./bin/{{NAME}}"; then
chmod 0755 "./bin/{{NAME}}"
@@ -34,8 +73,46 @@ build:
check-generated:
generated=$(mktemp)
trap 'rm -f "$generated"' EXIT
- sed "s/PHOTOALBUMVERSION/{{VERSION}}/" \
- "src/{{NAME}}.sh" > "$generated"
+
+ render_lib_sources() {
+ local lib_source
+ local line
+
+ for lib_source in {{LIB_SOURCES}}; do
+ printf '\n# Inlined from %s\n' "$lib_source"
+ while IFS= read -r line; do
+ line=${line//PHOTOALBUMVERSION/{{VERSION}}}
+ printf '%s\n' "$line"
+ done < "$lib_source"
+ done
+ }
+
+ render_photoalbum() {
+ local in_lib_sources=no
+ local line
+
+ while IFS= read -r line; do
+ case "$line" in
+ '# PHOTOALBUM_LIB_SOURCES_BEGIN')
+ printf '%s\n' "$line"
+ render_lib_sources
+ in_lib_sources=yes
+ ;;
+ '# PHOTOALBUM_LIB_SOURCES_END')
+ in_lib_sources=no
+ printf '\n%s\n' "$line"
+ ;;
+ *)
+ if [ "$in_lib_sources" = no ]; then
+ line=${line//PHOTOALBUMVERSION/{{VERSION}}}
+ printf '%s\n' "$line"
+ fi
+ ;;
+ esac
+ done < "src/{{NAME}}.sh"
+ }
+
+ render_photoalbum > "$generated"
chmod 0755 "$generated"
if [ ! -f "./bin/{{NAME}}" ]; then
printf '%s\n' \
@@ -81,6 +158,8 @@ clean:
shellcheck:
shellcheck \
+ --external-sources \
+ --check-sourced \
./src/photoalbum.sh \
./tests/cli.sh \
./tests/helpers.sh
diff --git a/bin/photoalbum b/bin/photoalbum
index aae3364..6b686a1 100755
--- a/bin/photoalbum
+++ b/bin/photoalbum
@@ -104,6 +104,12 @@ declare -Ar CLI_OPTION_ARGUMENT=(
[--sync-destination]=destination
)
+PHOTOALBUM_SOURCE_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
+declare -r PHOTOALBUM_SOURCE_DIR
+
+# PHOTOALBUM_LIB_SOURCES_BEGIN
+
+# Inlined from src/lib/bootstrap.source.sh
usage() {
cat - <<USAGE >&2
Usage:
@@ -188,7 +194,7 @@ resolve_source_root() {
local script_dir
local source_root
- script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
+ script_dir="$PHOTOALBUM_SOURCE_DIR"
source_root=$(cd "$script_dir/.." && pwd)
if [[ -f "$source_root/src/photoalbum.default.conf" \
@@ -278,6 +284,7 @@ init_config() {
log_info "Created ./$rc_file"
}
+# Inlined from src/lib/system.source.sh
# shellcheck disable=SC2034
resolve_imagemagick_command() {
local -r operation="$1"; shift
@@ -451,6 +458,7 @@ resolve_sync_destinations() {
esac
}
+# Inlined from src/lib/template.source.sh
_html_escape() {
local text="$1"; shift
@@ -954,6 +962,7 @@ template() {
render_template "$template_name" "$html" render_context
}
+# Inlined from src/lib/image.source.sh
cleanphotos() {
local basename
local photo
@@ -1246,6 +1255,7 @@ maybe_shuffle() {
fi
}
+# Inlined from src/lib/album.source.sh
album_photo_files() {
local -r photos_dir="$1"; shift
@@ -2147,6 +2157,7 @@ dry_run() {
fi
}
+# Inlined from src/lib/config.source.sh
print_shell_assignment() {
local -r name="$1"; shift
local -r value="$1"; shift
@@ -2827,6 +2838,7 @@ parse_cli_arguments() {
done
}
+# Inlined from src/lib/action.source.sh
run_simple_action() {
case "$action" in
--version)
@@ -2945,6 +2957,8 @@ run_action() {
esac
}
+# PHOTOALBUM_LIB_SOURCES_END
+
main() {
local action=''
local config_file=''
diff --git a/src/lib/action.source.sh b/src/lib/action.source.sh
new file mode 100644
index 0000000..01dc245
--- /dev/null
+++ b/src/lib/action.source.sh
@@ -0,0 +1,117 @@
+run_simple_action() {
+ case "$action" in
+ --version)
+ if [[ -n "$config_file" || "$has_config_overrides" = 'yes' \
+ || "$PHOTOALBUM_FORCE_GENERATE" = yes ]]; then
+ usage
+ exit 1
+ fi
+
+ printf 'This is Photoalbum Version %s\n' "$VERSION"
+ ;;
+ --init)
+ if [[ -n "$config_file" || "$has_config_overrides" = 'yes' \
+ || "$PHOTOALBUM_FORCE_GENERATE" = yes ]]; then
+ usage
+ exit 1
+ fi
+
+ init_config
+ ;;
+ esac
+}
+
+load_configured_action() {
+ local -r rc_file="$1"; shift
+
+ if [ ! -f "$rc_file" ]; then
+ missing_config "$rc_file"
+ fi
+
+ # shellcheck source=/dev/null
+ source "$rc_file"
+ apply_config_defaults
+ apply_template_dir_default
+ apply_cli_overrides
+ PHOTOALBUM_CONFIG_SOURCE="$rc_file"
+ export PHOTOALBUM_CONFIG_SOURCE
+}
+
+log_configured_action() {
+ local -r rc_file="$1"; shift
+
+ if [ "$action" = --print-config ]; then
+ return
+ fi
+
+ log_verbose "Selected config file: $rc_file"
+ log_verbose "Effective incoming directory: ${INCOMING_DIR:-}"
+ log_verbose "Effective output directory: ${DIST_DIR:-}"
+ log_verbose "Effective template directory: ${TEMPLATE_DIR:-}"
+ log_verbose "Effective image jobs: ${IMAGE_JOBS:-3}"
+ log_verbose "Effective ImageMagick timeout: ${IMAGEMAGICK_TIMEOUT:-60}s"
+ log_verbose "Effective tar timeout: ${TAR_TIMEOUT:-120}s"
+ log_verbose "Effective splash page setting: ${SPLASH_PAGE:-yes}"
+ log_verbose "Effective tarball setting: ${TARBALL_INCLUDE:-no}"
+ log_verbose "Effective sync delete setting: ${SYNC_DELETE:-yes}"
+ log_verbose "Effective force generation setting: $PHOTOALBUM_FORCE_GENERATE"
+}
+
+run_configured_action() {
+ local rc_file
+
+ if [[ "$PHOTOALBUM_FORCE_GENERATE" = yes && "$action" != --generate ]]; then
+ usage
+ exit 1
+ fi
+
+ rc_file="$(resolve_config_file "$config_file")"
+ load_configured_action "$rc_file"
+ log_configured_action "$rc_file"
+
+ case "$action" in
+ --clean)
+ if [ -d "$DIST_DIR" ]; then
+ log_info "Cleaning $DIST_DIR"
+ rm -rf "$DIST_DIR"
+ else
+ log_verbose "Output directory does not exist: $DIST_DIR"
+ fi
+ ;;
+ --generate)
+ validate_generation_config
+ generate_staged
+ ;;
+ --refresh-splash)
+ validate_refresh_splash_config
+ refresh_splash
+ ;;
+ --sync)
+ validate_sync_config
+ sync_dist
+ ;;
+ --dry-run)
+ validate_generation_config no
+ dry_run
+ ;;
+ --print-config)
+ validate_print_config
+ print_config
+ ;;
+ esac
+}
+
+run_action() {
+ case "$action" in
+ --version|--init)
+ run_simple_action
+ ;;
+ --clean|--generate|--refresh-splash|--sync|--dry-run|--print-config)
+ run_configured_action
+ ;;
+ *)
+ usage
+ exit 1
+ ;;
+ esac
+}
diff --git a/src/lib/album.source.sh b/src/lib/album.source.sh
new file mode 100644
index 0000000..886bd81
--- /dev/null
+++ b/src/lib/album.source.sh
@@ -0,0 +1,900 @@
+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"
+}
+
+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
+
+ 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")
+ 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"
+ 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
+
+ cache_dir="$DIST_DIR/.photoalbum-cache/exif"
+ cache_file="$cache_dir/$photo.txt"
+ current_signature=$(photo_cache_signature "$photo" "$photo_path")
+
+ 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'
+}
+
+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
+
+ 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"
+ )
+ 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"
+ 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=()
+ local photo
+
+ while IFS= read -r photo; do
+ wait_for_image_job_slot image_job_pids failed
+ create_photo_derivatives "$photos_dir" "$thumbs_dir" "$blurs_dir" \
+ "$photo" &
+ image_job_pids+=("$!")
+ done < <(
+ find "$DIST_DIR/$photos_dir" -maxdepth 1 -type f -printf '%f\n' \
+ | sort
+ )
+
+ wait_for_image_jobs image_job_pids failed
+ if (( failed != 0 )); then
+ return 1
+ fi
+}
+
+render_album_page_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="$1"; shift
+
+ render_preview_thumbnail \
+ "$page_name" \
+ "$html_dir" \
+ "$thumbs_dir" \
+ "$backhref" \
+ "$page_num" \
+ "$preview_num" \
+ "$photo"
+}
+
+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_view_redirects() {
+ local -r html_dir="$1"; shift
+ # shellcheck disable=SC2178
+ local -n view_pages_ref="$1"; shift
+ # shellcheck disable=SC2178
+ local -n last_views_ref="$1"; shift
+ local lastview
+ local max_page
+ local nextredirect
+ local page
+ local prevredirect
+
+ if (( ${#view_pages_ref[@]} == 0 )); then
+ return
+ fi
+
+ max_page=${view_pages_ref[$(( ${#view_pages_ref[@]} - 1 ))]}
+
+ for page in "${view_pages_ref[@]}"; do
+ lastview=${last_views_ref[$page]}
+ prevredirect="${page}-0"
+ nextredirect="${page}-$(( lastview + 1 ))"
+
+ template redirect "$prevredirect.html" \
+ html_dir "$html_dir" \
+ redirect_page "$(( page - 1 ))-${MAXPREVIEWS}"
+
+ if (( page == max_page )); then
+ template redirect "0-$MAXPREVIEWS.html" \
+ html_dir "$html_dir" \
+ redirect_page "${page}-$lastview"
+ template redirect "$nextredirect.html" \
+ html_dir "$html_dir" \
+ redirect_page '1-1'
+ else
+ template redirect "$nextredirect.html" \
+ html_dir "$html_dir" \
+ redirect_page "$(( page + 1 ))-1"
+ fi
+ done
+}
+
+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}" = 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"
+}
+
+advance_album_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 tarball_name="$1"; shift
+ # shellcheck disable=SC2178
+ local -n page_name_ref="$1"; shift
+ # shellcheck disable=SC2178
+ local -n prev_page_ref="$1"; shift
+ # shellcheck disable=SC2178
+ local -n page_num_ref="$1"; shift
+ # shellcheck disable=SC2178
+ local -n preview_num_ref="$1"; shift
+ local next_page_name
+
+ next_page_name=$(album_page_name "$(( page_num_ref + 1 ))")
+ finish_preview_page_with_next \
+ "$page_name_ref" \
+ "$html_dir" \
+ "$backhref" \
+ "$tarball_name" \
+ "$next_page_name" \
+ "${prev_page_ref:-}"
+
+ prev_page_ref="$page_name_ref"
+ (( ++page_num_ref ))
+ # shellcheck disable=SC2034
+ preview_num_ref=1
+ page_name_ref=$(album_page_name "$page_num_ref")
+
+ start_preview_page \
+ "$photos_dir" \
+ "$html_dir" \
+ "$blurs_dir" \
+ "$backhref" \
+ "$page_name_ref" \
+ 'no'
+ render_previous_page_link "$page_name_ref" "$html_dir" "$prev_page_ref"
+}
+
+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
+ # shellcheck disable=SC2178
+ local -n render_failed_ref="$1"; shift
+
+ wait_for_image_job_slot render_job_pids_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+=("$!")
+}
+
+wait_for_album_view_render_jobs() {
+ # shellcheck disable=SC2178
+ local -n render_job_pids_ref="$1"; shift
+ # shellcheck disable=SC2178
+ local -n render_failed_ref="$1"; shift
+
+ wait_for_image_jobs render_job_pids_ref render_failed_ref
+ if (( render_failed_ref != 0 )); then
+ return 1
+ fi
+}
+
+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 name
+ local photo
+ # Passed by name to advance_album_preview_page.
+ # shellcheck disable=SC2034
+ local prev
+ local -i i=0
+ local -i num=1
+ # Passed by name to queue_album_view_render_job.
+ # shellcheck disable=SC2034
+ local -i render_failed=0
+ # Passed by name to queue_album_view_render_job.
+ # shellcheck disable=SC2034
+ local -a render_job_pids=()
+ # Passed by name to record_rendered_view_page and render_view_redirects.
+ # shellcheck disable=SC2034
+ local -A rendered_last_views=()
+ # Passed by name to record_rendered_view_page and render_view_redirects.
+ # shellcheck disable=SC2034
+ local -a rendered_view_pages=()
+
+ name=$(album_page_name "$num")
+
+ start_preview_page \
+ "$photos_dir" "$html_dir" "$blurs_dir" "$backhref" "$name" 'yes'
+
+ while IFS= read -r photo; do
+ (( ++i ))
+
+ if (( i > MAXPREVIEWS )); then
+ advance_album_preview_page \
+ "$photos_dir" \
+ "$html_dir" \
+ "$blurs_dir" \
+ "$backhref" \
+ "$tarball_name" \
+ name \
+ prev \
+ num \
+ i
+ fi
+
+ render_album_page_thumbnail \
+ "$name" \
+ "$html_dir" \
+ "$thumbs_dir" \
+ "$backhref" \
+ "$num" \
+ "$i" \
+ "$photo"
+ queue_album_view_render_job \
+ "$photos_dir" \
+ "$blurs_dir" \
+ "$html_dir" \
+ "$backhref" \
+ "$tarball_name" \
+ "$num" \
+ "$i" \
+ "$photo" \
+ render_job_pids \
+ render_failed
+ record_rendered_view_page rendered_view_pages rendered_last_views \
+ "$num" "$i"
+ done < <(album_photo_files "$photos_dir")
+
+ finish_preview_page "$name" "$html_dir" "$backhref" "$tarball_name"
+ if ! wait_for_album_view_render_jobs render_job_pids 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 [ ! -d "$dir" ]; then
+ printf '0\n'
+ return
+ fi
+
+ if [ -n "$name" ]; then
+ find "$dir" -maxdepth 1 -type f -name "$name" | wc -l
+ else
+ find "$dir" -maxdepth 1 -type f | wc -l
+ fi
+}
+
+count_incoming_images() {
+ incoming_image_files | wc -l
+}
+
+tarball_name_plan() {
+ local base
+
+ base=$(basename "$INCOMING_DIR")
+ printf '%s-<timestamp>%s\n' "$base" "${TARBALL_SUFFIX:-.tar}"
+}
+
+generated_tarball_name() {
+ local base
+ local timestamp
+
+ base=$(basename "$INCOMING_DIR")
+ timestamp=$(current_timestamp_slug)
+ printf '%s-%s%s\n' "$base" "$timestamp" "${TARBALL_SUFFIX:-.tar}"
+}
+
+count_tree_files() {
+ local -r dir="$1"; shift
+ local -r name="$1"; shift
+
+ if [ ! -d "$dir" ]; then
+ printf '0\n'
+ return
+ fi
+
+ find "$dir" -type f -name "$name" | wc -l
+}
+
+write_generation_metadata() {
+ local -r tarball_file="$1"; shift
+ local -r generated_at=$(current_timestamp_iso)
+ local -r config_source="${PHOTOALBUM_CONFIG_SOURCE:-}"
+ local -r template_name=$(basename "$TEMPLATE_DIR")
+ local -r source_image_count=$(count_incoming_images)
+ local -r generated_photo_count=$(count_files "$DIST_DIR/photos")
+ local -r generated_thumb_count=$(count_files "$DIST_DIR/thumbs")
+ local -r generated_html_count=$(count_tree_files "$DIST_DIR" '*.html')
+
+ {
+ printf '{\n'
+ printf ' "generator": {\n'
+ printf ' "name": "photoalbum",\n'
+ printf ' "version": %s\n' "$(_json_string "$VERSION")"
+ printf ' },\n'
+ printf ' "generated_at": %s,\n' "$(_json_string "$generated_at")"
+ printf ' "config_source": %s,\n' "$(_json_string "$config_source")"
+ printf ' "template": {\n'
+ printf ' "name": %s,\n' "$(_json_string "$template_name")"
+ printf ' "directory": %s\n' "$(_json_string "$TEMPLATE_DIR")"
+ printf ' },\n'
+ printf ' "source": {\n'