#!/usr/bin/env bash # Fetch the configured voice if the shared volume doesn't have it yet, then # serve it. The download is the only step that needs the internet, and it runs # once per voice for the life of the volume — Petal itself stays offline-first. set -euo pipefail voice="${PIPER_VOICE:?PIPER_VOICE must be set}" data_dir="${PIPER_DATA_DIR:-/voices}" port="${PIPER_PORT:-5000}" if [ ! -f "${data_dir}/${voice}.onnx" ]; then echo ">> downloading voice ${voice} into ${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}" # 0.0.0.0 is safe here: the container sits on Petal's internal compose network # with no published ports, so only Petal can reach it. exec python -m piper.http_server \ -m "${voice}" \ --data-dir "${data_dir}" \ --host 0.0.0.0 --port "${port}"