fix(frank-tts): make the boot warm pass survive both readers of its env
`WARM_TEXT` reaches the container **with its quotes**. The unit reads the env file through podman's `--env-file`, which takes the literal remainder of the line — no shell parsing — while bash `source` strips one layer. The hook interpolated the raw value into the synthesis body, so with the quotes intact the JSON was invalid and every voice logged "registered, but the warm synthesis failed" while a hand-run hook (bash shape) succeeded. Reproduced both ways against a real `podman run --env-file`, not by reading the man page. The hook now strips exactly one matched pair — only when both ends are the same quote AND the inner content carries another quote or a backslash, so a value that legitimately contains quotes is never mangled — and then JSON-escapes what is left, which is what makes a sentence like `He said "hi\" once"` arrive as itself instead of corrupting the body. The seam gains a test that reads `scripts/frank-tts-engine.env` BOTH ways — `source` and the way `podman --env-file` does — and asserts the engine receives the same sentence either way, in both shapes. A test that only sources the file cannot see this bug, which is how it survived the first deploy.
This commit is contained in:
1 parent
d135ae7f02
commit
d336bdaa66
4 files changed
+95
-8
No files matched your search
@@ -78,6 +78,31 @@ set -euo pipefail
|
||||
# systemd counts the whole warm pass against the unit's start deadline.
|
||||
: "${REQUEST_TIMEOUT:=120}"
|
||||
|
||||
# The warm text goes into a JSON string literal by interpolation. Two steps make
|
||||
# whatever the two readers of scripts/frank-tts-engine.env deliver arrive as the
|
||||
# sentence itself:
|
||||
#
|
||||
# 1. Unwrap ONE shell-style quoting layer. bash (`set -a; source`) strips the
|
||||
# quotes around a value with spaces; podman's `--env-file` does NOT — measured
|
||||
# 2026-10-18 against the image's pinned base, `WARM_TEXT="…"` reaches the
|
||||
# container WITH its quotes. Unwrapped only when the whole value is exactly one
|
||||
# quoted string (starts AND ends with `"` and holds no quote inside), so a
|
||||
# sentence that legitimately contains quotes is never mangled.
|
||||
# 2. Escape the two characters that would corrupt or end a JSON string: a
|
||||
# backslash, and a double quote.
|
||||
#
|
||||
# Without step 1 the body is `{"input":""Warm up…"",…}`, the engine's JSON parser
|
||||
# rejects it, every voice stays cold, and the boot still reads healthy. Voice NAMES
|
||||
# need neither step: the character class checked below admits no quote, no
|
||||
# backslash and no control byte.
|
||||
warm_text="$WARM_TEXT"
|
||||
if (( ${#warm_text} >= 2 )) && [[ "$warm_text" == \"*\" ]]; then
|
||||
inner="${warm_text:1:${#warm_text}-2}"
|
||||
[[ "$inner" != *'"'* ]] && warm_text="$inner"
|
||||
fi
|
||||
warm_json="${warm_text//\\/\\\\}"
|
||||
warm_json="${warm_json//\"/\\\"}"
|
||||
|
||||
fail=0
|
||||
log() { printf 'frank-tts-engine-warm: %s\n' "$*"; }
|
||||
|
||||
@@ -135,7 +160,7 @@ for dir in "$VOICES_DIR"/*/; do
|
||||
start=$(date +%s%3N)
|
||||
if ! curl -fsS --max-time "$REQUEST_TIMEOUT" -o /dev/null \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-binary "{\"input\":\"${WARM_TEXT}\",\"voice\":\"${name}\",\"response_format\":\"wav\"}" \
|
||||
--data-binary "{\"input\":\"${warm_json}\",\"voice\":\"${name}\",\"response_format\":\"wav\"}" \
|
||||
"${ENGINE_URL}/v1/audio/speech"; then
|
||||
log "voice '${name}': registered, but the warm synthesis failed — first live request stays cold"
|
||||
fail=1
|
||||
@@ -149,7 +174,7 @@ for dir in "$VOICES_DIR"/*/; do
|
||||
start=$(date +%s%3N)
|
||||
if ! curl -fsS --max-time "$REQUEST_TIMEOUT" -o /dev/null \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-binary "{\"input\":\"${WARM_TEXT}\",\"voice\":\"${name}\",\"response_format\":\"wav\"}" \
|
||||
--data-binary "{\"input\":\"${warm_json}\",\"voice\":\"${name}\",\"response_format\":\"wav\"}" \
|
||||
"${ENGINE_URL}/v1/audio/speech"; then
|
||||
log "voice '${name}': warm synthesis failed after a cold one that worked"
|
||||
fail=1
|
||||
|
||||
Reference in new issue
Block a user