fix(tts): voice_gender im UserPrefs-Schema -> Stimmwahl wirkt endlich
Eigentliche Ursache für 'immer weibliche Stimme, egal welcher Provider': UserPrefs kannte kein Feld voice_gender, daher verwarf pydantic in PUT /api/me/prefs die Geschlechtswahl still. resolve_route sah immer 'any' -> Piper/Cartesia lieferten die weibliche Stimme (kerstin bzw. Alina). - voice_gender zum Schema hinzugefügt; Frontend sendet es bereits, resolve_route liest es bereits (ROUTE_KEYS), voice_for_route mappt m/f bereits. - Regressions-Test: voice_gender persistiert über /api/me/prefs. - Docs/stimmen-inventar.md: zentrale Übersicht aller Stimmen je Provider/ Sprache/Geschlecht (Name, Datei, UUID) inkl. Fundstellen im Code. Live verifiziert (Profil jamulix, de): m -> de_DE-thorsten-high, f -> de_DE-kerstin-low. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
5c63f7b6cc
commit
6bf281fc30
3 changed files with 123 additions and 0 deletions
111
Docs/stimmen-inventar.md
Normal file
111
Docs/stimmen-inventar.md
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
# Stimmen-Inventar (TTS) — eine Quelle der Wahrheit
|
||||
|
||||
Diese Datei listet **alle** TTS-Stimmen je Provider, Sprache und Geschlecht: Stimmenname,
|
||||
Datei bzw. UUID und die Stelle im Code/Config, an der sie definiert ist. Ziel: Schluss mit
|
||||
dem Suchen über mehrere Dateien.
|
||||
|
||||
> **Wer wählt die Stimme?** `app/dependencies.py::voice_for_route(tts_provider, language, voice_gender)`.
|
||||
> Es liest `voice_gender` aus der aufgelösten Route (`resolve_route`), die es wiederum aus den
|
||||
> Nutzer-Prefs zieht. Die Geschlechtswahl im Web-UI wird über `PUT /api/me/prefs` gespeichert —
|
||||
> dafür **muss** `voice_gender` im Schema `app/schemas.py::UserPrefs` stehen (sonst verwirft
|
||||
> pydantic das Feld still → Nutzer hört immer „any" = weiblich).
|
||||
|
||||
Legende: ♀ = weiblich, ♂ = männlich · „—" = nicht vorhanden/keine Geschlechtswahl.
|
||||
|
||||
---
|
||||
|
||||
## 1. Piper (lokal, Standard im Profil `jamulix`)
|
||||
|
||||
- **Definition Geschlecht→Stimme:** `app/dependencies.py` → `PIPER_VOICE_GENDERED` (mit Geschlecht)
|
||||
und `LANG_TO_PIPER_VOICE` (geschlechtsloser Fallback).
|
||||
- **Modelldateien:** `config/voices/<name>.onnx` (+ `.onnx.json`); Verzeichnis aus
|
||||
`config/voice-assistant.toml::piper_voices_dir`.
|
||||
|
||||
| Sprache | ♀ Stimme | Datei (.onnx) | ♂ Stimme | Datei (.onnx) |
|
||||
|---|---|---|---|---|
|
||||
| de | de_DE-kerstin-low | `de_DE-kerstin-low` | de_DE-thorsten-high | `de_DE-thorsten-high` |
|
||||
| en | en_US-amy-medium | `en_US-amy-medium` | en_US-lessac-high | `en_US-lessac-high` |
|
||||
| fr | fr_FR-siwis-medium | `fr_FR-siwis-medium` | fr_FR-tom-medium | `fr_FR-tom-medium` |
|
||||
| es | es_ES-sharvard-medium **#1** | `es_ES-sharvard-medium` (Speaker 1=F) | es_ES-sharvard-medium **#0** | `es_ES-sharvard-medium` (Speaker 0=M) |
|
||||
| it | it_IT-paola-medium | `it_IT-paola-medium` | it_IT-riccardo-x_low | `it_IT-riccardo-x_low` |
|
||||
| ru | ru_RU-irina-medium | `ru_RU-irina-medium` | ru_RU-ruslan-medium | `ru_RU-ruslan-medium` |
|
||||
| pl | pl_PL-gosia-medium | `pl_PL-gosia-medium` | pl_PL-darkman-medium | `pl_PL-darkman-medium` |
|
||||
| pt | — *(keine ♀ in Piper)* | — | pt_BR-faber-medium | `pt_BR-faber-medium` |
|
||||
| ar | — *(keine ♀ in Piper)* | — | ar_JO-kareem-medium | `ar_JO-kareem-medium` |
|
||||
| zh / cmn | zh_CN-huayan-medium | `zh_CN-huayan-medium` | — *(kein ♂ nutzbar)* | — |
|
||||
|
||||
Hinweise:
|
||||
- `es` ist ein Multi-Speaker-Modell: Speaker-Index `#0`=männlich, `#1`=weiblich (`speaker_id_map`).
|
||||
- `pt`/`ar`/`zh` haben in Piper **keine** Geschlechts-Varianten → beide Schalterstellungen liefern
|
||||
dieselbe Stimme (Fallback aus `LANG_TO_PIPER_VOICE`).
|
||||
- **zh ♂** wäre nur über `zh_CN-chaowen-medium` möglich, das aber `phoneme_type=pinyin` (g2pW)
|
||||
braucht — in dieser Installation **nicht** lauffähig (erzeugt kein Audio).
|
||||
|
||||
---
|
||||
|
||||
## 2. Cartesia (Cloud, Modell sonic-turbo)
|
||||
|
||||
- **Definition:** `config/voice-assistant.toml::cartesia_voices`, Format `lang:gender:uuid:name`.
|
||||
- **Auflösung UUID:** `app/providers/tts/cartesia.py::_resolve_voice_id`
|
||||
(Fallback-Kette f→m→any→`cartesia_voice_id`).
|
||||
|
||||
| Sprache | Geschlecht | Name | UUID |
|
||||
|---|---|---|---|
|
||||
| de | ♂ | Sebastian | `b7187e84-fe22-4344-ba4a-bc013fcb533e` |
|
||||
| de | ♀ | Alina | `38aabb6a-f52b-4fb0-a3d1-988518f4dc06` |
|
||||
| en | ♂ | Caspar | `4f7f1324-1853-48a6-b294-4e78e8036a83` |
|
||||
| en | ♀ | Gemma | `62ae83ad-4f6a-430b-af41-a9bede9286ca` |
|
||||
| fr | ♂ | Antoine | `0418348a-0ca2-4e90-9986-800fb8b3bbc0` |
|
||||
| fr | ♀ | Amélie | `7c58f4a4-a72c-42fa-a503-41b9408820f3` |
|
||||
| es | ♂ | Augustin (mx) | `2695b6b5-5543-4be1-96d9-3967fb5e7fec` |
|
||||
| es | ♀ | Daniela (mx) | `5c5ad5e7-1020-476b-8b91-fdcbe9cc313c` |
|
||||
| pt | ♂ | Bruno | `b603811e-54c2-4a0a-8854-09eab9ffa63f` |
|
||||
| pt | ♀ | Isabella | `c9611be8-aae9-4a93-bb1c-98dd6b7d52a4` |
|
||||
| it | — | sonic-turbo unterstützt IT nicht (auskommentiert) | — |
|
||||
| pl | — | sonic-turbo unterstützt PL nicht (auskommentiert) | — |
|
||||
| ru | — | sonic-turbo unterstützt RU nicht (auskommentiert) | — |
|
||||
| ar | — | Cartesia unterstützt Arabic-TTS nicht | — |
|
||||
| zh | — | nicht konfiguriert | — |
|
||||
|
||||
> Für it/pl/ru/ar/zh greift bei Cartesia die globale Fallback-Kette → letztlich `tts_fallback`
|
||||
> (`config/voice-assistant.toml`, aktuell `piper`).
|
||||
|
||||
---
|
||||
|
||||
## 3. OpenRouter (Cloud, Modell `openai/gpt-4o-mini-tts`)
|
||||
|
||||
- **Definition:** `app/config.py::openrouter_tts_voice` (Default `alloy`), `openrouter_tts_model`.
|
||||
- **Keine** Geschlechts- oder Sprachauswahl: **eine** feste Stimme für alle Sprachen.
|
||||
`voice_for_route` gibt für OpenRouter `None` zurück → Default-Stimme.
|
||||
|
||||
| Sprache | ♀ | ♂ |
|
||||
|---|---|---|
|
||||
| alle | — (eine feste Stimme `alloy`) | — |
|
||||
|
||||
---
|
||||
|
||||
## 4. Chatterbox (lokaler GPU-Dienst, Voice-Cloning)
|
||||
|
||||
- **Definition:** `app/config.py::chatterbox_*`; Referenz-WAV je Sprache unter
|
||||
`chatterbox_voices_dir` als `config/voices/<lang>.wav`.
|
||||
- **Keine** Geschlechtswahl: pro Sprache **eine** geklonte Stimme (Geschlecht = Aufnahme).
|
||||
|
||||
| vorhandene Referenz-WAVs | en, es, fr, it, nl, ru, zh |
|
||||
|---|---|
|
||||
| ohne `<lang>.wav` → globale Default-Stimme | de, pt, pl, ar, … |
|
||||
|
||||
---
|
||||
|
||||
## Lücken-Überblick (welche Kombination fehlt)
|
||||
|
||||
| Sprache | Piper | Cartesia | OpenRouter | Chatterbox |
|
||||
|---|---|---|---|---|
|
||||
| de, en, fr, es | ♀+♂ | ♀+♂ | nur 1 fix | 1 (Klon) |
|
||||
| pt | nur ♂ | ♀+♂ | nur 1 fix | Default |
|
||||
| it, pl, ru | ♀+♂ | — | nur 1 fix | teils 1 |
|
||||
| ar | nur ♂ | — | nur 1 fix | Default |
|
||||
| zh | nur ♀ | — | nur 1 fix | 1 (Klon) |
|
||||
|
||||
Für die echten Lücken (pt ♀ / ar ♀ / zh ♂) braucht es **sprach-/geschlechtsabhängiges
|
||||
Provider-Routing** (z. B. pt ♀ → Cartesia Isabella) plus UI-Ausgrauen nicht verfügbarer
|
||||
Kombinationen. Siehe `MULTITOOL_AUSBAU_PLAN.md` / offene Aufgaben.
|
||||
Loading…
Add table
Add a link
Reference in a new issue