Files
church-translate/app/tools/setup-local-voices.sh
T

71 lines
3.3 KiB
Bash
Raw Permalink Normal View History

#!/bin/bash
# One-time, per-Mac installer for the free local voices
# (Kokoro and Qwen3-TTS, both via mlx-audio).
# Bought once for $0: Apache-2.0 weights, runs entirely on this Mac.
# The venv CANNOT be copied between machines — run this on each Mac.
set -euo pipefail
DIR="$HOME/Library/Application Support/Church Translate Local Voices"
VENV="$DIR/venv"
PORT="${1:-43111}"
echo "== Church Translate — local voices installer =="
# macOS ships Python 3.9, which mlx-audio cannot use — so FIND a 3.10+ python
# rather than assuming the one on PATH. Checked in order: uv-managed (this Mac
# has them), Homebrew, versioned names on PATH, then plain python3.
PY=""
pick_py() {
for c in "$@"; do
[ -x "$c" ] || continue
"$c" -c 'import sys; sys.exit(0 if sys.version_info >= (3,10) else 1)' 2>/dev/null && { PY="$c"; return 0; }
done
return 1
}
pick_py $(ls -d "$HOME/.local/share/uv/python/"*/bin/python3 2>/dev/null | sort -r) \
/opt/homebrew/bin/python3.13 /opt/homebrew/bin/python3.12 /opt/homebrew/bin/python3.11 \
/opt/homebrew/bin/python3 /usr/local/bin/python3 \
"$(command -v python3.13 || true)" "$(command -v python3.12 || true)" \
"$(command -v python3.11 || true)" "$(command -v python3 || true)" || {
echo "No Python 3.10+ found. Install one, then re-run:"
echo " brew install python@3.12 # or: xcode-select --install"
exit 1
}
echo "using python: $PY ($("$PY" --version 2>&1))"
mkdir -p "$DIR"
[ -d "$VENV" ] || "$PY" -m venv "$VENV"
"$VENV/bin/pip" install --upgrade pip >/dev/null
# The exact dependency set, learned from consecutive boot failures:
# mlx_audio.server needs uvicorn/fastapi/python-multipart; webrtcvad still
# imports pkg_resources, which setuptools>=81 removed.
"$VENV/bin/pip" install 'setuptools<81' mlx-audio uvicorn fastapi python-multipart webrtcvad
# Kokoro's text processing. mlx-audio ships the model code but not these, and
# without them synthesis fails at the last step with an ImportError for
# 'misaki' — after the 330MB model has already downloaded. espeakng-loader
# carries its own libespeak-ng, so no Homebrew and nothing system-wide.
"$VENV/bin/pip" install misaki phonemizer-fork espeakng-loader
echo "== prefetching the model (one-time download, ~2GB) =="
"$VENV/bin/python3" - <<'PY'
from huggingface_hub import snapshot_download
snapshot_download('mlx-community/Qwen3-TTS-12Hz-1.7B-CustomVoice-8bit')
print('model cached')
PY
echo "== self-test: boot the server and synthesize one sentence =="
"$VENV/bin/python3" -m mlx_audio.server --host 127.0.0.1 --port "$PORT" &
SRV=$!
trap 'kill $SRV 2>/dev/null' EXIT
for i in $(seq 1 30); do curl -s -o /dev/null "http://127.0.0.1:$PORT/v1/models" && break; sleep 1; done
OUT="$DIR/selftest.wav"
curl -s -X POST "http://127.0.0.1:$PORT/v1/audio/speech" \
-H 'Content-Type: application/json' \
-d '{"model":"mlx-community/Qwen3-TTS-12Hz-1.7B-CustomVoice-8bit","voice":"aiden","input":"The Lord bless you and keep you.","lang_code":"english","response_format":"wav","max_tokens":300,"temperature":0.2}' \
-o "$OUT"
head -c 4 "$OUT" | grep -q RIFF && echo "SELF-TEST OK — $OUT ($(stat -f%z "$OUT") bytes)" || { echo "SELF-TEST FAILED"; exit 1; }
kill $SRV 2>/dev/null; trap - EXIT
echo
echo "Done. In Church Translate: Operator pane → Premium voices → 'Local (this Mac — free)'."