SubtitleEdit/subtitleedit · error · InvalidOperationException
{detail} {stderr}
Error message
{detail} {stderr} What it means
InvalidOperationException thrown after the omnivoice-tts process exits when either the exit code is non-zero or the output WAV file does not exist. The message is built from NativeExitCodeHelper.Format (which translates known native loader exit codes like missing CUDA DLLs into actionable sentences) plus the trimmed stderr. The full diagnostic (voice, text, args, stderr, stdout) is logged to Tools log.
Source
Thrown at src/ui/Features/Video/TextToSpeech/Engines/OmniVoiceTtsCpp.cs:349
process.StandardInput.Close();
await process.WaitForExitAsync(cancellationToken);
var stderr = await stderrTask;
var stdout = await stdoutTask;
if (process.ExitCode != 0 || !File.Exists(outputFileName))
{
// A process killed by the Windows loader (e.g. missing CUDA runtime, issue #13196)
// never reaches main, so stderr is empty and the bare exit code is all the user sees.
// NativeExitCodeHelper turns the known status codes into an actionable sentence.
var detail = NativeExitCodeHelper.Format(Name, process.ExitCode, GetSetFolder());
var msg = $"{detail} - "
+ $"Voice: {omniVoice}, Text: {text}, "
+ $"Args: {string.Join(' ', psi.ArgumentList)}, "
+ $"StdErr: {stderr.Trim()}, StdOut: {stdout.Trim()}";
Se.LogError(msg);
Se.WriteToolsLog(msg);
throw new InvalidOperationException($"{detail} {stderr.Trim()}".TrimEnd());
}
return new TtsResult(outputFileName, text);
}
catch (OperationCanceledException)
{
// The omnivoice-tts process keeps running after the await is cancelled. Kill the
// process tree and remove the partial output file before propagating the cancel,
// so we don't leak CPU work or stale .wav files.
TryKill(process);
TryDelete(outputFileName);
throw;
}
finally
{
process.Dispose();
}
}View on GitHub (pinned to 17a9f07487)
Solutions
- Read the 'detail' portion of the message — NativeExitCodeHelper translates known exit codes (e.g., 0xC0000135 = missing DLL) into a specific remedy.
- If the detail mentions CUDA, install the CUDA runtime or switch to the CPU/Vulkan build of omnivoice-tts.
- If the detail mentions Vulkan, ensure vulkan-1.dll is on PATH (set OmniVoiceTtsCppVulkanPath in settings or install the Vulkan SDK).
- Check stderr in the message — it may contain the model-load error or tokenizer error.
- Verify the output folder is writable and has sufficient disk space.
- Re-download the model and codec files if they may be corrupt.
Example fix
// before — generic error with no structured exit-code guidance
if (process.ExitCode != 0 || !File.Exists(outputFileName))
throw new InvalidOperationException($"{detail} {stderr}".TrimEnd());
// after — separate loader failures (no stderr) from app failures (stderr present)
if (process.ExitCode != 0 || !File.Exists(outputFileName))
{
var hint = string.IsNullOrWhiteSpace(stderr)
? NativeExitCodeHelper.Format(Name, process.ExitCode, GetSetFolder())
: stderr.Trim();
throw new InvalidOperationException(
$"omnivoice-tts failed (exit {process.ExitCode}): {hint}");
} Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-flight: verify GPU/runtime dependencies and output folder writability
if (OperatingSystem.IsWindows() && !IsCudaRuntimeAvailable())
Se.WriteToolsLog("Warning: CUDA runtime not detected; omnivoice-tts may fail to start.");
if (!HasWriteAccess(Path.GetDirectoryName(outputFileName)))
throw new UnauthorizedAccessException("Cannot write to output folder."); Type guard
null
Try / catch
catch (InvalidOperationException ex)
{
// NativeExitCodeHelper.Format already decoded the exit code into 'detail'.
var detail = ex.Message; // starts with the formatted detail
if (detail.Contains("CUDA") || detail.Contains("DLL"))
await ShowRuntimeInstallPrompt();
else if (detail.Contains("model") || detail.Contains("codec"))
await OfferModelRedownload();
else throw;
} Prevention
- Install the CUDA runtime or use the CPU/Vulkan build of omnivoice-tts.
- Set OmniVoiceTtsCppVulkanPath in settings if using the Vulkan build on Windows.
- Verify model and codec files are not corrupt (re-download if exit code suggests load failure).
- Ensure the output folder is writable with sufficient disk space.
When it happens
Trigger: process.ExitCode != 0 OR !File.Exists(outputFileName) after WaitForExitAsync. The NativeExitCodeHelper comment references issue #13196: a process killed by the Windows loader (missing CUDA runtime) never reaches main, so stderr is empty and only the exit code is available.
Common situations: Missing CUDA runtime DLLs on Windows (the loader kills the process before main); missing Vulkan runtime (vulkan-1.dll not found despite the PATH augmentation in Speak); the model or codec file is corrupt and omnivoice-tts fails during loading; the input text contains characters that crash the tokenizer; the output folder is not writable so no WAV is produced; GPU OOM during inference.
Related errors
- Failed to start crispasr (omnivoice)
- crispasr (omnivoice) exited during startup (code {exitCode})
- Failed to start omnivoice-tts
- Failed to start crispasr (moss-tts)
- crispasr (moss-tts) exited during startup (code {exitCode}).
AI-assisted analysis of SubtitleEdit/subtitleedit@17a9f07487 (2026-08-13).
Data as JSON: /api/errors/f63bb0bdbd435516.
Report an issue: GitHub.