Files
church-translate/app/tools/setup-local-voices.sh
T
christianandClaude Opus 5 c9eef0672c Church Translate, as it runs at the campuses
Live sermon translation into 100+ languages, running entirely on one Mac per
campus: audio in, voice detection, Whisper on the GPU, NLLB-200 in its own
process, and a built-in web server the congregation reads from on their
phones. No audio leaves the building.

This is the source only. What used to sit beside it — 14 GB of speech and
translation models, 8 GB of voice bundles, 6 GB of natural voices, the Electron
caches, and the app data holding the operator PIN and every service transcript
— is machine state and downloaded assets, and stays in the NAS backup where it
belongs. The vendored node, cloudflared and Windows ffmpeg binaries are out too
(533 MB, re-fetched for packaging); the browser-side runtime beside them is
kept, so a checkout still serves the congregation page.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-23 15:41:21 -04:00

71 lines
3.3 KiB
Bash
Executable File

#!/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)'."