414 lines
11 KiB
Bash
Executable File
414 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Analyze the current TTS voice / cue-asset settings from `.env`, decide which
|
|
# generated cue banks are stale, delete the generated files, and restart the
|
|
# pm2 service so the engine regenerates them at startup.
|
|
#
|
|
# The script is intentionally conservative:
|
|
# - by default it inspects every language subdirectory currently present under
|
|
# `assets/fillers`, `assets/start_filler` and `assets/backchannel`, plus
|
|
# `LANG_DEFAULT`;
|
|
# - it stores a per-directory voice signature in `.voice_signature`;
|
|
# - if the signature did not change, it leaves the bank alone;
|
|
# - `--force` overrides that check and refreshes the selected banks anyway.
|
|
#
|
|
# Usage examples:
|
|
# ./run/refresh_voice_assets.sh
|
|
# ./run/refresh_voice_assets.sh --dry-run
|
|
# ./run/refresh_voice_assets.sh --lang es --lang en
|
|
# ./run/refresh_voice_assets.sh --base-lang-only --force
|
|
#
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
ENV_FILE="$PROJECT_DIR/.env"
|
|
PM2_APP="${PM2_APP:-pm2-piperust}"
|
|
SIGNATURE_FILE=".voice_signature"
|
|
|
|
DRY_RUN=0
|
|
FORCE=0
|
|
BASE_LANG_ONLY=0
|
|
NO_RESTART=0
|
|
declare -a REQUESTED_LANGS=()
|
|
|
|
if [ ! -f "$ENV_FILE" ]; then
|
|
echo "[refresh_voice_assets] missing .env: $ENV_FILE" >&2
|
|
exit 1
|
|
fi
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
refresh_voice_assets.sh
|
|
|
|
Analyze voice-related `.env` settings, refresh stale cue asset banks, and
|
|
restart the pm2 service so the engine regenerates them.
|
|
|
|
Options:
|
|
--dry-run Print what would be refreshed, do not delete or restart
|
|
--force Refresh the selected banks even when the signature matches
|
|
--lang <code> Restrict the refresh to one language (repeatable)
|
|
--base-lang-only Restrict the refresh to LANG_DEFAULT
|
|
--no-restart Delete files but do not restart pm2
|
|
--help Show this help
|
|
|
|
Environment override:
|
|
PM2_APP=<name> pm2 app to restart (default: pm2-piperust)
|
|
EOF
|
|
}
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--dry-run) DRY_RUN=1 ;;
|
|
--force) FORCE=1 ;;
|
|
--base-lang-only) BASE_LANG_ONLY=1 ;;
|
|
--no-restart) NO_RESTART=1 ;;
|
|
--lang)
|
|
shift
|
|
[ $# -gt 0 ] || { echo "[refresh_voice_assets] --lang requires a value" >&2; exit 1; }
|
|
REQUESTED_LANGS+=("$1")
|
|
;;
|
|
--help|-h)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "[refresh_voice_assets] unknown argument: $1" >&2
|
|
usage >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
set -a
|
|
# shellcheck disable=SC1090
|
|
source "$ENV_FILE"
|
|
set +a
|
|
|
|
TTS_ENGINE="${TTS_ENGINE:-fish}"
|
|
LANG_DEFAULT="${LANG_DEFAULT:-es}"
|
|
FILLERS_DIR="${FILLERS_DIR:-assets/fillers}"
|
|
START_FILLER_DIR="${START_FILLER_DIR:-assets/start_filler}"
|
|
BACKCHANNEL_DIR="${BACKCHANNEL_DIR:-assets/backchannel}"
|
|
TTS_MODEL="${TTS_MODEL:-}"
|
|
TTS_VOICE="${TTS_VOICE:-}"
|
|
ASSET_VOICE_PROFILE="${ASSET_VOICE_PROFILE:-}"
|
|
TTS_REFERENCE_AUDIO="${TTS_REFERENCE_AUDIO:-}"
|
|
TTS_REFERENCE_TEXT="${TTS_REFERENCE_TEXT:-}"
|
|
TTS_TEMPERATURE="${TTS_TEMPERATURE:-}"
|
|
TTS_TOP_P="${TTS_TOP_P:-}"
|
|
TTS_TOP_K="${TTS_TOP_K:-}"
|
|
TTS_STYLE_TAG="${TTS_STYLE_TAG:-}"
|
|
SGLANG_URL="${SGLANG_URL:-}"
|
|
KOKORO_URL="${KOKORO_URL:-}"
|
|
KOKORO_VOICE="${KOKORO_VOICE:-}"
|
|
START_FILLER_TEXTS="${START_FILLER_TEXTS:-}"
|
|
BACKCHANNEL_TEXTS="${BACKCHANNEL_TEXTS:-}"
|
|
|
|
abs_path() {
|
|
local p="$1"
|
|
if [[ "$p" = /* ]]; then
|
|
printf '%s\n' "$p"
|
|
else
|
|
printf '%s\n' "$PROJECT_DIR/$p"
|
|
fi
|
|
}
|
|
|
|
sha_file() {
|
|
local path="$1"
|
|
if [ -f "$path" ]; then
|
|
sha256sum "$path" | awk '{print $1}'
|
|
else
|
|
printf 'missing'
|
|
fi
|
|
}
|
|
|
|
sha_text() {
|
|
printf '%s' "$1" | sha256sum | awk '{print $1}'
|
|
}
|
|
|
|
count_entries() {
|
|
printf '%s' "$1" | tr '|' '\n' | awk 'NF { c++ } END { print c + 0 }'
|
|
}
|
|
|
|
sanitize_asset_segment() {
|
|
local raw="$1"
|
|
printf '%s' "$raw" \
|
|
| tr '[:upper:]' '[:lower:]' \
|
|
| sed -E 's/[^a-z0-9._ -]+/_/g; s/[ .-]+/_/g; s/^_+//; s/_+$//'
|
|
}
|
|
|
|
cue_voice_profile() {
|
|
local explicit
|
|
explicit="$(sanitize_asset_segment "$ASSET_VOICE_PROFILE")"
|
|
if [ -n "$explicit" ]; then
|
|
printf '%s\n' "$explicit"
|
|
return 0
|
|
fi
|
|
|
|
local voice
|
|
voice="$(sanitize_asset_segment "$TTS_VOICE")"
|
|
if [ -n "$voice" ] && [ "$voice" != "default" ]; then
|
|
printf '%s\n' "$voice"
|
|
return 0
|
|
fi
|
|
|
|
if [ -n "$TTS_REFERENCE_AUDIO" ]; then
|
|
local stem
|
|
stem="$(basename "${TTS_REFERENCE_AUDIO%.*}")"
|
|
stem="$(sanitize_asset_segment "$stem")"
|
|
if [ -n "$stem" ]; then
|
|
printf '%s\n' "$stem"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
if [ -n "$voice" ]; then
|
|
printf '%s\n' "$voice"
|
|
else
|
|
printf 'default\n'
|
|
fi
|
|
}
|
|
|
|
read_reference_text_mode() {
|
|
if [ -z "$TTS_REFERENCE_TEXT" ]; then
|
|
printf 'auto'
|
|
elif [ -f "$TTS_REFERENCE_TEXT" ]; then
|
|
printf 'file'
|
|
else
|
|
printf 'inline'
|
|
fi
|
|
}
|
|
|
|
reference_text_hash() {
|
|
if [ -z "$TTS_REFERENCE_TEXT" ]; then
|
|
printf 'auto'
|
|
elif [ -f "$TTS_REFERENCE_TEXT" ]; then
|
|
sha_file "$TTS_REFERENCE_TEXT"
|
|
else
|
|
sha_text "$TTS_REFERENCE_TEXT"
|
|
fi
|
|
}
|
|
|
|
voice_signature() {
|
|
{
|
|
printf 'TTS_ENGINE=%s\n' "$TTS_ENGINE"
|
|
printf 'LANG_DEFAULT=%s\n' "$LANG_DEFAULT"
|
|
printf 'SGLANG_URL=%s\n' "$SGLANG_URL"
|
|
printf 'TTS_MODEL=%s\n' "$TTS_MODEL"
|
|
printf 'TTS_VOICE=%s\n' "$TTS_VOICE"
|
|
printf 'ASSET_VOICE_PROFILE=%s\n' "$ASSET_VOICE_PROFILE"
|
|
printf 'TTS_REFERENCE_AUDIO=%s\n' "$TTS_REFERENCE_AUDIO"
|
|
printf 'TTS_REFERENCE_AUDIO_HASH=%s\n' "$(sha_file "$TTS_REFERENCE_AUDIO")"
|
|
printf 'TTS_REFERENCE_TEXT_MODE=%s\n' "$(read_reference_text_mode)"
|
|
printf 'TTS_REFERENCE_TEXT_HASH=%s\n' "$(reference_text_hash)"
|
|
printf 'TTS_TEMPERATURE=%s\n' "$TTS_TEMPERATURE"
|
|
printf 'TTS_TOP_P=%s\n' "$TTS_TOP_P"
|
|
printf 'TTS_TOP_K=%s\n' "$TTS_TOP_K"
|
|
printf 'TTS_STYLE_TAG=%s\n' "$TTS_STYLE_TAG"
|
|
printf 'KOKORO_URL=%s\n' "$KOKORO_URL"
|
|
printf 'KOKORO_VOICE=%s\n' "$KOKORO_VOICE"
|
|
printf 'START_FILLER_TEXTS_HASH=%s\n' "$(sha_text "$START_FILLER_TEXTS")"
|
|
printf 'BACKCHANNEL_TEXTS_HASH=%s\n' "$(sha_text "$BACKCHANNEL_TEXTS")"
|
|
} | sha256sum | awk '{print $1}'
|
|
}
|
|
|
|
print_summary() {
|
|
echo "Project : $PROJECT_DIR"
|
|
echo "Env file : $ENV_FILE"
|
|
echo "pm2 app : $PM2_APP"
|
|
echo "TTS engine : $TTS_ENGINE"
|
|
echo "SGLang URL : ${SGLANG_URL:-<empty>}"
|
|
echo "TTS model : ${TTS_MODEL:-<empty>}"
|
|
echo "TTS voice : ${TTS_VOICE:-<empty>}"
|
|
echo "Cue voice profile : $CUE_VOICE_PROFILE"
|
|
echo "Reference audio : ${TTS_REFERENCE_AUDIO:-<empty>}"
|
|
echo "Reference audio hash : $(sha_file "$TTS_REFERENCE_AUDIO")"
|
|
echo "Reference text mode : $(read_reference_text_mode)"
|
|
echo "Reference text hash : $(reference_text_hash)"
|
|
echo "Temperature / top-p/k : ${TTS_TEMPERATURE:-<empty>} / ${TTS_TOP_P:-<empty>} / ${TTS_TOP_K:-<empty>}"
|
|
echo "Style tag : ${TTS_STYLE_TAG:-<empty>}"
|
|
echo "LANG_DEFAULT : $LANG_DEFAULT"
|
|
echo "FILLERS_DIR : $(abs_path "$FILLERS_DIR")"
|
|
echo "START_FILLER_DIR : $(abs_path "$START_FILLER_DIR")"
|
|
echo "BACKCHANNEL_DIR : $(abs_path "$BACKCHANNEL_DIR")"
|
|
echo "START_FILLER_TEXTS : $(count_entries "$START_FILLER_TEXTS") entries"
|
|
echo "BACKCHANNEL_TEXTS : $(count_entries "$BACKCHANNEL_TEXTS") entries"
|
|
echo "Voice signature : $CURRENT_SIGNATURE"
|
|
}
|
|
|
|
declare -A LANGS=()
|
|
add_lang() {
|
|
local lang="$1"
|
|
[ -n "$lang" ] || return 0
|
|
LANGS["$lang"]=1
|
|
}
|
|
|
|
collect_languages_from_dir() {
|
|
local base_dir="$1"
|
|
[ -d "$base_dir" ] || return 0
|
|
local d
|
|
for d in "$base_dir"/*; do
|
|
[ -d "$d" ] || continue
|
|
add_lang "$(basename "$d")"
|
|
done
|
|
}
|
|
|
|
FILLERS_ABS="$(abs_path "$FILLERS_DIR")"
|
|
START_FILLER_ABS="$(abs_path "$START_FILLER_DIR")"
|
|
BACKCHANNEL_ABS="$(abs_path "$BACKCHANNEL_DIR")"
|
|
CUE_VOICE_PROFILE="$(cue_voice_profile)"
|
|
CURRENT_SIGNATURE="$(voice_signature)"
|
|
|
|
if [ "${#REQUESTED_LANGS[@]}" -gt 0 ]; then
|
|
for lang in "${REQUESTED_LANGS[@]}"; do
|
|
add_lang "$lang"
|
|
done
|
|
elif [ "$BASE_LANG_ONLY" -eq 1 ]; then
|
|
add_lang "$LANG_DEFAULT"
|
|
else
|
|
add_lang "$LANG_DEFAULT"
|
|
collect_languages_from_dir "$FILLERS_ABS"
|
|
collect_languages_from_dir "$START_FILLER_ABS"
|
|
collect_languages_from_dir "$BACKCHANNEL_ABS"
|
|
fi
|
|
|
|
mapfile -t TARGET_LANGS < <(printf '%s\n' "${!LANGS[@]}" | sort)
|
|
|
|
bank_dir() {
|
|
local bank_root="$1"
|
|
local lang="$2"
|
|
printf '%s/%s/%s\n' "$bank_root" "$lang" "$CUE_VOICE_PROFILE"
|
|
}
|
|
|
|
count_raw() {
|
|
local dir="$1"
|
|
if [ -d "$dir" ]; then
|
|
find "$dir" -maxdepth 1 -type f -name '*.raw' | wc -l | awk '{print $1}'
|
|
else
|
|
printf '0'
|
|
fi
|
|
}
|
|
|
|
saved_signature() {
|
|
local dir="$1"
|
|
local path="$dir/$SIGNATURE_FILE"
|
|
[ -f "$path" ] && cat "$path" || true
|
|
}
|
|
|
|
refresh_reason() {
|
|
local dir="$1"
|
|
local saved="$2"
|
|
if [ "$FORCE" -eq 1 ]; then
|
|
printf 'forced'
|
|
elif [ ! -d "$dir" ]; then
|
|
printf 'missing dir'
|
|
elif [ -z "$saved" ]; then
|
|
printf 'missing signature'
|
|
elif [ "$saved" != "$CURRENT_SIGNATURE" ]; then
|
|
printf 'signature changed'
|
|
else
|
|
printf ''
|
|
fi
|
|
}
|
|
|
|
clean_bank() {
|
|
local kind="$1"
|
|
local dir="$2"
|
|
mkdir -p "$dir"
|
|
find "$dir" -maxdepth 1 -type f -name '*.raw' -delete
|
|
rm -f "$dir/$SIGNATURE_FILE"
|
|
if [ "$kind" = "start-filler" ] || [ "$kind" = "backchannel" ]; then
|
|
rm -f "$dir/texts.txt"
|
|
fi
|
|
}
|
|
|
|
write_signature_marker() {
|
|
local dir="$1"
|
|
mkdir -p "$dir"
|
|
printf '%s\n' "$CURRENT_SIGNATURE" > "$dir/$SIGNATURE_FILE"
|
|
}
|
|
|
|
NEEDS_REFRESH=0
|
|
declare -a PLAN=()
|
|
|
|
print_summary
|
|
echo
|
|
echo "Target languages : ${TARGET_LANGS[*]}"
|
|
echo
|
|
printf '%-14s %-6s %-18s %-8s %-64s %s\n' "Bank" "Lang" "Voice profile" "Clips" "Saved signature" "Action"
|
|
printf '%-14s %-6s %-18s %-8s %-64s %s\n' "--------------" "------" "------------------" "--------" "----------------------------------------------------------------" "----------------"
|
|
|
|
for lang in "${TARGET_LANGS[@]}"; do
|
|
for kind in filler start-filler backchannel; do
|
|
case "$kind" in
|
|
filler) dir="$(bank_dir "$FILLERS_ABS" "$lang")" ;;
|
|
start-filler) dir="$(bank_dir "$START_FILLER_ABS" "$lang")" ;;
|
|
backchannel) dir="$(bank_dir "$BACKCHANNEL_ABS" "$lang")" ;;
|
|
esac
|
|
clips="$(count_raw "$dir")"
|
|
saved="$(saved_signature "$dir")"
|
|
reason="$(refresh_reason "$dir" "$saved")"
|
|
if [ -n "$reason" ]; then
|
|
action="refresh ($reason)"
|
|
NEEDS_REFRESH=1
|
|
PLAN+=("$kind::$dir")
|
|
else
|
|
action="keep"
|
|
fi
|
|
printf '%-14s %-6s %-18s %-8s %-64s %s\n' "$kind" "$lang" "$CUE_VOICE_PROFILE" "$clips" "${saved:-<none>}" "$action"
|
|
done
|
|
done
|
|
|
|
if [ "$NEEDS_REFRESH" -eq 0 ]; then
|
|
echo
|
|
echo "No refresh needed: signatures already match the current .env voice settings."
|
|
exit 0
|
|
fi
|
|
|
|
echo
|
|
if [ "$DRY_RUN" -eq 1 ]; then
|
|
echo "Dry run only: no files deleted, no pm2 restart."
|
|
exit 0
|
|
fi
|
|
|
|
echo "Cleaning stale/generated cue assets..."
|
|
for item in "${PLAN[@]}"; do
|
|
kind="${item%%::*}"
|
|
dir="${item#*::}"
|
|
clean_bank "$kind" "$dir"
|
|
echo " cleaned $kind -> $dir"
|
|
done
|
|
|
|
if [ "$NO_RESTART" -eq 1 ]; then
|
|
echo
|
|
echo "Files cleaned. Restart skipped (--no-restart)."
|
|
echo "Run: pm2 restart $PM2_APP --update-env"
|
|
exit 0
|
|
fi
|
|
|
|
echo
|
|
echo "Restarting pm2 app: $PM2_APP"
|
|
pm2 restart "$PM2_APP" --update-env
|
|
|
|
echo "Waiting for /health..."
|
|
for _ in $(seq 1 30); do
|
|
if curl -fsS "http://localhost:${SERVER_PORT:-8787}/health" >/dev/null 2>&1; then
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
echo "Writing signature markers..."
|
|
for lang in "${TARGET_LANGS[@]}"; do
|
|
write_signature_marker "$(bank_dir "$FILLERS_ABS" "$lang")"
|
|
write_signature_marker "$(bank_dir "$START_FILLER_ABS" "$lang")"
|
|
write_signature_marker "$(bank_dir "$BACKCHANNEL_ABS" "$lang")"
|
|
done
|
|
|
|
echo
|
|
echo "Done."
|
|
echo "Tip: inspect recent regeneration logs with:"
|
|
echo " pm2 logs $PM2_APP --nostream --lines 120 | rg 'refreshing|generated|up to date|clip banks bootstrapped'"
|