From 3b714e297ae4c1e31fb7515e45798b70a29f87fc Mon Sep 17 00:00:00 2001 From: prosolis <5590409+prosolis@users.noreply.github.com> Date: Mon, 27 Jul 2026 13:25:28 -0700 Subject: [PATCH] Fetch the Portuguese voice Piper's own downloader can't MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit piper.download_voices pastes the voice name straight into the HTTP request line, and http.client encodes that as ASCII — so it dies with UnicodeEncodeError on the ã in pt_PT-tugão-medium before a byte leaves the container. Every pt_BR voice downloads fine. The failure lands precisely on the one voice the pt-PT pair needs, and it is the *only* European voice in the catalogue. The entrypoint now falls back to fetching the model and its config itself with the path percent-encoded, which is all the downloader was missing. Same host, same files, same destination names, so the cached-voice check and the server invocation are unchanged. --- deploy/piper/entrypoint.sh | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/deploy/piper/entrypoint.sh b/deploy/piper/entrypoint.sh index dc84ce4..d51caf7 100755 --- a/deploy/piper/entrypoint.sh +++ b/deploy/piper/entrypoint.sh @@ -10,7 +10,42 @@ port="${PIPER_PORT:-5000}" if [ ! -f "${data_dir}/${voice}.onnx" ]; then echo ">> downloading voice ${voice} into ${data_dir}" - python -m piper.download_voices "${voice}" --data-dir "${data_dir}" + # piper.download_voices cannot fetch a voice whose name isn't ASCII, and the + # only European Portuguese voice in the catalogue is pt_PT-tugão-medium: + # the downloader pastes the name straight into the request line, and + # http.client encodes that as ASCII, so it dies with UnicodeEncodeError on + # the ã before a byte leaves the container. Every pt_BR voice downloads + # fine — the failure lands precisely on the voice the pt-PT pair needs. + # + # So: try the supported path, and fall back to fetching the two files + # ourselves with the URL percent-encoded, which is all the downloader was + # missing. Same host, same files, same destination names. + python -m piper.download_voices "${voice}" --data-dir "${data_dir}" || { + echo ">> download_voices failed for ${voice}; fetching directly (non-ASCII voice name)" + python - "${voice}" "${data_dir}" <<'PY' +import json, sys, urllib.parse, urllib.request + +voice, data_dir = sys.argv[1], sys.argv[2] +BASE = "https://huggingface.co/rhasspy/piper-voices/resolve/main/" + +catalogue = json.load(urllib.request.urlopen(BASE + "voices.json", timeout=120)) +entry = catalogue.get(voice) +if entry is None: + sys.exit(f"no voice named {voice!r} in the catalogue") + +# The catalogue keys the files by repo path; only the model and its config are +# needed to serve (MODEL_CARD is licence text). +for path in entry["files"]: + if not path.endswith((".onnx", ".onnx.json")): + continue + url = BASE + urllib.parse.quote(path) + dest = f"{data_dir}/{path.rsplit('/', 1)[-1]}" + print(f">> {url} -> {dest}", flush=True) + with urllib.request.urlopen(url, timeout=600) as r, open(dest, "wb") as out: + while chunk := r.read(1 << 20): + out.write(chunk) +PY + } fi echo ">> serving ${voice} on :${port}"