SubtitleEdit/subtitleedit · critical · InvalidOperationException

Failed to start crispasr (qwen3-tts)

Error message

Failed to start crispasr (qwen3-tts)

What it means

Defensive InvalidOperationException thrown when Process.Start(psi) returns null while launching crispasr. As with the qwen3-tts equivalent, UseShellExecute is false here so Start should throw rather than return null in normal operation; this guard catches a runtime/platform quirk that yields null instead.

Source

Thrown at src/ui/Features/Video/TextToSpeech/Engines/Qwen3TtsCrispAsr.cs:986

                psi.ArgumentList.Add(codec);
            }
            if (!hasLocalTalker || !hasLocalCodec)
            {
                psi.ArgumentList.Add("--auto-download");
            }
            psi.ArgumentList.Add("--host");
            psi.ArgumentList.Add("127.0.0.1");
            psi.ArgumentList.Add("--port");
            psi.ArgumentList.Add(port.ToString());
            // The crispasr server gates /v1/audio/speech requests with a `voice` field behind
            // --voice-dir being set. Pointing at our voices folder satisfies the gate and also
            // makes /v1/voices reflect any imported reference WAVs.
            psi.ArgumentList.Add("--voice-dir");
            psi.ArgumentList.Add(GetSetVoicesFolder());
            CrispAsrTtsProvenance.AddServerMarkingArgs(psi.ArgumentList, exe);

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

            var launchCommand = FormatLaunchCommand(exe, psi.ArgumentList);
            _serverLaunchCommand = launchCommand;
            Se.WriteToolsLog("Qwen3 TTS (CrispASR) server starting — "
                + $"PID: {process.Id}, "
                + $"Cmd: {launchCommand}");

            lock (_serverLog) _serverLog.Clear();
            process.ErrorDataReceived += (_, e) =>
            {
                if (e.Data != null) lock (_serverLog) _serverLog.AppendLine(e.Data);
            };
            process.OutputDataReceived += (_, e) =>
            {
                if (e.Data != null) lock (_serverLog) _serverLog.AppendLine(e.Data);
            };
            process.BeginErrorReadLine();
            process.BeginOutputReadLine();

View on GitHub (pinned to 17a9f07487)

Solutions

  1. Confirm UseShellExecute stays false in the launch path.
  2. Reproduce the ProcessStartInfo in a standalone harness to see whether Start returns null or throws a real exception.
  3. If isolated to a sandbox, relax its process-creation policy.
  4. Treat as a defect on normal desktops — file a bug; Start is expected to throw, not return null.
Defensive patterns

Strategy: validation

Validate before calling

if (psi.UseShellExecute)
    throw new InvalidOperationException("UseShellExecute must be false.");
if (string.IsNullOrWhiteSpace(psi.FileName))
    throw new InvalidOperationException("Executable path is empty.");

Try / catch

try { var process = Process.Start(psi) ?? throw new InvalidOperationException("Failed to start crispasr (qwen3-tts)"); }
catch (System.ComponentModel.Win32Exception ex) { Se.LogError(ex); throw; }

Prevention

When it happens

Trigger: A future change sets UseShellExecute=true; a host runtime that returns null for an empty/invalid argument list instead of throwing; an exotic sandboxed environment that partially virtualises process creation.

Common situations: Code regression flipping UseShellExecute; unusual container/jail setups; effectively unreachable on normal desktop runtimes.

Related errors


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