Files
petal/deploy
prosolis 5120b1e5a2 Read-aloud: natural neural voice via local Piper TTS
Replace the browser's robotic Web Speech API (espeak on Chromium) as the
primary read-aloud path with server-side Piper neural TTS, served by petal
and kept fully offline on millenia next to Ollama.

- internal/tts: proxy short passages to Piper, transcode WAV -> mp3/opus via
  ffmpeg, content-addressed disk cache (instant re-taps). Each Piper instance
  loads one voice, so language routes to its own endpoint:{voice}. Unknown
  language -> 404 so the client falls back to Web Speech. UTF-8-safe truncation
  for multibyte (Chinese) text.
- config: TTS_ENDPOINT / TTS_ENDPOINT_ZH / TTS_VOICE_EN / TTS_VOICE_ZH /
  TTS_CACHE_DIR / TTS_TIMEOUT / TTS_AUDIO_FORMAT. Route mounts only when
  TTS_ENDPOINT is set; otherwise unchanged behavior.
- web/audio/speech.ts: speak() hits /api/tts first, falls back to Web Speech on
  any failure; rapid-tap-safe via a request token. Call sites unchanged.
- deploy/: Piper user systemd units (EN :5005, ZH :5006), setup script, README.

English (en_US-amy-medium) and Chinese (zh_CN-huayan-medium) are both live.

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-26 11:34:52 -07:00
..

Deploying read-aloud (Piper TTS) to millenia

Petal's read-aloud generates audio with a local Piper neural-TTS server and transcodes it to mp3 with ffmpeg. Both run on millenia (192.168.1.212); nothing leaves the box. If Piper is down or TTS_ENDPOINT is unset, the frontend falls back to the browser's Web Speech API automatically.

1. Piper as a systemd service (one-time)

# from this repo, on your workstation:
scp deploy/piper.service deploy/setup-piper.sh 192.168.1.212:/tmp/
ssh 192.168.1.212 'cd /tmp && sudo ./setup-piper.sh'

setup-piper.sh creates ~/piper/venv, installs piper-tts[http], downloads the en_US-amy-medium voice into ~/piper/voices, installs+enables piper.service (loopback :5005), and smoke-tests it. Idempotent.

Check it any time: systemctl status piper, journalctl -u piper -f.

2. Point petal at Piper + redeploy the binary

Add to petal's environment (its .env or launch env):

TTS_ENDPOINT=http://127.0.0.1:5005
TTS_VOICE_EN=en_US-amy-medium
TTS_AUDIO_FORMAT=mp3

Then ship the rebuilt binary (go build -o petal ./cmd/server already done) and restart the petal :8088 session. Confirm the log line: read-aloud enabled (TTS endpoint=http://127.0.0.1:5005).

3. Verify

# on millenia — end-to-end through petal, including ffmpeg transcode:
curl -sf -X POST localhost:8088/api/tts \
  -H 'Content-Type: application/json' \
  -d '{"text":"hello there","lang":"en-US"}' -o /tmp/petal-tts.mp3 \
  && file /tmp/petal-tts.mp3   # expect: Audio file ... MPEG ... layer III
  • Second identical call is served from the cache (~/petal/.../data/tts/*.mp3).
  • A language with no configured instance returns 404 → client uses Web Speech.
  • Browser check via the uitest harness (~/petal/uitest): tap a word in the WordCard / select a sentence and hit speak — expect the natural Piper voice.

Chinese voice (live)

Each Piper HTTP server loads ONE model, so Chinese runs as a second instance: piper-zh.service on :5006 with zh_CN-huayan-medium. Deployed via:

scp deploy/piper-zh.service 192.168.1.212:~/.config/systemd/user/
ssh 192.168.1.212 'export XDG_RUNTIME_DIR=/run/user/$(id -u)
  ~/piper/venv/bin/python -m piper.download_voices zh_CN-huayan-medium --data-dir ~/piper/voices
  systemctl --user daemon-reload && systemctl --user enable --now piper-zh.service'

Then in petal's start.sh: TTS_ENDPOINT_ZH=http://127.0.0.1:5006 and TTS_VOICE_ZH=zh_CN-huayan-medium. The handler maps language → instance from config, so adding more languages is just another instance + env pair (no code change).