client sdk
This commit is contained in:
Executable
+413
@@ -0,0 +1,413 @@
|
||||
#!/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'"
|
||||
Reference in New Issue
Block a user