SubtitleEdit/subtitleedit · error · InvalidOperationException

Qwen3 TTS request failed: {ex.Message}{ServerOutput}

Error message

Qwen3 TTS request failed: {ex.Message}{ServerOutput}

What it means

Thrown by Qwen3TtsCpp.Speak when the HTTP POST to the local qwen3-tts-server (/v1/synthesize or /v1/synthesize_with_voice) raises an exception other than OperationCanceledException. The catch wraps the raw exception in an InvalidOperationException and appends the server's captured stdout/stderr so that a server crash (which surfaces as a bare connection error) is diagnosable.

Source

Thrown at src/ui/Features/Video/TextToSpeech/Engines/Qwen3TtsCpp.cs:262

        Se.WriteToolsLog($"Qwen3 TTS: POST {ServerBaseUrl}{endpoint} (voice={qwen3Voice}, model={modelFileName}, textLen={text.Length}, instructionLen={instruction.Length})");

        HttpResponseMessage response;
        try
        {
            response = string.IsNullOrEmpty(qwen3Voice.FilePath)
                ? await SynthesizeAsync(inputText, instruction, cancellationToken)
                : await SynthesizeWithVoiceAsync(inputText, qwen3Voice.FilePath, instruction, cancellationToken);
        }
        catch (Exception ex) when (ex is not OperationCanceledException)
        {
            // A crashed/exited server surfaces here as a bare connection error - attach whatever
            // the server printed (model-load / synthesis failures land in its stdout/stderr).
            var serverOutput = SnapshotServerStderr();
            var msg = $"Qwen3 TTS request failed: {ex.Message}"
                + (serverOutput.Length == 0 ? string.Empty : $"{Environment.NewLine}Server output:{Environment.NewLine}{serverOutput}");
            Se.LogError(ex, msg);
            Se.WriteToolsLog(msg);
            throw new InvalidOperationException(msg, ex);
        }

        using (response)
        {
            if (!response.IsSuccessStatusCode)
            {
                var errorBody = await SafeReadErrorAsync(response, cancellationToken);
                var serverOutput = SnapshotServerStderr();
                var errMsg = $"Qwen3 TTS server error {(int)response.StatusCode} {response.StatusCode} - "
                    + $"Voice: {qwen3Voice}, Text: {text}, Body: {errorBody}"
                    + (serverOutput.Length == 0 ? string.Empty : $"{Environment.NewLine}Server output:{Environment.NewLine}{serverOutput}");
                Se.LogError(errMsg);
                Se.WriteToolsLog(errMsg);
                throw new InvalidOperationException(
                    $"Qwen3 TTS synthesis failed ({(int)response.StatusCode}): {errorBody}");
            }

            await using var fileStream = File.Create(outputFileName);

View on GitHub (pinned to 17a9f07487)

Solutions

  1. Read the appended Server output in the exception message — it usually names the real cause (CUDA/Vulkan error, model-load failure, assertion).
  2. Confirm the server is still alive: check ToolsLog for the PID line and whether a later 'exited during startup' / 'did not report healthy' followed.
  3. Reduce model size or instruction length if the server log shows an out-of-memory error, then retry synthesis.
  4. If the reference WAV is involved, verify qwen3Voice.FilePath exists and is readable before the call.

Example fix

// before
response = await SynthesizeWithVoiceAsync(inputText, qwen3Voice.FilePath, instruction, cancellationToken);

// after — guard against a vanished reference file before the request
if (!File.Exists(qwen3Voice.FilePath))
    throw new InvalidOperationException($"Reference voice missing: {qwen3Voice.FilePath}");
response = await SynthesizeWithVoiceAsync(inputText, qwen3Voice.FilePath, instruction, cancellationToken);
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate the reference voice and server liveness before the request.
if (!string.IsNullOrEmpty(qwen3Voice.FilePath) && !File.Exists(qwen3Voice.FilePath))
    throw new FileNotFoundException("Reference voice missing", qwen3Voice.FilePath);
if (_serverProcess is not { HasExited: false })
    throw new InvalidOperationException("Qwen3 TTS server is not running.");

Try / catch

try
{
    response = string.IsNullOrEmpty(qwen3Voice.FilePath)
        ? await SynthesizeAsync(inputText, instruction, ct)
        : await SynthesizeWithVoiceAsync(inputText, qwen3Voice.FilePath, instruction, ct);
}
catch (OperationCanceledException) { throw; }
catch (Exception ex)
{
    var serverOutput = SnapshotServerStderr();
    throw new InvalidOperationException($"Qwen3 TTS request failed: {ex.Message}" +
        (serverOutput.Length == 0 ? string.Empty : $"{Environment.NewLine}Server output:{Environment.NewLine}{serverOutput}"), ex);
}

Prevention

When it happens

Trigger: The server process exited/crashed between health-check and the request (HttpRequestException: connection refused), the loopback port was reclaimed by another process, the request body was malformed and the server reset the connection, or the HttpClient timed out while the server was loading the model mid-request.

Common situations: GPU/Vulkan OOM during synthesis kills the server; the user switched models so EnsureServerRunningAsync is restarting the server while a queued Speak fires; antivirus/firewall interferes with the loopback socket; the reference WAV path in qwen3Voice.FilePath is inaccessible so the multipart upload stream faults.

Related errors


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