deploy/mainnet: add PiperTTS (private) + supplier-tts (audio.synthesize.piper.v1)

Mirrors deploy/testnet/docker-compose.supplier-tts.yml and the simulation
PiperTTS stack, co-located on vector-marketplace as a private internal
service on marketplace-mainnet-net (no Traefik labels, no public DNS for
Piper itself). Supplier reaches Piper at http://marketplace-mainnet-tts-piper:8000.
Voice: en_US-lessac-high. New supplier wallet + on-chain advert are
published out-of-band by the operator per the runbook header.

Files:
- deploy/mainnet/docker-compose.tts-piper.yml         openedai-speech-min, private
- deploy/mainnet/docker-compose.supplier-tts.yml      CAPABILITY_KIND=tts supplier
- deploy/mainnet/install-voice-extras.sh              voice download + yaml mapping
- deploy/mainnet/voice-mappings.py                    idempotent yaml editor
- supplier/.env.tts.example                           operator env template
- .gitignore                                          whitelist .env.*.example

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
david
2026-05-19 17:15:06 +02:00
parent ecd9984419
commit a997445970
6 changed files with 363 additions and 1 deletions

View File

@@ -0,0 +1,60 @@
#!/usr/bin/env python3
# voice-mappings.py — idempotently apply project-specific voice aliases to
# /app/config/voice_to_speaker.yaml inside the marketplace-mainnet-tts-piper container.
#
# Why this exists:
# The openedai-speech-min image populates voice_to_speaker.yaml on first
# start with its OWN defaults (alloy/echo/fable/onyx/nova/shimmer mapped
# onto the bundled LibriTTS + northern-english-male voices). Voices we
# install ourselves (e.g. en_US-lessac-high) need a name in that yaml so
# the OpenAI-shape `voice` field can resolve to them. This script adds
# those entries — and only those — without disturbing the defaults.
#
# Idempotency contract:
# Reads the yaml, compares each desired entry against what's there, only
# rewrites if at least one entry is missing or stale. Prints "modified"
# on write, "unchanged" otherwise — install-voice-extras.sh greps for
# "modified" to decide whether to restart the container.
#
# Run via:
# docker cp voice-mappings.py marketplace-mainnet-tts-piper:/tmp/voice-mappings.py
# docker exec marketplace-mainnet-tts-piper python3 /tmp/voice-mappings.py
import sys
import yaml
CONFIG_PATH = "/app/config/voice_to_speaker.yaml"
# Mappings to ensure exist under `tts-1`. Adding more voices later is a
# matter of dropping the .onnx + .onnx.json into voices/ (handled by
# install-voice-extras.sh) and adding an entry here.
DESIRED_TTS1 = {
# en_US-lessac-high — high-quality American female single-speaker voice.
# Single-speaker model, so no `speaker:` key.
"lessac": {"model": "voices/en_US-lessac-high.onnx"},
}
def main() -> int:
with open(CONFIG_PATH) as f:
cfg = yaml.safe_load(f) or {}
cfg.setdefault("tts-1", {})
modified_keys = []
for name, mapping in DESIRED_TTS1.items():
if cfg["tts-1"].get(name) != mapping:
cfg["tts-1"][name] = mapping
modified_keys.append(name)
if modified_keys:
with open(CONFIG_PATH, "w") as f:
yaml.safe_dump(cfg, f, sort_keys=False)
print(f"modified — applied: {', '.join(modified_keys)}")
else:
print("unchanged — all mappings already present")
return 0
if __name__ == "__main__":
sys.exit(main())