summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-06-05 15:50:24 +0300
committerPaul Buetow <paul@buetow.org>2026-06-05 15:50:24 +0300
commit9ac5b0bcfa6614c0d5656b9b09bf46bf1098d698 (patch)
tree54e6002e4d151135dee3fb9527c01ebc92a6da98
parenta7954db7e70c47d137e957b1a7ca0c9e0f85bdf3 (diff)
Add fast splash refresh action
-rw-r--r--README.md22
-rwxr-xr-xbin/photoalbum114
-rwxr-xr-xsrc/photoalbum.sh114
-rwxr-xr-xtests/cli.sh86
4 files changed, 321 insertions, 15 deletions
diff --git a/README.md b/README.md
index a488b02..3409a02 100644
--- a/README.md
+++ b/README.md
@@ -40,6 +40,7 @@ modern `magick` command and falls back to `convert` when needed.
```
photoalbum --init
photoalbum --generate [--config PATH] [OPTIONS]
+photoalbum --refresh-splash [--config PATH] [OPTIONS]
photoalbum --dry-run [--config PATH] [OPTIONS]
photoalbum --print-config [--config PATH] [OPTIONS]
photoalbum --clean [--config PATH] [OPTIONS]
@@ -49,6 +50,7 @@ photoalbum --version
* `--init` creates `./photoalbum.conf` in the current working directory from the
default config. It refuses to overwrite an existing file.
* `--generate` builds the static album.
+* `--refresh-splash` rewrites only the generated root splash page.
* `--dry-run` loads the config and overrides, validates the planned generation,
and prints the effective paths, image count, tarball plan, and generated file
plan without writing output or running ImageMagick or tar.
@@ -57,12 +59,12 @@ photoalbum --version
ImageMagick, running tar, cleaning, or initializing.
* `--clean` removes the configured output directory.
* `--version` prints the program version.
-* `--config PATH` selects the config file for `--generate`, `--dry-run`,
- `--print-config`, or `--clean`.
+* `--config PATH` selects the config file for `--generate`,
+ `--refresh-splash`, `--dry-run`, `--print-config`, or `--clean`.
When `--config PATH` is not provided, `--generate`, `--dry-run`,
-`--print-config`, and `--clean` read `./photoalbum.conf`. If the file is
-missing, run `photoalbum --init` first.
+`--print-config`, `--refresh-splash`, and `--clean` read `./photoalbum.conf`.
+If the file is missing, run `photoalbum --init` first.
The config file is a Bash file with assignments such as `INCOMING_DIR`,
`DIST_DIR`, `TEMPLATE_DIR`, `TITLE`, `HEIGHT`, `THUMBHEIGHT`, `MAXPREVIEWS`,
@@ -123,15 +125,21 @@ By default, the generated root `index.html` is a no-JavaScript splash page using
a randomly selected album photo. Set `SPLASH_PAGE=no` or pass `--no-splash` to
restore the top-level redirect to `page-1.html`.
+To quickly pick a new random splash photo for an already generated album, run
+`photoalbum --refresh-splash`. This rewrites only `DIST_DIR/index.html` using
+the existing `photos` and `blurs` output, so it avoids reprocessing images and
+rerendering album pages. It requires `SPLASH_PAGE=yes`; pass
+`--random-seed VALUE` when you need a repeatable pick.
+
By default, splash and background photos, animation classes, generated
timestamps, and `--shuffle` preview order remain non-deterministic. Set
`RANDOM_SEED` in the config, or pass `--random-seed VALUE`, to make those
choices repeatable for stable tests or reproducible album builds. Use the same
seed and inputs to produce the same HTML.
-`--dry-run` and `--print-config` accept the same override options as
-`--generate`. `--clean` accepts the same override options, but only `--dist`
-changes what it removes.
+`--dry-run`, `--print-config`, and `--refresh-splash` accept the same override
+options as `--generate`. `--clean` accepts the same override options, but only
+`--dist` changes what it removes.
Output is human-readable by default and reports routine generation progress.
Use `--quiet` to suppress routine progress while still writing errors to stderr.
diff --git a/bin/photoalbum b/bin/photoalbum
index dfef411..653e1a8 100755
--- a/bin/photoalbum
+++ b/bin/photoalbum
@@ -15,6 +15,7 @@ usage() {
cat - <<USAGE >&2
Usage:
$0 --generate [--config PATH] [OPTIONS]
+ $0 --refresh-splash [--config PATH] [OPTIONS]
$0 --dry-run [--config PATH] [OPTIONS]
$0 --print-config [--config PATH] [OPTIONS]
$0 --clean [--config PATH] [OPTIONS]
@@ -1266,10 +1267,16 @@ render_album_splash_page() {
local -r html_dir="$1"; shift
local -r blurs_dir="$1"; shift
local -r backhref="$1"; shift
+ local html='index.html'
local photo
- photo=$(randomphoto "$photos_dir" splash)
- template 'splash' 'index.html' \
+ 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" \
@@ -1374,6 +1381,43 @@ render_album_pages() {
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}"
@@ -1536,6 +1580,24 @@ generate() {
write_generation_metadata "$tarball_name"
}
+refresh_splash() {
+ local tmp_html
+ local tmp_path
+
+ tmp_path=$(mktemp "$DIST_DIR/.index.html.XXXXXX")
+ tmp_html=$(basename "$tmp_path")
+ rm -f "$tmp_path"
+
+ if render_album_splash_page 'photos' '.' 'blurs' '.' "$tmp_html"; then
+ mv "$DIST_DIR/$tmp_html" "$DIST_DIR/index.html"
+ log_info "Refreshed splash page $(_display_path "$DIST_DIR/index.html")"
+ return
+ fi
+
+ rm -f "$DIST_DIR/$tmp_html"
+ return 1
+}
+
dry_run() {
local -i image_count=0
local -i html_index_count=1
@@ -1994,6 +2056,45 @@ validate_template_dir() {
done
}
+validate_refresh_splash_config() {
+ local required_var
+ local -a required_vars=(
+ TITLE
+ DIST_DIR
+ TEMPLATE_DIR
+ )
+
+ for required_var in "${required_vars[@]}"; do
+ require_config_var "$required_var"
+ done
+
+ validate_yes_no_config_var SPLASH_PAGE
+
+ if [ "${SPLASH_PAGE:-yes}" != yes ]; then
+ config_error 'SPLASH_PAGE must be yes to refresh the splash page'
+ fi
+
+ validate_dist_dir
+
+ if [[ ! -d "$TEMPLATE_DIR" || ! -r "$TEMPLATE_DIR" \
+ || ! -x "$TEMPLATE_DIR" ]]; then
+ config_error "TEMPLATE_DIR $TEMPLATE_DIR must be a readable directory"
+ fi
+
+ if [ ! -r "$TEMPLATE_DIR/splash.tmpl" ]; then
+ config_error \
+ "template file $TEMPLATE_DIR/splash.tmpl must be readable"
+ fi
+
+ if [ ! -d "$DIST_DIR/photos" ]; then
+ config_error "DIST_DIR photos directory $DIST_DIR/photos must exist"
+ fi
+
+ if [ ! -d "$DIST_DIR/blurs" ]; then
+ config_error "DIST_DIR blurs directory $DIST_DIR/blurs must exist"
+ fi
+}
+
validate_imagemagick() {
if command -v magick >/dev/null 2>&1; then
return
@@ -2174,7 +2275,8 @@ parse_cli_arguments() {
--quiet)
PHOTOALBUM_OUTPUT_MODE=quiet
;;
- --version|--init|--clean|--generate|--dry-run|--print-config)
+ --version|--init|--clean|--generate|--refresh-splash|--dry-run|\
+ --print-config)
set_cli_action "$option"
;;
*)
@@ -2258,6 +2360,10 @@ run_configured_action() {
validate_generation_config
generate_staged
;;
+ --refresh-splash)
+ validate_refresh_splash_config
+ refresh_splash
+ ;;
--dry-run)
validate_generation_config no
dry_run
@@ -2274,7 +2380,7 @@ run_action() {
--version|--init)
run_simple_action
;;
- --clean|--generate|--dry-run|--print-config)
+ --clean|--generate|--refresh-splash|--dry-run|--print-config)
run_configured_action
;;
*)
diff --git a/src/photoalbum.sh b/src/photoalbum.sh
index 82353a5..234bf8a 100755
--- a/src/photoalbum.sh
+++ b/src/photoalbum.sh
@@ -15,6 +15,7 @@ usage() {
cat - <<USAGE >&2
Usage:
$0 --generate [--config PATH] [OPTIONS]
+ $0 --refresh-splash [--config PATH] [OPTIONS]
$0 --dry-run [--config PATH] [OPTIONS]
$0 --print-config [--config PATH] [OPTIONS]
$0 --clean [--config PATH] [OPTIONS]
@@ -1266,10 +1267,16 @@ render_album_splash_page() {
local -r html_dir="$1"; shift
local -r blurs_dir="$1"; shift
local -r backhref="$1"; shift
+ local html='index.html'
local photo
- photo=$(randomphoto "$photos_dir" splash)
- template 'splash' 'index.html' \
+ 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" \
@@ -1374,6 +1381,43 @@ render_album_pages() {
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}"
@@ -1536,6 +1580,24 @@ generate() {
write_generation_metadata "$tarball_name"
}
+refresh_splash() {
+ local tmp_html
+ local tmp_path
+
+ tmp_path=$(mktemp "$DIST_DIR/.index.html.XXXXXX")
+ tmp_html=$(basename "$tmp_path")
+ rm -f "$tmp_path"
+
+ if render_album_splash_page 'photos' '.' 'blurs' '.' "$tmp_html"; then
+ mv "$DIST_DIR/$tmp_html" "$DIST_DIR/index.html"
+ log_info "Refreshed splash page $(_display_path "$DIST_DIR/index.html")"
+ return
+ fi
+
+ rm -f "$DIST_DIR/$tmp_html"
+ return 1
+}
+
dry_run() {
local -i image_count=0
local -i html_index_count=1
@@ -1994,6 +2056,45 @@ validate_template_dir() {
done
}
+validate_refresh_splash_config() {
+ local required_var
+ local -a required_vars=(
+ TITLE
+ DIST_DIR
+ TEMPLATE_DIR
+ )
+
+ for required_var in "${required_vars[@]}"; do
+ require_config_var "$required_var"
+ done
+
+ validate_yes_no_config_var SPLASH_PAGE
+
+ if [ "${SPLASH_PAGE:-yes}" != yes ]; then
+ config_error 'SPLASH_PAGE must be yes to refresh the splash page'
+ fi
+
+ validate_dist_dir
+
+ if [[ ! -d "$TEMPLATE_DIR" || ! -r "$TEMPLATE_DIR" \
+ || ! -x "$TEMPLATE_DIR" ]]; then
+ config_error "TEMPLATE_DIR $TEMPLATE_DIR must be a readable directory"
+ fi
+
+ if [ ! -r "$TEMPLATE_DIR/splash.tmpl" ]; then
+ config_error \
+ "template file $TEMPLATE_DIR/splash.tmpl must be readable"
+ fi
+
+ if [ ! -d "$DIST_DIR/photos" ]; then
+ config_error "DIST_DIR photos directory $DIST_DIR/photos must exist"
+ fi
+
+ if [ ! -d "$DIST_DIR/blurs" ]; then
+ config_error "DIST_DIR blurs directory $DIST_DIR/blurs must exist"
+ fi
+}
+
validate_imagemagick() {
if command -v magick >/dev/null 2>&1; then
return
@@ -2174,7 +2275,8 @@ parse_cli_arguments() {
--quiet)
PHOTOALBUM_OUTPUT_MODE=quiet
;;
- --version|--init|--clean|--generate|--dry-run|--print-config)
+ --version|--init|--clean|--generate|--refresh-splash|--dry-run|\
+ --print-config)
set_cli_action "$option"
;;
*)
@@ -2258,6 +2360,10 @@ run_configured_action() {
validate_generation_config
generate_staged
;;
+ --refresh-splash)
+ validate_refresh_splash_config
+ refresh_splash
+ ;;
--dry-run)
validate_generation_config no
dry_run
@@ -2274,7 +2380,7 @@ run_action() {
--version|--init)
run_simple_action
;;
- --clean|--generate|--dry-run|--print-config)
+ --clean|--generate|--refresh-splash|--dry-run|--print-config)
run_configured_action
;;
*)
diff --git a/tests/cli.sh b/tests/cli.sh
index be71e6a..2cc9d19 100755
--- a/tests/cli.sh
+++ b/tests/cli.sh
@@ -1987,6 +1987,86 @@ test_generate_cli_no_splash_overrides_config() {
test::teardown
}
+test_refresh_splash_rewrites_only_index_from_existing_assets() {
+ local after_index
+ local after_metadata
+ local after_page
+ local before_index
+ local before_metadata
+ local before_page
+ local config_file
+ local failing_bin
+ local fake_bin
+ local output
+
+ test::setup
+ fake_bin="$TEST_TMPDIR/bin"
+ failing_bin="$TEST_TMPDIR/failing-bin"
+ config_file="$TEST_TMPDIR/photoalbum.conf"
+
+ test::install_fake_imagemagick "$fake_bin"
+ PATH="$fake_bin:$PATH" \
+ test::generate_fixture_images "$TEST_TMPDIR/incoming"
+ test::write_album_config \
+ "$config_file" "$TEST_TMPDIR/incoming" "$TEST_TMPDIR/dist" \
+ 'Refresh splash album' 2
+
+ (
+ cd "$TEST_TMPDIR"
+ PATH="$fake_bin:$PATH" "$TEST_PHOTOALBUM" \
+ --generate --random-seed seed-one
+ )
+
+ before_index=$(<"$TEST_TMPDIR/dist/index.html")
+ before_page=$(<"$TEST_TMPDIR/dist/page-1.html")
+ before_metadata=$(<"$TEST_TMPDIR/dist/photoalbum.json")
+
+ test::install_failing_imagemagick "$failing_bin"
+ output=$(
+ cd "$TEST_TMPDIR"
+ PATH="$failing_bin:$PATH" "$TEST_PHOTOALBUM" \
+ --refresh-splash --random-seed seed-two
+ )
+
+ after_index=$(<"$TEST_TMPDIR/dist/index.html")
+ after_page=$(<"$TEST_TMPDIR/dist/page-1.html")
+ after_metadata=$(<"$TEST_TMPDIR/dist/photoalbum.json")
+
+ test::assert_contains 'Refreshed splash page' "$output"
+ test::assert_contains '<img class="splash-photo" src="./photos/' \
+ "$after_index"
+ if [ "$before_index" = "$after_index" ]; then
+ echo 'FAIL: expected --refresh-splash to choose a new splash image' >&2
+ exit 1
+ fi
+ test "$before_page" = "$after_page"
+ test "$before_metadata" = "$after_metadata"
+ test::teardown
+}
+
+test_refresh_splash_requires_existing_generated_assets() {
+ local config_file
+ local output
+
+ test::setup
+ config_file="$TEST_TMPDIR/photoalbum.conf"
+ mkdir -p "$TEST_TMPDIR/incoming" "$TEST_TMPDIR/dist"
+ test::write_album_config \
+ "$config_file" "$TEST_TMPDIR/incoming" "$TEST_TMPDIR/dist" \
+ 'Missing splash assets album' 40
+
+ output=$(
+ cd "$TEST_TMPDIR"
+ test::capture_failure_output "$TEST_PHOTOALBUM" --refresh-splash
+ )
+
+ test::assert_contains \
+ "ERROR: DIST_DIR photos directory $TEST_TMPDIR/dist/photos must exist" \
+ "$output"
+ test::assert_path_absent "$TEST_TMPDIR/dist/index.html"
+ test::teardown
+}
+
test_generate_replaces_dist_after_success() {
local config_file
local fake_bin
@@ -2697,6 +2777,12 @@ main() {
'--generate --no-splash keeps root index redirect' \
test_generate_cli_no_splash_overrides_config
test::run_case \
+ '--refresh-splash rewrites only root index from existing assets' \
+ test_refresh_splash_rewrites_only_index_from_existing_assets
+ test::run_case \
+ '--refresh-splash requires existing generated assets' \
+ test_refresh_splash_requires_existing_generated_assets
+ test::run_case \
'--generate replaces final dist after success' \
test_generate_replaces_dist_after_success
test::run_case \