SubtitleEdit/subtitleedit · error · InvalidOperationException

OmniVoice (CrispASR) synthesis failed ({(int)response.Status

Error message

OmniVoice (CrispASR) synthesis failed ({(int)response.StatusCode}): {errorBody}{ServerLog}{LaunchCmdSuffix}

What it means

Thrown when the crispasr omnivoice server returns an HTTP response with a non-success status code from POST /v1/audio/speech. The response body is captured via SafeReadErrorAsync, and the message includes the numeric status code, the error body, the server log snapshot, and the launch command suffix.

Source

Thrown at src/ui/Features/Video/TextToSpeech/Engines/OmniVoiceCrispAsr.cs:491

                + (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 = $"OmniVoice (CrispASR) server error {(int)response.StatusCode} {response.StatusCode} — "
                    + $"Voice: {omniVoice}, Text: {text}, RequestJson: {body}, "
                    + $"ResponseBody: {errorBody}, ServerLog: {serverLog}"
                    + LaunchCmdSuffix(launchCommand);
                Se.LogError(errMsg);
                Se.WriteToolsLog(errMsg);
                throw new InvalidOperationException(
                    $"OmniVoice (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 TryReadRefText(string wavPath)
    {
        try
        {
            var sidecar = Path.ChangeExtension(wavPath, ".txt");

View on GitHub (pinned to 17a9f07487)

Solutions

  1. Read the errorBody in the exception — it is the server's structured error and identifies the exact field or condition that failed.
  2. If the error is language-related, verify the crispasr binary is v0.8.26 or newer (check the .installed.sha256 sidecar) — the download service pins v0.8.28.
  3. If cloning-related, re-import the reference WAV ensuring it is resampled to 24 kHz mono (the import path does this automatically, but a manually-copied file may not).
  4. Reset the speed setting to 1.0 in SE settings if the value was manually edited.
  5. Check the server log for stack traces that accompany 500 responses.

Example fix

// before — language sent unconditionally
if (!string.IsNullOrEmpty(languageArg))
    payload["language"] = languageArg;

// after — guard against versions that reject unknown codes
var version = CrispAsrDownloadService.GetInstalledVersion();
if (!string.IsNullOrEmpty(languageArg) && version >= new Version(0, 8, 26))
    payload["language"] = languageArg;
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate language support and input before the request
if (string.IsNullOrWhiteSpace(text))
    throw new ArgumentException("Input text must not be empty.");
var version = CrispAsrDownloadService.GetInstalledVersion();
if (!string.IsNullOrEmpty(languageArg) && version < new Version(0, 8, 26))
    Se.WriteToolsLog("Warning: language field requires crispasr v0.8.26+; current: " + version);

Type guard

null

Try / catch

catch (InvalidOperationException ex) when (ex.Message.Contains("synthesis failed ("))
{
    var code = ExtractStatusCode(ex.Message);
    if (code == 400 && ex.Message.Contains("language"))
    { /* strip the language field and retry, or warn the user */ }
    else throw;
}

Prevention

When it happens

Trigger: The HTTP call completes but response.IsSuccessStatusCode is false. For omnivoice, common causes: 400 for an unsupported language code (the per-request 'language' field requires v0.8.26+); 400 for empty input text; 500 when the backend's inference fails internally; 422 for a speed value the server rejects.

Common situations: Sending a language code that the installed crispasr version doesn't recognize (pre-v0.8.26 ignores it, but a version between v0.8.26 and the current pin may validate and reject); the speed setting was manually edited in settings to an out-of-range value; the voice WAV path contains characters the server's file loader rejects; a reference WAV that is stereo or wrong sample rate causes a codec error returned as 500.

Related errors


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