SubtitleEdit/subtitleedit · error · InvalidOperationException

Qwen3 TTS (CrispASR) voice cloning needs the spoken transcri

Error message

Qwen3 TTS (CrispASR) voice cloning needs the spoken transcription of '{qwen3Voice.Voice}'. Re-import the voice and enter the exact words spoken in the reference WAV.

What it means

InvalidOperationException thrown for the Clone model when the reference WAV exists but its sidecar transcript (the matching <name>.txt) is missing or unreadable. CrispASR auto-loads <name>.txt as the reference text; without it the server returns HTTP 500 'ref-text not set', so SE validates TryReadUsableTranscript up-front and asks the user to re-import with the transcript.

Source

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

        var modelKey = ResolveModelKey(model);

        // Voice clone (Base) needs a reference WAV plus its transcript. The backend resolves the
        // bare voice name against --voice-dir and auto-loads the matching <name>.txt as ref-text;
        // without that transcript it returns "ref-text not set" (HTTP 500). Surface both gaps
        // up-front with an actionable message instead of an opaque server error.
        if (modelKey == ModelKeyClone)
        {
            if (string.IsNullOrEmpty(qwen3Voice.FilePath))
            {
                throw new InvalidOperationException(
                    "Qwen3 TTS (CrispASR) voice cloning requires a reference voice. "
                    + "Import one via the voice settings (you'll be asked for its transcript), "
                    + "then pick it in the voice combo.");
            }

            if (string.IsNullOrWhiteSpace(TryReadUsableTranscript(qwen3Voice.FilePath)))
            {
                throw new InvalidOperationException(
                    $"Qwen3 TTS (CrispASR) voice cloning needs the spoken transcription of '{qwen3Voice.Voice}'. "
                    + "Re-import the voice and enter the exact words spoken in the reference WAV.");
            }
        }

        await EnsureServerRunningAsync(modelKey, cancellationToken);

        var outputFileName = Path.Combine(TtsOutputFolder.Resolve(outputFolder, GetSetFolder), Guid.NewGuid() + ".wav");
        var inputText = text;
        // Share the qwen3-tts.cpp instruction setting so users get the same voice description
        // regardless of which Qwen3 engine they're testing with.
        var instruction = Se.Settings.Video.TextToSpeech.Qwen3TtsCppInstruction ?? string.Empty;

        // OpenAI-compatible /v1/audio/speech payload. The crispasr backend interprets `voice`
        // differently per model (see crispasr_backend_qwen3_tts.cpp):
        //   - Voice clone (Base): `voice` is a BARE name (no path, no extension) resolved against
        //     --voice-dir to <name>.wav + the auto-loaded <name>.txt transcript. Sending the
        //     absolute path instead skips the sidecar load and fails — so send the basename only.

View on GitHub (pinned to 17a9f07487)

Solutions

  1. Re-import the reference voice through the voice settings and enter the exact words spoken in the WAV when prompted.
  2. If you prefer manual setup, create <voice-name>.txt next to <voice-name>.wav containing the verbatim transcript, then refresh voices.
  3. Verify the transcript file is non-empty and readable (TryReadUsableTranscript returns null for whitespace-only content).
  4. Ensure the .txt stem exactly matches the .wav stem, since the server resolves them by basename.
Defensive patterns

Strategy: validation

Validate before calling

if (modelKey == ModelKeyClone && string.IsNullOrWhiteSpace(TryReadUsableTranscript(qwen3Voice.FilePath)))
    throw new InvalidOperationException("Clone model requires a non-empty transcript sidecar.");

Prevention

When it happens

Trigger: The user imported the WAV but skipped/blanked the transcript prompt; the <name>.txt sidecar exists but is empty or whitespace; the sidecar is read-protected; the WAV was copied in manually without ever creating the transcript.

Common situations: Old imports done before the transcript step existed; manual file placement that bypassed the importer; transcript file truncated to zero bytes.

Related errors


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