diff options
| author | Paul Buetow <paul@buetow.org> | 2026-06-11 09:55:39 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-06-11 09:55:39 +0300 |
| commit | cbf67b99530aaddf52cb85c8edc80b3f13279ce3 (patch) | |
| tree | a0158896af17ec905bbae87723a80744c67f4054 /src/lib/image.source.sh | |
| parent | 94f73164b460da559fcae98169eabfb9ab274bdd (diff) | |
Split photoalbum source into lib modules
Diffstat (limited to 'src/lib/image.source.sh')
| -rw-r--r-- | src/lib/image.source.sh | 291 |
1 files changed, 291 insertions, 0 deletions
diff --git a/src/lib/image.source.sh b/src/lib/image.source.sh new file mode 100644 index 0000000..ccdca34 --- /dev/null +++ b/src/lib/image.source.sh @@ -0,0 +1,291 @@ +cleanphotos() { + local basename + local photo + local sub + + while IFS= read -r photo; do + basename=$(basename "$photo") + + if [[ -f "$INCOMING_DIR/$basename" ]] \ + && is_supported_image_file "$basename"; then + continue + fi + + log_info "Cleaning up $(_display_path "$photo")" + for sub in thumbs blurs photos; do + if [ -f "$DIST_DIR/$sub/$basename" ]; then + rm -f "$DIST_DIR/$sub/$basename" + log_info "removed '$(_display_path "$DIST_DIR/$sub/$basename")'" + fi + done + done < <(find "$DIST_DIR/photos" -maxdepth 1 -type f) +} + +is_supported_image_file() { + local -r file="$1"; shift + local extension + + if [[ "$file" != *.* ]]; then + return 1 + fi + + extension="${file##*.}" + extension="${extension,,}" + + case "$extension" in + gif|jpeg|jpg|png|webp) + return 0 + ;; + *) + return 1 + ;; + esac +} + +incoming_image_files() { + local file + + while IFS= read -r file; do + if is_supported_image_file "$file"; then + printf '%s\n' "$file" + fi + done < <(find "$INCOMING_DIR" -maxdepth 1 -type f -printf '%f\n') \ + | sort +} + +warn_unsupported_incoming_files() { + local file + + while IFS= read -r file; do + if ! is_supported_image_file "$file"; then + log_warning "Ignoring unsupported incoming file: $file" + fi + done < <(find "$INCOMING_DIR" -maxdepth 1 -type f -printf '%f\n' | sort) +} + +scalephotos() { + 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 + scale_photo "$photo" & + image_job_pids+=("$!") + done < <(incoming_image_files) + + wait_for_image_jobs image_job_pids failed + if (( failed != 0 )); then + return 1 + fi +} + +wait_for_image_job_pid() { + local -r pid="$1"; shift + local -i status=0 + + set +e + wait "$pid" + status=$? + set -e + + return "$status" +} + +image_job_pid_is_running() { + local -r pid="$1"; shift + local running_pid + + for running_pid in "$@"; do + if [ "$running_pid" = "$pid" ]; then + return 0 + fi + done + + return 1 +} + +reap_finished_image_jobs() { + local -r image_job_pids_name="$1"; shift + # shellcheck disable=SC2178 + local -n image_job_pids_ref="$image_job_pids_name" + local -n failed_ref="$1"; shift + local -i reaped_status="$1"; shift + local -i has_reaped_status=1 + local -i status=0 + local -a remaining_pids=() + local -a running_pids=() + local pid + + mapfile -t running_pids < <(jobs -rp) + + for pid in "${image_job_pids_ref[@]}"; do + if image_job_pid_is_running "$pid" "${running_pids[@]}"; then + remaining_pids+=("$pid") + continue + fi + + if wait_for_image_job_pid "$pid"; then + status=0 + else + status=$? + fi + if (( status == 127 && has_reaped_status != 0 )); then + status=$reaped_status + has_reaped_status=0 + fi + + if (( status != 0 )); then + failed_ref=1 + fi + done + + image_job_pids_ref=("${remaining_pids[@]}") +} + +wait_for_next_image_job() { + local -r image_job_pids_name="$1"; shift + local -r failed_name="$1"; shift + # shellcheck disable=SC2178 + local -n image_job_pids_ref="$image_job_pids_name" + local -i status=0 + + set +e + wait -n "${image_job_pids_ref[@]}" + status=$? + set -e + + reap_finished_image_jobs "$image_job_pids_name" "$failed_name" "$status" +} + +wait_for_image_job_slot() { + local -r image_job_pids_name="$1"; shift + local -r failed_name="$1"; shift + # shellcheck disable=SC2178 + local -n image_job_pids_ref="$image_job_pids_name" + local -r max_jobs="${IMAGE_JOBS:-3}" + + while (( ${#image_job_pids_ref[@]} >= max_jobs )); do + wait_for_next_image_job "$image_job_pids_name" "$failed_name" + done +} + +wait_for_image_jobs() { + # shellcheck disable=SC2178 + local -n image_job_pids_ref="$1"; shift + local -n failed_ref="$1"; shift + + : "$failed_ref" + while (( ${#image_job_pids_ref[@]} > 0 )); do + if ! wait_for_image_job_pid "${image_job_pids_ref[0]}"; then + failed_ref=1 + fi + image_job_pids_ref=("${image_job_pids_ref[@]:1}") + done +} + +scale_photo() { + local -r photo="$1"; shift + local destphoto + local dirname + + destphoto="$DIST_DIR/photos/$photo" + dirname=$(dirname "$destphoto") + mkdir -p "$dirname" + + if [ -f "$destphoto" ]; then + log_verbose "Skipped existing photo $(_display_path "$destphoto")" + return + fi + + log_info "Processing $photo to $(_display_path "$destphoto")" + if [ -n "${HEIGHT:-}" ]; then + # Scale down size. + imagemagick \ + "$INCOMING_DIR/$photo" \ + -auto-orient \ + -geometry "$HEIGHT" \ + "$destphoto" + else + # Keep original size. + imagemagick \ + "$INCOMING_DIR/$photo" \ + -auto-orient \ + "$destphoto" + fi +} + +random_seed_is_set() { + [ -n "${RANDOM_SEED:-}" ] +} + +deterministic_index() { + local -r namespace="$1"; shift + local -r count="$1"; shift + local checksum + + checksum=$(printf '%s' "${RANDOM_SEED}:$namespace" | cksum) + checksum=${checksum%% *} + + printf '%s\n' $(( checksum % count )) +} + +random_index() { + local -r namespace="$1"; shift + local -r count="$1"; shift + + if random_seed_is_set; then + deterministic_index "$namespace" "$count" + else + printf '%s\n' $(( RANDOM % count )) + fi +} + +random_animation_css_class() { + local -r speed="$1"; shift + local -r context="${1:-$speed}" + local -i index + local -a classes=( + "animate-opacity-$speed" + "animate-top-$speed" + "animate-left-$speed" + "animate-right-$speed" + "animate-bottom-$speed" + "animate-zoom-$speed" + "animate-snap-rotate-$speed" + "animate-hard-zoom-$speed" + "animate-slam-left-$speed" + "animate-slam-right-$speed" + "animate-flash-in-$speed" + "animate-invert-pop-$speed" + "animate-posterize-pop-$speed" + "animate-skew-snap-$speed" + "animate-glitch-step-$speed" + ) + + index=$(random_index "animation:$speed:$context" "${#classes[@]}") + printf '%s\n' "${classes[index]}" +} + +deterministic_shuffle() { + local checksum + local line + + while IFS= read -r line; do + checksum=$(printf '%s' "${RANDOM_SEED}:shuffle:$line" | cksum) + checksum=${checksum%% *} + printf '%010u\t%s\n' "$checksum" "$line" + done | sort -n -k1,1 -k2,2 | cut -f2- +} + +maybe_shuffle() { + if [ "${SHUFFLE:-no}" = yes ]; then + if random_seed_is_set; then + deterministic_shuffle + else + sort -R + fi + else + sort + fi +} |
