Repository navigation
Description
Summary
DEFAULT_VOICE_CODE / settings.default_voice_code no longer takes effect on the speech path. Any voice whose filename does not begin with a valid Kokoro language code returns HTTP 200 with a zero-byte body, and a voice whose first letter happens to be a different language's code is silently synthesized with that language's phonemizer.
This is the setting added for #153. kokoro_v1.py still honours it correctly; the request never gets that far.
Version: 0.8.1 (ghcr.io/remsky/kokoro-fastapi-gpu@sha256:5b7d4765…, /app/VERSION = 0.8.1)
Cause
api/src/services/tts_service.py, in _split_multi_voice():
# request lang_code wins, else each speaker gets the pipeline their prefix implies
segment_lang = lang_code if lang_code else segment_voice[:1].lower()settings.default_voice_code is not consulted. The computed segment_lang is then yielded as pipeline_lang_code and passed down as an explicit lang_code, so it wins the precedence chain in api/src/inference/kokoro_v1.py:
if lang_code: # <- now always set
pipeline_lang_code = lang_code
elif settings.default_voice_code: # <- unreachable from the speech path
pipeline_lang_code = settings.default_voice_code
else:
pipeline_lang_code = voice_name[0].lower()_split_multi_voice() runs on every request — with allow_voice_tags off it just yields one segment — so the setting is bypassed for all speech requests, not only multi-speaker ones.
Reproduction
docker run -d --name kokoro -p 8880:8880 \
-e ALLOW_LOCAL_VOICE_SAVING=true \
-e DEFAULT_VOICE_CODE=a \
ghcr.io/remsky/kokoro-fastapi-gpu:v0.8.1
# Give a good American voice a name that is not a language code:
docker exec kokoro cp /app/api/src/voices/v1_0/af_heart.pt \
/app/api/src/voices/v1_0/ursa.pt
curl -s -o out.wav -w '%{http_code} %{size_download}\n' \
-X POST http://localhost:8880/v1/audio/speech \
-H 'Content-Type: application/json' \
-d '{"model":"tts-1","voice":"ursa","input":"Testing.","response_format":"wav"}'Expected: 200 with audio, phonemized as a per DEFAULT_VOICE_CODE.
Actual: 200 0 — an empty body. af_heart under its own name works fine, and passing "lang_code":"a" explicitly in the same request also works.
Two more names showing the silent-wrong-language variant: eve.pt → first letter e → Spanish phonemizer; iris.pt → i → Italian. Both return real audio, so the misconfiguration is invisible unless you listen.
Impact
Custom voice names are the natural way to expose per-speaker voices to a client that sends a bare OpenAI voice string and cannot send lang_code — that is exactly our case (per-agent voices, one name per agent). DEFAULT_VOICE_CODE is the documented lever for it and currently does nothing. A 200 with an empty body reads as a client bug, which is what made this take a while to find.
Suggested fix
Mirror the kokoro_v1.py precedence in _split_multi_voice():
segment_lang = (
lang_code
or settings.default_voice_code
or segment_voice[:1].lower()
)settings is already imported in that module. Happy to open a PR if you'd like it.
A separate, smaller thought: an unrecognized language code resolving to an empty 200 is hard to debug regardless of this bug — a 400 naming the offending code would have made it obvious.
Activity
Christian-Sidak commentedon Aug 27, 2026
Opened a fix in #515: adds
settings.default_voice_codeto the language-code resolution chain in_split_multi_voice, so voices whose filenames don't start with a recognised language prefix (likeursa) fall back to the configured default instead of producing empty audio.fix: honour default_voice_code in _split_multi_voice (issue remsky#514)…
chore(release): 0.6.0 [skip ci]