{"record":{"id":"d1b92584fed84e0b","repo":"SubtitleEdit/subtitleedit","slug":"qwen3-tts-crispasr-synthesis-failed-int-respo","errorCode":null,"errorMessage":"Qwen3 TTS (CrispASR) synthesis failed ({(int)response.StatusCode}): {errorBody}{Environment.NewLine}Server log:{Environment.NewLine}{serverLog}{LaunchCmdSuffix}","messagePattern":"Qwen3 TTS \\(CrispASR\\) synthesis failed \\((.+?)\\): (.+?)(.+?)Server log:(.+?)(.+?)(.+?)","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/ui/Features/Video/TextToSpeech/Engines/Qwen3TtsCrispAsr.cs","lineNumber":868,"sourceCode":"                + (string.IsNullOrEmpty(serverLog) ? string.Empty : $\"{Environment.NewLine}Server log:{Environment.NewLine}{serverLog}\")\n                + LaunchCmdSuffix(launchCommand),\n                ex);\n        }\n\n        using (response)\n        {\n            if (!response.IsSuccessStatusCode)\n            {\n                var errorBody = await SafeReadErrorAsync(response, cancellationToken);\n                var serverLog = SnapshotServerLog();\n                var launchCommand = _serverLaunchCommand;\n                var errMsg = $\"Qwen3 TTS (CrispASR) server error {(int)response.StatusCode} {response.StatusCode} — \"\n                    + $\"Voice: {qwen3Voice}, Text: {text}, RequestJson: {body}, \"\n                    + $\"ResponseBody: {errorBody}, ServerLog: {serverLog}\"\n                    + LaunchCmdSuffix(launchCommand);\n                Se.LogError(errMsg);\n                Se.WriteToolsLog(errMsg);\n                throw new InvalidOperationException(\n                    $\"Qwen3 TTS (CrispASR) synthesis failed ({(int)response.StatusCode}): {errorBody}\"\n                    + (string.IsNullOrEmpty(serverLog) ? string.Empty : $\"{Environment.NewLine}Server log:{Environment.NewLine}{serverLog}\")\n                    + LaunchCmdSuffix(launchCommand));\n            }\n\n            await using var fileStream = File.Create(outputFileName);\n            await using var contentStream = await response.Content.ReadAsStreamAsync(cancellationToken);\n            await contentStream.CopyToAsync(fileStream, cancellationToken);\n        }\n\n        return new TtsResult(outputFileName, text);\n    }\n\n    private static string FormatLaunchCommand(string exe, System.Collections.ObjectModel.Collection<string> args)\n    {\n        static string Quote(string s) =>\n            !string.IsNullOrEmpty(s) && s.IndexOfAny(new[] { ' ', '\\t' }) >= 0\n                ? \"\\\"\" + s.Replace(\"\\\"\", \"\\\\\\\"\") + \"\\\"\"","sourceCodeStart":850,"sourceCodeEnd":886,"githubUrl":"https://github.com/SubtitleEdit/subtitleedit/blob/17a9f0748781032255db3526b7215d2fb891e3af/src/ui/Features/Video/TextToSpeech/Engines/Qwen3TtsCrispAsr.cs#L850-L886","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Read the embedded errorBody — crispasr states the exact rejected field.","For Clone, confirm `voice` is the extension-less stem (Path.GetFileNameWithoutExtension), never an absolute path.","Match the language arg to a value the talker's codec_language_id supports, or omit it for auto-infer.","Ensure an `instructions` field is present for VoiceDesign requests."],"exampleFix":"// before — sending the full path trips the server's path-traversal guard\npayload[\"voice\"] = qwen3Voice.FilePath;\n\n// after — send only the bare stem\npayload[\"voice\"] = Path.GetFileNameWithoutExtension(qwen3Voice.FilePath);","handlingStrategy":"validation","validationCode":"// Ensure `voice` is a bare stem and an instruction is present for VoiceDesign.\nif (modelKey == ModelKeyClone)\n    payload[\"voice\"] = Path.GetFileNameWithoutExtension(qwen3Voice.FilePath);\nif (modelKey == ModelKeyVoiceDesign && !payload.ContainsKey(\"instructions\"))\n    payload[\"instructions\"] = \"a calm female voice\";","typeGuard":null,"tryCatchPattern":"try { response = await HttpClient.PostAsync(...); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"synthesis failed\"))\n{ Se.LogError(ex); throw; }","preventionTips":["Always send the bare voice stem, never a path or extension.","Match the language arg to a codec-supported code or omit it.","Include an instruction for VoiceDesign requests."],"tags":["tts","crispasr","http-status","csharp"],"backgroundTag":null,"analyzedSha":"17a9f0748781032255db3526b7215d2fb891e3af","analyzedAt":"2026-08-13T18:11:43.374Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}