feat(provider): Cloud-Provider und Vier-Stufen-Fallback-Kette
- elevenlabs: POST /v1/text-to-speech/{voice}, Header xi-api-key, Body mit
model_id eleven_multilingual_v2 und voice_settings 0.5/0.75, MP3-Bytes.
- openai: POST /v1/audio/speech, Bearer-Key, Body {model, voice, input,
response_format:mp3}; Modell konfigurierbar (Default tts-1).
- edge: Microsofts Read-Aloud-WSS-Protokoll nach edge-tts-Referenz,
inklusive aktuellem DRM Sec-MS-GEC v2 (CS64 + verschleierte SHA-256-
Variante, wörtlich portiert und gegen Referenzvektoren getestet) und
MUID-Cookie; bei 403 Uhrenkorrektur aus dem Date-Header + ein Retry.
Klare Protokoll-geaendert-Fehler statt stiller Fehlfunktion.
- chain: geordnete Kette (elevenlabs -> openai -> edge -> osbuiltin), Pin
per --provider/config, Available ohne Netz-Calls, Fallback mit
Protokoll je Stufe (nie still). Chain implementiert Provider UND
DirectSpeaker: Speak orchestriert Bytes -> Player bzw. Direktansage,
verwirft am null-Player tonlos — CLI und Daemon (Task 04) nutzen
dieselbe Kette unverändert.
- voices: neutrale Repo-Defaults je Provider+Sprache (Rachel/alloy/
Katja/Aria); Rangfolge --voice > config-Mapping > Default. KXxZ… bleibt
bewusst Privat-Default der eigenen config.toml (Spec: nicht
Repo-Default).
Tests: Cloud-Provider gegen httptest-Fakes (200/500/401, Request-Vertrag
wörtlich), Edge über Interface + Fehlerpfad-Mocks + Loopback-WSS-Fake
(kein echtes Netz) + als testing.Short überspringbare Integration-
Markierung (TALK_TEST_REAL_EDGE=1, am Dev-Host einmalig ausgeführt:
17136 Bytes echtes Audio). Kette: erster 500 -> zweiter getroffen -> Bytes;
Pin, alle-Stufen-tot, Direktansage mit/ohne null-Player.
This commit is contained in:
1 parent
88eddc63d2
commit
91cd0ec8da
15 files changed
+2434
-27
No files matched your search
@@ -0,0 +1,76 @@
|
||||
package provider
|
||||
|
||||
import "sort"
|
||||
|
||||
// voiceDefaults sind die neutralen Repo-Defaults je Provider und Sprache
|
||||
// (Teil des öffentlichen Vertrags: `talk voices` aus Task 08 listet sie,
|
||||
// überschrieben werden sie durch voice.<provider>.<lang> in der config.toml
|
||||
// oder das CLI-Flag --voice).
|
||||
//
|
||||
// Entscheidungsnotiz ElevenLabs-Stimme: Spec und Plan führen die persönliche
|
||||
// Stimme KXxZd16DiBqt82nbarJx ausdrücklich als PRIVATEN Default des Nutzers
|
||||
// („nicht Repo-Default", Spec Further Notes); der Repo-Default ist deshalb
|
||||
// die neutrale, mehrsprachige Standardstimme „Rachel" (21m00Tcm4TlvDq8ikWAM
|
||||
// aus dem öffentlichen ElevenLabs-Katalog). Die persönliche Stimme gehört in
|
||||
// die eigene config.toml:
|
||||
//
|
||||
// [voice.elevenlabs]
|
||||
// de = "KXxZd16DiBqt82nbarJx"
|
||||
// en = "KXxZd16DiBqt82nbarJx"
|
||||
//
|
||||
// OpenAI „alloy" und die Edge-Neural-Stimmen (de-DE-KatjaNeural,
|
||||
// en-US-AriaNeural) sind die jeweils neutralen Standardstimmen der APIs
|
||||
// (Edge-Stimmen sind im Task-Brief vorgegeben).
|
||||
var voiceDefaults = map[string]map[string]string{
|
||||
"elevenlabs": {
|
||||
"de": "21m00Tcm4TlvDq8ikWAM", // „Rachel", multilingual
|
||||
"en": "21m00Tcm4TlvDq8ikWAM",
|
||||
},
|
||||
"openai": {
|
||||
"de": "alloy",
|
||||
"en": "alloy",
|
||||
},
|
||||
"edge": {
|
||||
"de": "de-DE-KatjaNeural",
|
||||
"en": "en-US-AriaNeural",
|
||||
},
|
||||
"osbuiltin": {
|
||||
// osbuiltin mappt intern selbst (macOS Anna/Samantha, SAPI-Kultur,
|
||||
// espeak -v <lang>) und kennt keine Voice-IDs — hier nur der
|
||||
// Vollständigkeit wegen dokumentiert.
|
||||
"de": "",
|
||||
"en": "",
|
||||
},
|
||||
}
|
||||
|
||||
// resolveVoice liefert die Voice-ID für einen Provider und eine Sprache.
|
||||
// Rangfolge: override (CLI-Flag --voice bzw. PinVoice) > Mapping aus der
|
||||
// config.toml > neutrales Repo-Default > en-Default > alphabetisch erstes
|
||||
// Default. Ein leerer Rückgabewert heißt: der Provider kennt für diese
|
||||
// Sprache keine Stimme (osbuiltin) und entscheidet selbst.
|
||||
func resolveVoice(name, lang, override string, cfgVoices map[string]string) string {
|
||||
if override != "" {
|
||||
return override
|
||||
}
|
||||
if v := cfgVoices[lang]; v != "" {
|
||||
return v
|
||||
}
|
||||
def := voiceDefaults[name]
|
||||
if v := def[lang]; v != "" {
|
||||
return v
|
||||
}
|
||||
if v := def["en"]; v != "" {
|
||||
return v
|
||||
}
|
||||
keys := make([]string, 0, len(def))
|
||||
for k := range def {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
if v := def[k]; v != "" {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Reference in new issue
Block a user