feat: Mistral Voxtral als drittes Ansage-TTS-Backend, neuer Ansage-Text

ansage_bauen.sh unterstuetzt jetzt ANSAGE_TTS=mistral neben piper/say -
einmalige Cloud-Erzeugung der ansage.wav (kein Live-Anrufdaten-Transfer,
die fertige Datei wird danach lokal fuer jeden Anruf wiederverwendet).
Inline implementiert statt vom globalen Claude-Skill abhaengig, damit es
auf jeder Maschine mit nur MISTRAL_KEY in der .env laeuft.

Ansage-Text ueberarbeitet ("Demo Praxis", Kollegen im Gespraech, kuerzer
gehalten) und mit gb_oliver_neutral vertont.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PuP3zfgYqhnrwyEne2ejG7
This commit is contained in:
Jeuner 2026-08-23 11:38:16 +02:00
parent 1aca65b8e6
commit 0f550be66a
3 changed files with 48 additions and 7 deletions

View file

@ -37,10 +37,19 @@ SIP_DOMAIN=sip.plusnet.de
SIP_PASS=
SIP_PROXY=voice01.sip.plusnet.de
# --- Ansage (lokal; piper braucht Modelle unter ~/piper-voices) ---
# --- Ansage ---
# piper = lokal, natuerlich, Modelle unter ~/piper-voices
# say = lokal, macOS-Bordmittel (Rueckfall wenn Piper fehlt)
# mistral = Voxtral-API (Cloud) - nur fuer die einmalige Erzeugung der
# ansage.wav, die danach lokal fuer jeden Anruf wiederverwendet
# wird. Keine Live-Anrufdaten gehen dabei nach aussen.
ANSAGE_TTS=piper
ANSAGE_STIMME=de_DE-thorsten_emotional-medium
ANSAGE_EMOTION=0
# Nur fuer ANSAGE_TTS=mistral (Stimmen: telefon/ansage_bauen.sh --list-voices
# gibt es nicht direkt her - siehe MISTRAL_TTS_MODEL/Skill-Doku fuer die Liste)
MISTRAL_KEY=
MISTRAL_TTS_MODEL=voxtral-mini-tts-latest
# --- Deck-Triage-Board (Nextcloud) ---
# Hauptschalter: ohne 1 entsteht kein Board, egal was unten steht.

View file

@ -1 +1 @@
Hallo, bitte hinterlassen Sie kurz Ihr Anliegen. Wir rufen Sie umgehend zurück.
Guten Tag, hier ist die Demo Praxis. Alle Kollegen befinden sich im Gespräch. Bitte hinterlassen Sie ihr Anliegen nach dem Signal. Wir rufen Sie zurück.

View file

@ -2,17 +2,21 @@
# Erzeugt die Telefonansage aus telefon/ansage.txt.
# Ergebnis: telefon/ansage.wav in Telefonqualitaet (8 kHz mono, PCM16).
#
# Zwei Sprachsynthesen, beide lokal:
# piper (Standard, natuerlicher) - Modelle unter ~/piper-voices
# say (macOS-Bordmittel, Rueckfall wenn Piper fehlt)
# Drei Sprachsynthesen:
# piper (Standard, natuerlicher, lokal) - Modelle unter ~/piper-voices
# say (macOS-Bordmittel, lokal, Rueckfall wenn Piper fehlt)
# mistral (Voxtral-API, Cloud - nur fuer diesen einmaligen Erzeugungslauf;
# die fertige ansage.wav wird danach lokal fuer jeden Anruf
# wiederverwendet, es geht dabei keine Patientendaten raus)
#
# Steuerung ueber Umgebungsvariablen bzw. .env:
# ANSAGE_TTS=piper|say
# ANSAGE_STIMME=<Piper-Modellname oder say-Stimme>
# ANSAGE_TTS=piper|say|mistral
# ANSAGE_STIMME=<Piper-Modellname, say-Stimme, oder Voxtral-Stimmen-Slug>
# ANSAGE_EMOTION=<Sprecher-ID, nur bei thorsten_emotional: 0=amused,
# 1=angry, 2=disgusted, 3=drunk, 4=neutral, 5=sleepy,
# 6=surprised, 7=whisper>
# ANSAGE_TEMPO=<1.0 = normal, groesser = langsamer>
# MISTRAL_KEY=<nur fuer ANSAGE_TTS=mistral>, MISTRAL_TTS_MODEL (optional)
set -euo pipefail
cd "$(dirname "$0")/.."
# .env laden, aber bereits gesetzte Umgebungsvariablen behalten Vorrang -
@ -61,6 +65,34 @@ case "$TTS" in
ffmpeg -v error -y -i "$TMP/sprache.aiff" -ar 8000 -ac 1 "$TMP/sprache.wav"
BESCHREIBUNG="say/$SAY_STIMME"
;;
mistral)
MISTRAL_STIMME="${ANSAGE_STIMME:-en_paul_neutral}"
MISTRAL_MODELL="${MISTRAL_TTS_MODEL:-voxtral-mini-tts-latest}"
[ -n "${MISTRAL_KEY:-}" ] || { echo "MISTRAL_KEY fehlt (ANSAGE_TTS=mistral)" >&2; exit 1; }
TEXT="$(tr '\n' ' ' < telefon/ansage.txt)"
PAYLOAD="$(TTS_MODEL="$MISTRAL_MODELL" TTS_VOICE="$MISTRAL_STIMME" python3 -c '
import json, os, sys
print(json.dumps({
"model": os.environ["TTS_MODEL"],
"input": sys.argv[1],
"voice": os.environ["TTS_VOICE"],
"response_format": "mp3",
}))
' "$TEXT")"
RESP="$(curl -sf -X POST "https://api.mistral.ai/v1/audio/speech" \
-H "Authorization: Bearer $MISTRAL_KEY" -H "Content-Type: application/json" \
-d "$PAYLOAD")" || { echo "Voxtral-API-Aufruf fehlgeschlagen" >&2; exit 1; }
python3 - "$TMP/sprache.mp3" "$RESP" <<'PYEOF'
import json, base64, sys
ziel, roh = sys.argv[1], sys.argv[2]
b64 = json.loads(roh).get("audio_data")
if not b64:
print("Keine Audiodaten in der Voxtral-Antwort", file=sys.stderr); sys.exit(1)
open(ziel, "wb").write(base64.b64decode(b64))
PYEOF
ffmpeg -v error -y -i "$TMP/sprache.mp3" -ar 8000 -ac 1 "$TMP/sprache.wav"
BESCHREIBUNG="mistral-voxtral/$MISTRAL_STIMME"
;;
*) echo "Unbekanntes TTS-Backend: $TTS" >&2; exit 1 ;;
esac