SubtitleEdit/subtitleedit · error · InvalidOperationException

IndexTTS (CrispASR) synthesis failed ({(int)response.StatusC

Error message

IndexTTS (CrispASR) synthesis failed ({(int)response.StatusCode}): {errorBody}

What it means

InvalidOperationException from IndexTtsCrispAsr.Speak when the POST to `/v1/audio/speech` returns a non-success HTTP status code. The server stayed up and answered, but with an error (4xx/5xx). The numeric code, the response body (via SafeReadErrorAsync), the server log, and the launch command are all captured and logged via Se.LogError before throwing.

Source

Thrown at src/ui/Features/Video/TextToSpeech/Engines/IndexTtsCrispAsr.cs:446

                + (string.IsNullOrEmpty(serverLog) ? string.Empty : $"{Environment.NewLine}Server log:{Environment.NewLine}{serverLog}")
                + LaunchCmdSuffix(launchCommand),
                ex);
        }

        using (response)
        {
            if (!response.IsSuccessStatusCode)
            {
                var errorBody = await SafeReadErrorAsync(response, cancellationToken);
                var serverLog = SnapshotServerLog();
                var launchCommand = _serverLaunchCommand;
                var errMsg = $"IndexTTS (CrispASR) server error {(int)response.StatusCode} {response.StatusCode} — "
                    + $"Voice: {indexVoice}, Text: {text}, RequestJson: {body}, "
                    + $"ResponseBody: {errorBody}, ServerLog: {serverLog}"
                    + LaunchCmdSuffix(launchCommand);
                Se.LogError(errMsg);
                Se.WriteToolsLog(errMsg);
                throw new InvalidOperationException(
                    $"IndexTTS (CrispASR) synthesis failed ({(int)response.StatusCode}): {errorBody}"
                    + (string.IsNullOrEmpty(serverLog) ? string.Empty : $"{Environment.NewLine}Server log:{Environment.NewLine}{serverLog}")
                    + LaunchCmdSuffix(launchCommand));
            }

            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 string FormatLaunchCommand(string exe, System.Collections.ObjectModel.Collection<string> args)
    {
        static string Quote(string s) =>
            !string.IsNullOrEmpty(s) && s.IndexOfAny(new[] { ' ', '\t' }) >= 0
                ? "\"" + s.Replace("\"", "\\\"") + "\""

View on GitHub (pinned to 17a9f07487)

Solutions

  1. Read the `(status): errorBody` — the server's JSON error names the exact problem.
  2. HTTP 400 mentioning `voice`/path separators → ensure the payload omits the `voice` field (BuildSpeakPayload pattern).
  3. HTTP 500 → inspect the server log tail for the backend stack trace and retry; if reproducible, capture and report.
  4. Verify the CrispASR server version matches the payload schema the code expects.
  5. Confirm the `speed` value is within 0.25–4.0 (it is clamped, but a custom build may differ).
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate payload shape mirrors BuildSpeakPayload (no `voice` field)
Debug.Assert(!payload.ContainsKey("voice")); // path-traversal guard rejects it

Try / catch

try { await indexEngine.Speak(...); }
catch (InvalidOperationException ex) when (ex.Message.Contains("synthesis failed ("))
{
    var status = ExtractStatusCode(ex.Message);
    if (status == 400) WarnUser("Payload rejected by server — check voice field / schema.");
    else if (status >= 500) { await Task.Delay(500, ct); await indexEngine.Speak(...); }
    else ShowDiagnostics(ex.Message);
}

Prevention

When it happens

Trigger: HTTP 400 from sending a `voice` field with path separators (the path-traversal guard) — the code deliberately omits `voice` to avoid this, so a 400 here means a payload regression; 500 from an internal backend exception during inference; 422 from unsupported input; 503 if the server is overloaded.

Common situations: A code change reintroduced the `voice` field (#12757 family bug); backend crashes-then-reports instead of exiting; unsupported sample rate / speed clamp violation; server version drift where the payload schema changed.

Related errors


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