SubtitleEdit/subtitleedit · error · InvalidOperationException

Qwen3 TTS (CrispASR) synthesis failed ({(int)response.Status

Error message

Qwen3 TTS (CrispASR) synthesis failed ({(int)response.StatusCode}): {errorBody}{Environment.NewLine}Server log:{Environment.NewLine}{serverLog}{LaunchCmdSuffix}

What it means

InvalidOperationException thrown when the crispasr /v1/audio/speech endpoint returns a non-success status. The response body is read via SafeReadErrorAsync and embedded, along with the captured server log and launch command. Detailed context (voice, text, request JSON, response body) is logged via Se.LogError before the throw.

Source

Thrown at src/ui/Features/Video/TextToSpeech/Engines/Qwen3TtsCrispAsr.cs:868

                + (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 = $"Qwen3 TTS (CrispASR) server error {(int)response.StatusCode} {response.StatusCode} — "
                    + $"Voice: {qwen3Voice}, Text: {text}, RequestJson: {body}, "
                    + $"ResponseBody: {errorBody}, ServerLog: {serverLog}"
                    + LaunchCmdSuffix(launchCommand);
                Se.LogError(errMsg);
                Se.WriteToolsLog(errMsg);
                throw new InvalidOperationException(
                    $"Qwen3 TTS (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 embedded errorBody — crispasr states the exact rejected field.
  2. For Clone, confirm `voice` is the extension-less stem (Path.GetFileNameWithoutExtension), never an absolute path.
  3. Match the language arg to a value the talker's codec_language_id supports, or omit it for auto-infer.
  4. Ensure an `instructions` field is present for VoiceDesign requests.

Example fix

// before — sending the full path trips the server's path-traversal guard
payload["voice"] = qwen3Voice.FilePath;

// after — send only the bare stem
payload["voice"] = Path.GetFileNameWithoutExtension(qwen3Voice.FilePath);
Defensive patterns

Strategy: validation

Validate before calling

// Ensure `voice` is a bare stem and an instruction is present for VoiceDesign.
if (modelKey == ModelKeyClone)
    payload["voice"] = Path.GetFileNameWithoutExtension(qwen3Voice.FilePath);
if (modelKey == ModelKeyVoiceDesign && !payload.ContainsKey("instructions"))
    payload["instructions"] = "a calm female voice";

Try / catch

try { response = await HttpClient.PostAsync(...); }
catch (InvalidOperationException ex) when (ex.Message.Contains("synthesis failed"))
{ Se.LogError(ex); throw; }

Prevention

When it happens

Trigger: HTTP 400 for an unknown voice name or a voice value containing path separators; HTTP 500 'ref-text not set' when the transcript sidecar is missing server-side; HTTP 422 when the language code isn't supported by the talker; HTTP 404 if the OpenAI-compatible route was renamed in a newer crispasr build.

Common situations: Sending an absolute path as `voice` instead of the bare stem; selecting a language the loaded talker doesn't support; a VoiceDesign request with no instruction (the code already sends a neutral default, but a fork may not); server build mismatch.

Related errors


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