Skip to content

default_voice_code is ignored on the speech path in 0.8.1 — voices not named after a language code return 200 with empty audio #514

Closed

Description

@Cloud-Ops-Dev

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

  1. Christian-Sidak commented on Aug 27, 2026

    @Christian-Sidak
    Contributor

    Opened a fix in #515: adds settings.default_voice_code to the language-code resolution chain in _split_multi_voice, so voices whose filenames don't start with a recognised language prefix (like ursa) fall back to the configured default instead of producing empty audio.

  2. added a commit that references this issue on Oct 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions