SubtitleEdit/subtitleedit · error · InvalidOperationException

Failed to start omnivoice-tts

Error message

Failed to start omnivoice-tts

What it means

InvalidOperationException thrown if Process.Start(psi) returns null when launching the omnivoice-tts CLI for a single synthesis. As with errors 243 and 250, this null path is practically unreachable with UseShellExecute=false; the realistic failure is a Win32Exception when the binary cannot execute.

Solutions

  1. Verify ProcessStartInfo.UseShellExecute is false.
  2. If the binary genuinely won't start, the real error is a Win32Exception — wrap Process.Start in try/catch(Win32Exception) for a useful message.
  3. Confirm the executable is not blocked (Windows Mark-of-the-Web) or quarantined.
  4. Check execute permissions on Linux/macOS (chmod +x).

Example fix

// before
var process = Process.Start(psi)
    ?? throw new InvalidOperationException("Failed to start omnivoice-tts");

// after — catch the real failure mode
try { var process = Process.Start(psi); }
catch (Win32Exception ex) when (ex.NativeErrorCode == 5)
{
    throw new InvalidOperationException("omnivoice-tts is not executable. Run chmod +x.", ex);
}
catch (Win32Exception ex)
{
    throw new InvalidOperationException($"Failed to start omnivoice-tts: {ex.Message}", ex);
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Verify binary exists and is executable
var exe = GetExecutableFileName();
if (!File.Exists(exe)) throw new FileNotFoundException("Missing: " + exe, exe);

Type guard

null

Try / catch

catch (InvalidOperationException ex) when (ex.Message == "Failed to start omnivoice-tts")
{
    Se.LogError(ex, "Process.Start returned null unexpectedly.");
    throw;
}

Prevention

When it happens

Trigger: Process.Start(psi) returns null. With UseShellExecute=false, Process.Start throws rather than returning null. This guard is defensive dead code under the current configuration.

Common situations: Unreachable in production. If observed, check whether UseShellExecute was changed, or whether a framework-level issue caused a null return.

Related errors


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

Appendix: source

Thrown at src/ui/Features/Video/TextToSpeech/Engines/OmniVoiceTtsCpp.cs:323

            psi.ArgumentList.Add(refTextPath);
        }

        // Voice-design keywords (--instruct) only shape the voice when there is no reference
        // audio - with --ref-wav omnivoice-tts takes the voice from the clone and ignores them.
        if (!usingReference)
        {
            var instruction = (Se.Settings.Video.TextToSpeech.OmniVoiceTtsCppInstruction ?? string.Empty).Trim();
            if (!string.IsNullOrEmpty(instruction))
            {
                psi.ArgumentList.Add("--instruct");
                psi.ArgumentList.Add(instruction);
            }
        }

        Se.WriteToolsLog($"OmniVoice TTS: {exe} {string.Join(' ', psi.ArgumentList)} (voice={omniVoice}, textLen={text.Length})");

        var process = Process.Start(psi)
            ?? throw new InvalidOperationException("Failed to start omnivoice-tts");

        try
        {
            var stderrTask = process.StandardError.ReadToEndAsync(cancellationToken);
            var stdoutTask = process.StandardOutput.ReadToEndAsync(cancellationToken);

            await process.StandardInput.WriteAsync(inputText.AsMemory(), cancellationToken);
            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.

View on GitHub (pinned to 17a9f07487)