SubtitleEdit/subtitleedit · error · InvalidOperationException

Kokoro TTS synthesis failed ({(int)response.StatusCode}): {e

Error message

Kokoro TTS synthesis failed ({(int)response.StatusCode}): {errorBody}

What it means

InvalidOperationException from KokoroTtsCpp.Speak when the POST to `{ServerBaseUrl}/v1/synthesize` returns a non-success status. The numeric code and the response body (SafeReadErrorAsync) are embedded, and the full diagnostic (voice, text, body) is logged via Se.LogError. The server stays running; this is a per-request synthesis error.

Source

Thrown at src/ui/Features/Video/TextToSpeech/Engines/KokoroTtsCpp.cs:254

        await EnsureServerRunningAsync(cancellationToken);

        var outputFileName = Path.Combine(TtsOutputFolder.Resolve(outputFolder, GetSetFolder), Guid.NewGuid() + ".wav");
        var inputText = text;
        var voiceName = string.IsNullOrEmpty(kokoroVoice.Voice) ? DefaultVoice : kokoroVoice.Voice;

        var body = JsonSerializer.Serialize(new { text = inputText, voice = voiceName });
        using var content = new StringContent(body, Encoding.UTF8, "application/json");
        Se.WriteToolsLog($"Kokoro TTS: POST {ServerBaseUrl}/v1/synthesize (voice={voiceName}, textLen={text.Length})");
        using var response = await HttpClient.PostAsync($"{ServerBaseUrl}/v1/synthesize", content, cancellationToken);

        if (!response.IsSuccessStatusCode)
        {
            var errorBody = await SafeReadErrorAsync(response, cancellationToken);
            var errMsg = $"Kokoro TTS server error {(int)response.StatusCode} {response.StatusCode} - "
                + $"Voice: {voiceName}, Text: {text}, Body: {errorBody}";
            Se.LogError(errMsg);
            Se.WriteToolsLog(errMsg);
            throw new InvalidOperationException(
                $"Kokoro TTS synthesis failed ({(int)response.StatusCode}): {errorBody}");
        }

        await using (var fileStream = File.Create(outputFileName))
        await using (var contentStream = await response.Content.ReadAsStreamAsync(cancellationToken))
        {
            await contentStream.CopyToAsync(fileStream, cancellationToken);
        }

        return new TtsResult(outputFileName, text);
    }

    private static async Task<string> SafeReadErrorAsync(HttpResponseMessage response, CancellationToken ct)
    {
        try
        {
            return await response.Content.ReadAsStringAsync(ct);
        }

View on GitHub (pinned to 17a9f07487)

Solutions

  1. Read the `(status): errorBody` — kokoro's server returns a JSON error message.
  2. HTTP 400 on voice → confirm the voice name exists in the voices model (call GetVoices first).
  3. Ensure input text is non-empty after trimming.
  4. Verify the kokoro-tts-server version matches the `/v1/synthesize` contract the code expects.
  5. If the DefaultVoice constant is stale, update it to a voice present in the shipped voices file.
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate voice name against the loaded voices model before POST
var valid = (await kokoroEngine.GetVoices(language)).Select(v => v.Name).ToHashSet();
if (!valid.Contains(voiceName)) { WarnUser("Voice not in voices file"); return; }

Try / catch

try { await kokoroEngine.Speak(...); }
catch (InvalidOperationException ex) when (ex.Message.Contains("Kokoro TTS synthesis failed"))
{
    var status = ExtractStatusCode(ex.Message);
    if (status == 400) WarnUser("Bad voice/text — check voices file and non-empty text.");
    else if (status >= 500) { await Task.Delay(500, ct); await kokoroEngine.Speak(...); }
}

Prevention

When it happens

Trigger: HTTP 400 from an unknown/empty voice name; 500 from an internal kokoro inference error; 422 from input the model can't handle (empty text, unsupported characters); the voices file passed at startup doesn't contain the requested voice.

Common situations: Voice name in the request not present in the loaded voices model file; text is empty or only whitespace; server version drift changed the `/v1/synthesize` schema; the DefaultVoice fallback fired but isn't in the voices file.

Related errors


AI-assisted analysis of SubtitleEdit/subtitleedit@17a9f07487 (2026-08-13). Data as JSON: /api/errors/9da59bbade8b58a1. Report an issue: GitHub.