2026-10-24 10:08:44 +02:00
|
|
|
package provider
|
|
|
|
|
|
|
|
|
|
import "sort"
|
|
|
|
|
|
2026-10-24 11:15:35 +02:00
|
|
|
// Voice is an entry of the voice catalog: a known voice of a provider for a
|
|
|
|
|
// language, with a readable name. The catalog is curated (the common
|
|
|
|
|
// standard voices), not complete; any voice ID of the provider also works
|
|
|
|
|
// without a catalog entry (--voice, config).
|
2026-10-24 10:08:44 +02:00
|
|
|
type Voice struct {
|
|
|
|
|
Provider string
|
|
|
|
|
Lang string // de | en
|
|
|
|
|
ID string
|
|
|
|
|
Name string
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-24 11:15:35 +02:00
|
|
|
// catalog lists the known voices per provider. ElevenLabs: public premade
|
|
|
|
|
// voices (multilingual, hence listed for both de AND en). OpenAI: the voices
|
|
|
|
|
// of the TTS API (language independent). Edge: neural voices of the
|
|
|
|
|
// respective locale. osbuiltin has no IDs (the OS chooses).
|
2026-10-24 10:08:44 +02:00
|
|
|
var catalog = []Voice{
|
|
|
|
|
{"elevenlabs", "de", "21m00Tcm4TlvDq8ikWAM", "Rachel"},
|
|
|
|
|
{"elevenlabs", "de", "pNInz6obpgDQGcFmaJgB", "Adam"},
|
|
|
|
|
{"elevenlabs", "de", "EXAVITQu4vr4xnSDxMaL", "Bella"},
|
|
|
|
|
{"elevenlabs", "de", "ErXwobaYiN019PkySvjV", "Antoni"},
|
|
|
|
|
{"elevenlabs", "de", "TxGEqnHWrfWFTfGW9XjX", "Josh"},
|
|
|
|
|
{"elevenlabs", "en", "21m00Tcm4TlvDq8ikWAM", "Rachel"},
|
|
|
|
|
{"elevenlabs", "en", "pNInz6obpgDQGcFmaJgB", "Adam"},
|
|
|
|
|
{"elevenlabs", "en", "EXAVITQu4vr4xnSDxMaL", "Bella"},
|
|
|
|
|
{"elevenlabs", "en", "ErXwobaYiN019PkySvjV", "Antoni"},
|
|
|
|
|
{"elevenlabs", "en", "TxGEqnHWrfWFTfGW9XjX", "Josh"},
|
|
|
|
|
{"openai", "de", "alloy", "Alloy"},
|
|
|
|
|
{"openai", "de", "echo", "Echo"},
|
|
|
|
|
{"openai", "de", "fable", "Fable"},
|
|
|
|
|
{"openai", "de", "onyx", "Onyx"},
|
|
|
|
|
{"openai", "de", "nova", "Nova"},
|
|
|
|
|
{"openai", "de", "shimmer", "Shimmer"},
|
|
|
|
|
{"openai", "en", "alloy", "Alloy"},
|
|
|
|
|
{"openai", "en", "echo", "Echo"},
|
|
|
|
|
{"openai", "en", "fable", "Fable"},
|
|
|
|
|
{"openai", "en", "onyx", "Onyx"},
|
|
|
|
|
{"openai", "en", "nova", "Nova"},
|
|
|
|
|
{"openai", "en", "shimmer", "Shimmer"},
|
|
|
|
|
{"edge", "de", "de-DE-KatjaNeural", "Katja"},
|
|
|
|
|
{"edge", "de", "de-DE-ConradNeural", "Conrad"},
|
|
|
|
|
{"edge", "de", "de-DE-AmalaNeural", "Amala"},
|
|
|
|
|
{"edge", "de", "de-DE-KillianNeural", "Killian"},
|
|
|
|
|
{"edge", "de", "de-AT-IngridNeural", "Ingrid (AT)"},
|
|
|
|
|
{"edge", "de", "de-CH-LeniNeural", "Leni (CH)"},
|
|
|
|
|
{"edge", "en", "en-US-AriaNeural", "Aria"},
|
|
|
|
|
{"edge", "en", "en-US-GuyNeural", "Guy"},
|
|
|
|
|
{"edge", "en", "en-US-JennyNeural", "Jenny"},
|
|
|
|
|
{"edge", "en", "en-US-ChristopherNeural", "Christopher"},
|
|
|
|
|
{"edge", "en", "en-GB-SoniaNeural", "Sonia (GB)"},
|
|
|
|
|
{"edge", "en", "en-GB-RyanNeural", "Ryan (GB)"},
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-24 11:15:35 +02:00
|
|
|
// Catalog returns the voice catalog in stable order (chain order of the
|
|
|
|
|
// providers, then de before en, then catalog order).
|
2026-10-24 10:08:44 +02:00
|
|
|
func Catalog() []Voice {
|
|
|
|
|
out := make([]Voice, len(catalog))
|
|
|
|
|
copy(out, catalog)
|
|
|
|
|
rank := map[string]int{}
|
|
|
|
|
for i, name := range DefaultOrder {
|
|
|
|
|
rank[name] = i
|
|
|
|
|
}
|
|
|
|
|
sort.SliceStable(out, func(i, j int) bool {
|
|
|
|
|
if rank[out[i].Provider] != rank[out[j].Provider] {
|
|
|
|
|
return rank[out[i].Provider] < rank[out[j].Provider]
|
|
|
|
|
}
|
|
|
|
|
return out[i].Lang < out[j].Lang
|
|
|
|
|
})
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-24 11:15:35 +02:00
|
|
|
// VoiceName returns the catalog name of a voice ID; empty if unknown.
|
2026-10-24 10:08:44 +02:00
|
|
|
func VoiceName(provider, id string) string {
|
|
|
|
|
for _, v := range catalog {
|
|
|
|
|
if v.Provider == provider && v.ID == id {
|
|
|
|
|
return v.Name
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-24 11:15:35 +02:00
|
|
|
// DefaultVoice returns the effective default voice of a provider for a
|
|
|
|
|
// language: config mapping voice.<provider>.<lang> before the repo default
|
|
|
|
|
// (without a --voice pin). Empty for osbuiltin (the OS chooses).
|
2026-10-24 10:08:44 +02:00
|
|
|
func DefaultVoice(provider, lang string, cfgVoices map[string]map[string]string) string {
|
|
|
|
|
return resolveVoice(provider, lang, "", cfgVoices[provider])
|
|
|
|
|
}
|