magicciv/tools/audio-fetch-options.sh

125 lines
3.8 KiB
Bash
Executable file

#!/usr/bin/env bash
# Pre-fetch every alternative declared in audio-options.json so the
# design app at /audio can preview them via its dropdown selector.
#
# Files land at .local/audio-alternatives/<safe_path>/<idx>.ogg —
# outside the shipped assets tree, so audio-validate.py's orphan scan
# is not affected. The design app exposes them via Vite's `@audio-alts`
# alias.
#
# Idempotent: skips alternatives whose .ogg already exists.
set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(dirname "$SCRIPT_DIR")"
THEME="${AUDIO_THEME:-age-of-dwarves}"
OPTIONS_JSON="$REPO_ROOT/public/games/$THEME/data/audio-options.json"
ALT_ROOT="$REPO_ROOT/.local/audio-alternatives"
STAGING="$REPO_ROOT/.local/audio-staging"
if [ ! -f "$OPTIONS_JSON" ]; then
echo "audio-options.json not found at $OPTIONS_JSON" >&2
exit 1
fi
mkdir -p "$ALT_ROOT" "$STAGING"
ok=0
skip=0
fail=0
# Walk every (output_path, idx, source_url) triple via python (jq isn't
# guaranteed everywhere; python3 is). One line per triple, tab-separated.
mapfile -t ROWS < <(python3 -c "
import json
data = json.load(open('$OPTIONS_JSON'))
for output_path, opts in data.get('options', {}).items():
for i, opt in enumerate(opts):
url = opt.get('source_url', '')
if not url:
continue
print(f'{output_path}\t{i}\t{url}')
")
for row in "${ROWS[@]}"; do
IFS=$'\t' read -r output_path idx source_url <<< "$row"
[ -z "$output_path" ] && continue
safe_key="$(printf '%s' "$output_path" | tr '/' '_' | sed 's/\.ogg$//')"
out_dir="$ALT_ROOT/$safe_key"
out_file="$out_dir/$idx.ogg"
if [ -f "$out_file" ]; then
skip=$((skip + 1))
continue
fi
echo "$output_path[$idx]"
mkdir -p "$out_dir"
fetch_url="$source_url"
inner_path=""
if [[ "$source_url" == *"#"* ]]; then
fetch_url="${source_url%%#*}"
inner_path="${source_url#*#}"
fi
case "$fetch_url" in
https://github.com/*/blob/*)
fetch_url="$(echo "$fetch_url" | sed -e 's|github.com|raw.githubusercontent.com|' -e 's|/blob/|/|')"
;;
esac
if [ -n "$inner_path" ]; then
zip_hash="$(printf '%s' "$fetch_url" | shasum | cut -c1-8)"
zip_cache="$STAGING/_zip_${zip_hash}.zip"
zip_extract_dir="$STAGING/_zip_${zip_hash}"
if [ ! -f "$zip_cache" ]; then
if ! curl -sfL -o "$zip_cache" "$fetch_url"; then
echo " ✗ ZIP download failed: $fetch_url" >&2
fail=$((fail + 1)); continue
fi
fi
if [ ! -d "$zip_extract_dir" ]; then
mkdir -p "$zip_extract_dir"
if ! unzip -q -o "$zip_cache" -d "$zip_extract_dir"; then
echo " ✗ ZIP extract failed" >&2
fail=$((fail + 1)); continue
fi
fi
staged="$zip_extract_dir/$inner_path"
if [ ! -f "$staged" ]; then
echo " ✗ ZIP missing inner file: $inner_path" >&2
fail=$((fail + 1)); continue
fi
else
src_ext="${fetch_url##*.}"
case "$src_ext" in
wav|ogg|mp3|flac) ;;
*) src_ext="bin" ;;
esac
staged="$STAGING/_alt_${safe_key}_${idx}.${src_ext}"
if ! curl -sfL -o "$staged" "$fetch_url"; then
echo " ✗ download failed: $fetch_url" >&2
fail=$((fail + 1)); continue
fi
fi
if ! ffmpeg -y -hide_banner -loglevel error \
-i "$staged" \
-af "loudnorm=I=-16:TP=-3:LRA=11,aresample=44100" \
-c:a libvorbis -b:a 128k \
"$out_file"; then
echo " ✗ encode failed" >&2
fail=$((fail + 1)); continue
fi
ok=$((ok + 1))
done
echo ""
echo "── alternatives summary ────"
echo " ok: $ok"
echo " skip: $skip"
echo " fail: $fail"
[ $fail -gt 0 ] && exit 1
exit 0