feat: enhance TTS language detection with frequency scoring and explicit overrides
This commit is contained in:
parent
ce3ae79132
commit
46ef73bbae
@ -178,23 +178,39 @@ def _request_app_name(request: Request) -> str:
|
|||||||
# ── Language detection ────────────────────────────────────────────────────────
|
# ── Language detection ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def _detect_text_language(text: str) -> str:
|
def _detect_text_language(text: str) -> str:
|
||||||
low = f" {text.lower()} "
|
words = re.findall(r"\b[a-zäöüßéèêàçùœáíóúñąćęłńóśźż]+\b", text.lower())
|
||||||
if re.search(r"[äöüß]", low) or re.search(r"\b(und|der|die|das|ich|nicht|mit|ist|ein|eine|auf|für)\b", low):
|
if not words:
|
||||||
return "DE"
|
|
||||||
if re.search(r"[éèêàçùœ]", low) or re.search(r"\b(et|le|la|les|des|une|avec|pour|est|pas|que)\b", low):
|
|
||||||
return "FR"
|
|
||||||
if re.search(r"[áéíóúñ¿¡]", low) or re.search(r"\b(el|la|los|las|una|con|para|que|pero|está|hola)\b", low):
|
|
||||||
return "ES"
|
|
||||||
if re.search(r"\b(il|lo|gli|una|con|per|che|ciao|grazie|sono|della)\b", low):
|
|
||||||
return "IT"
|
|
||||||
if re.search(r"[ãõç]", low) or re.search(r"\b(com|para|uma|que|não|está|obrigado)\b", low):
|
|
||||||
return "PT"
|
|
||||||
if re.search(r"\b(het|een|niet|met|voor|zijn|maar|dank|goede)\b", low):
|
|
||||||
return "NL"
|
|
||||||
if re.search(r"[ąćęłńóśźż]", low) or re.search(r"\b(jest|nie|tak|dla|oraz|dzień|dziękuję)\b", low):
|
|
||||||
return "PL"
|
|
||||||
return "EN"
|
return "EN"
|
||||||
|
|
||||||
|
scores = {"EN": 0, "DE": 0, "FR": 0, "ES": 0, "IT": 0, "PT": 0, "NL": 0, "PL": 0}
|
||||||
|
|
||||||
|
if re.search(r"[äöüß]", text.lower()):
|
||||||
|
scores["DE"] += 3
|
||||||
|
if re.search(r"[ąćęłńóśźż]", text.lower()):
|
||||||
|
scores["PL"] += 3
|
||||||
|
if re.search(r"[ãõ]", text.lower()):
|
||||||
|
scores["PT"] += 3
|
||||||
|
if re.search(r"[ñ¿¡]", text.lower()):
|
||||||
|
scores["ES"] += 3
|
||||||
|
|
||||||
|
stopwords = {
|
||||||
|
"EN": {"the", "be", "to", "of", "and", "a", "in", "that", "have", "i", "it", "for", "not", "on", "with", "he", "as", "you", "do", "at", "this", "but", "his", "by", "from", "they", "we", "say", "her", "she", "or", "an", "will", "my", "one", "all", "would", "there", "their", "what", "so", "up", "out", "if", "about", "who", "get", "which", "go", "me", "when", "make", "can", "like", "time", "no", "just", "him", "know", "take", "people", "into", "year", "your", "good", "some", "could", "them", "see", "other", "than", "then", "now", "look", "only", "come", "its", "over", "think", "also", "back", "after", "use", "two", "how", "our", "work", "first", "well", "way", "even", "new", "want", "because", "any", "these", "give", "day", "most", "us", "hello", "hi", "yes", "thanks", "please"},
|
||||||
|
"DE": {"und", "der", "die", "das", "ich", "nicht", "mit", "ist", "ein", "eine", "auf", "für", "ja", "nein", "gut", "morgen", "hallo", "danke", "bitte", "wie", "was", "warum", "wer", "wo", "hier", "da", "dann", "wenn", "so", "nur", "auch", "aber", "oder", "als", "um", "zu", "von", "aus", "bei", "nach", "vor", "an", "im", "am", "über", "unter", "doch", "schon", "sehr", "viel", "mehr", "immer", "wieder", "heute", "jetzt", "machen", "tun", "sagen", "gehen", "kommen", "sehen", "wissen", "sind", "wir", "ihr", "sie", "ihnen", "mir", "mich", "dir", "dich", "uns", "euch"},
|
||||||
|
"FR": {"et", "le", "la", "les", "des", "une", "avec", "pour", "est", "pas", "que", "je", "tu", "il", "elle", "nous", "vous", "ils", "elles", "qui", "quoi", "quand", "où", "pourquoi", "comment", "oui", "non", "merci", "bonjour", "bien", "très", "tout", "plus", "moins", "dans", "sur", "sous", "devant", "derrière", "avant", "après", "ici", "là", "aujourd'hui", "maintenant", "faire", "dire", "aller", "venir", "voir", "savoir", "un", "en", "au", "aux", "ce", "ces", "se", "sa", "son", "ses"},
|
||||||
|
"ES": {"el", "la", "los", "las", "una", "con", "para", "que", "pero", "está", "hola", "y", "o", "no", "sí", "gracias", "por", "favor", "bien", "mal", "muy", "mucho", "poco", "más", "menos", "todo", "nada", "algo", "aquí", "allí", "ahora", "hoy", "mañana", "ayer", "siempre", "nunca", "hacer", "decir", "ir", "venir", "ver", "saber", "poder", "querer", "tener", "ser", "estar", "un", "en", "su", "sus", "te", "me", "se", "nos"},
|
||||||
|
"IT": {"il", "lo", "gli", "una", "con", "per", "che", "ciao", "grazie", "sono", "della", "e", "o", "non", "sì", "prego", "bene", "male", "molto", "poco", "più", "meno", "tutto", "niente", "qualcosa", "qui", "lì", "ora", "oggi", "domani", "ieri", "sempre", "mai", "fare", "dire", "andare", "venire", "vedere", "sapere", "potere", "volere", "avere", "essere", "un", "in", "su", "di", "da", "al", "ai", "mi", "ti", "si", "ci", "vi"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for word in words:
|
||||||
|
for lang, words_set in stopwords.items():
|
||||||
|
if word in words_set:
|
||||||
|
scores[lang] += 1
|
||||||
|
|
||||||
|
best_lang = max(scores, key=scores.get)
|
||||||
|
if scores[best_lang] == 0:
|
||||||
|
return "EN"
|
||||||
|
return best_lang
|
||||||
|
|
||||||
|
|
||||||
# ── Route matching ────────────────────────────────────────────────────────────
|
# ── Route matching ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@ -217,8 +233,8 @@ def _route_specificity(rule: dict, app: str, voice: str, lang: str) -> tuple[int
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _resolve_tts_route(app: str, voice: str, text: str) -> tuple[str, dict | None]:
|
def _resolve_tts_route(app: str, voice: str, text: str, explicit_lang: str = "") -> tuple[str, dict | None]:
|
||||||
lang = _detect_text_language(text)
|
lang = explicit_lang.upper() if explicit_lang else _detect_text_language(text)
|
||||||
best: tuple[tuple[int, int, int, int], dict] | None = None
|
best: tuple[tuple[int, int, int, int], dict] | None = None
|
||||||
for idx, rule in enumerate(_load_tts_routes()):
|
for idx, rule in enumerate(_load_tts_routes()):
|
||||||
spec = _route_specificity(rule, app, voice, lang)
|
spec = _route_specificity(rule, app, voice, lang)
|
||||||
|
|||||||
@ -741,9 +741,10 @@ async def tts_route_test(request: Request):
|
|||||||
text = str(data.get("input") or data.get("text") or "").strip()
|
text = str(data.get("input") or data.get("text") or "").strip()
|
||||||
voice = str(data.get("voice") or "default").strip()
|
voice = str(data.get("voice") or "default").strip()
|
||||||
app_name = _canonical_app_name(str(data.get("app") or data.get("client") or "Open WebUI").strip())
|
app_name = _canonical_app_name(str(data.get("app") or data.get("client") or "Open WebUI").strip())
|
||||||
|
explicit_lang = str(data.get("language") or data.get("lang") or "").strip()
|
||||||
if not text:
|
if not text:
|
||||||
raise HTTPException(400, "input is required")
|
raise HTTPException(400, "input is required")
|
||||||
routed_voice, route = _resolve_tts_route(app_name, voice, text)
|
routed_voice, route = _resolve_tts_route(app_name, voice, text, explicit_lang)
|
||||||
backend = _route_backend(route, routed_voice)
|
backend = _route_backend(route, routed_voice)
|
||||||
settings = _load_settings()
|
settings = _load_settings()
|
||||||
scan_dir = Path(settings.get("voices_scan_dir", _VOICES_DIR_DEFAULT))
|
scan_dir = Path(settings.get("voices_scan_dir", _VOICES_DIR_DEFAULT))
|
||||||
@ -872,7 +873,8 @@ async def openai_speech_proxy(request: Request):
|
|||||||
)
|
)
|
||||||
raise HTTPException(400, "voice is required")
|
raise HTTPException(400, "voice is required")
|
||||||
voice = fallback
|
voice = fallback
|
||||||
voice, route = _resolve_tts_route(request_app, voice, text)
|
explicit_lang = str(data.get("language") or data.get("lang") or "").strip()
|
||||||
|
voice, route = _resolve_tts_route(request_app, voice, text, explicit_lang)
|
||||||
backend = _route_backend(route, voice)
|
backend = _route_backend(route, voice)
|
||||||
style_instruction = str(data.get("instruct") or data.get("style_instruction") or "")
|
style_instruction = str(data.get("instruct") or data.get("style_instruction") or "")
|
||||||
virtual = _resolve_virtual_voice(voice)
|
virtual = _resolve_virtual_voice(voice)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user