feat(audio): add offline local neural voice via Piper (sv + en)
Announcements can use Piper, a free local neural TTS, instead of the browser's system voice. No account, no paid service, and it keeps working with no internet at the track. - npm run tts:install (or Settings -> Audio -> Install) downloads the standalone Piper release for Linux/Windows/macOS plus Swedish and English voices into piper/ once - server/tts.js generates each line once, one at a time, and caches it as WAV under data/tts/; a new line takes ~0.3 s with the standalone build - src/piper_speech.js plays it and falls back to the browser voice if Piper is missing, errors, or takes over 2.5 s, so no announcement is lost and timing is never held up - "Pre-generate phrases and driver names" renders every fixed phrase and name in both languages before race day Browser voice stays the default. Generation routes fall under the admin PIN. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -175,3 +175,11 @@ Deliberately still open, so the original findings remain valid for an installati
|
||||
- A cross-origin admin UI (`RC_ALLOWED_ORIGINS`) can't use the cookie, which is same-origin (`SameSite=Strict`) by design.
|
||||
|
||||
Design notes: the PIN hash (scrypt, salted) lives in `data/auth.json`, never in the `app_state` blob, so it can't leak via `GET /api/state`, a JSON export or a backup. The login cookie is `HttpOnly`, `SameSite=Strict`, HMAC-signed with a per-installation secret (`data/auth_secret`), valid 30 days, and invalidated by any PIN change. Token signatures are format-checked strictly, because `Buffer.from(hex)` silently drops a trailing odd character (caught by the unit tests while this was built).
|
||||
|
||||
## Follow-up - 2026-09-27: local voice (Piper)
|
||||
|
||||
New surface, reviewed as it was built:
|
||||
|
||||
- `POST /api/tts/speech`, `/api/tts/pregenerate` and `/api/tts/install` are writes, so they fall under the admin PIN once one is set. `GET /api/tts/status` is open like other reads; it exposes the Piper binary path and voice folders (same class of detail as `/api/health`'s database path - "Low" finding above).
|
||||
- Piper runs through `execFile` with array arguments and the text on stdin, never a shell, so text can't inject commands. Text is capped at 300 characters, and generation is serialized (one Piper process at a time, 20 s timeout), which bounds how much CPU a flood of requests can take - but without a PIN anyone on the network can still keep that one queue busy.
|
||||
- `npm run tts:install` / `POST /api/tts/install` download the Piper release from GitHub and voices from Hugging Face over HTTPS, without checksum verification - the same trust model as the "Medium: bundled AMMC binaries are trusted artifacts" finding above, and the same fix applies (pin expected SHA-256 hashes).
|
||||
|
||||
Reference in New Issue
Block a user