2026-10-18 16:34:43 +02:00
|
|
|
package provider
|
|
|
|
|
|
|
|
|
|
import "sort"
|
|
|
|
|
|
2026-10-24 11:15:35 +02:00
|
|
|
// voiceDefaults are the neutral repo defaults per provider and language
|
|
|
|
|
// (part of the public contract: `talk voices` lists them; they are
|
|
|
|
|
// overridden by voice.<provider>.<lang> in config.toml or the CLI flag
|
|
|
|
|
// --voice).
|
2026-10-18 16:34:43 +02:00
|
|
|
//
|
2026-10-24 11:15:35 +02:00
|
|
|
// Decision note on the ElevenLabs voice: spec and plan explicitly treat the
|
|
|
|
|
// personal voice KXxZd16DiBqt82nbarJx as the user's PRIVATE default ("not a
|
|
|
|
|
// repo default", spec Further Notes); the repo default is therefore the
|
|
|
|
|
// neutral, multilingual standard voice "Rachel" (21m00Tcm4TlvDq8ikWAM from
|
|
|
|
|
// the public ElevenLabs catalog). The personal voice belongs in one's own
|
|
|
|
|
// config.toml:
|
2026-10-18 16:34:43 +02:00
|
|
|
//
|
|
|
|
|
// [voice.elevenlabs]
|
|
|
|
|
// de = "KXxZd16DiBqt82nbarJx"
|
|
|
|
|
// en = "KXxZd16DiBqt82nbarJx"
|
|
|
|
|
//
|
2026-10-24 11:15:35 +02:00
|
|
|
// OpenAI "alloy" and the Edge neural voices (de-DE-KatjaNeural,
|
|
|
|
|
// en-US-AriaNeural) are the respective neutral standard voices of the APIs.
|
2026-10-18 16:34:43 +02:00
|
|
|
var voiceDefaults = map[string]map[string]string{
|
|
|
|
|
"elevenlabs": {
|
2026-10-24 11:15:35 +02:00
|
|
|
"de": "21m00Tcm4TlvDq8ikWAM", // "Rachel", multilingual
|
2026-10-18 16:34:43 +02:00
|
|
|
"en": "21m00Tcm4TlvDq8ikWAM",
|
|
|
|
|
},
|
|
|
|
|
"openai": {
|
|
|
|
|
"de": "alloy",
|
|
|
|
|
"en": "alloy",
|
|
|
|
|
},
|
|
|
|
|
"edge": {
|
|
|
|
|
"de": "de-DE-KatjaNeural",
|
|
|
|
|
"en": "en-US-AriaNeural",
|
|
|
|
|
},
|
|
|
|
|
"osbuiltin": {
|
2026-10-24 11:15:35 +02:00
|
|
|
// osbuiltin maps internally on its own (macOS Anna/Samantha, SAPI
|
|
|
|
|
// culture, espeak -v <lang>) and has no voice IDs; documented here
|
|
|
|
|
// only for completeness.
|
2026-10-18 16:34:43 +02:00
|
|
|
"de": "",
|
|
|
|
|
"en": "",
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-24 11:15:35 +02:00
|
|
|
// resolveVoice returns the voice ID for a provider and a language.
|
|
|
|
|
// Precedence: override (CLI flag --voice or PinVoice) > mapping from
|
|
|
|
|
// config.toml > neutral repo default > en default > alphabetically first
|
|
|
|
|
// default. An empty result means: the provider has no voice for this
|
|
|
|
|
// language (osbuiltin) and decides on its own.
|
2026-10-18 16:34:43 +02:00
|
|
|
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 ""
|
|
|
|
|
}
|