{"record":{"id":"396c323c05643576","repo":"SubtitleEdit/subtitleedit","slug":"qwen3-tts-crispasr-voice-cloning-needs-the-spoke","errorCode":null,"errorMessage":"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.","messagePattern":"Qwen3 TTS \\(CrispASR\\) voice cloning needs the spoken transcription of '(.+?)'\\. Re-import the voice and enter the exact words spoken in the reference WAV\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/ui/Features/Video/TextToSpeech/Engines/Qwen3TtsCrispAsr.cs","lineNumber":758,"sourceCode":"        var modelKey = ResolveModelKey(model);\n\n        // Voice clone (Base) needs a reference WAV plus its transcript. The backend resolves the\n        // bare voice name against --voice-dir and auto-loads the matching <name>.txt as ref-text;\n        // without that transcript it returns \"ref-text not set\" (HTTP 500). Surface both gaps\n        // up-front with an actionable message instead of an opaque server error.\n        if (modelKey == ModelKeyClone)\n        {\n            if (string.IsNullOrEmpty(qwen3Voice.FilePath))\n            {\n                throw new InvalidOperationException(\n                    \"Qwen3 TTS (CrispASR) voice cloning requires a reference voice. \"\n                    + \"Import one via the voice settings (you'll be asked for its transcript), \"\n                    + \"then pick it in the voice combo.\");\n            }\n\n            if (string.IsNullOrWhiteSpace(TryReadUsableTranscript(qwen3Voice.FilePath)))\n            {\n                throw new InvalidOperationException(\n                    $\"Qwen3 TTS (CrispASR) voice cloning needs the spoken transcription of '{qwen3Voice.Voice}'. \"\n                    + \"Re-import the voice and enter the exact words spoken in the reference WAV.\");\n            }\n        }\n\n        await EnsureServerRunningAsync(modelKey, cancellationToken);\n\n        var outputFileName = Path.Combine(TtsOutputFolder.Resolve(outputFolder, GetSetFolder), Guid.NewGuid() + \".wav\");\n        var inputText = text;\n        // Share the qwen3-tts.cpp instruction setting so users get the same voice description\n        // regardless of which Qwen3 engine they're testing with.\n        var instruction = Se.Settings.Video.TextToSpeech.Qwen3TtsCppInstruction ?? string.Empty;\n\n        // OpenAI-compatible /v1/audio/speech payload. The crispasr backend interprets `voice`\n        // differently per model (see crispasr_backend_qwen3_tts.cpp):\n        //   - Voice clone (Base): `voice` is a BARE name (no path, no extension) resolved against\n        //     --voice-dir to <name>.wav + the auto-loaded <name>.txt transcript. Sending the\n        //     absolute path instead skips the sidecar load and fails — so send the basename only.","sourceCodeStart":740,"sourceCodeEnd":776,"githubUrl":"https://github.com/SubtitleEdit/subtitleedit/blob/17a9f0748781032255db3526b7215d2fb891e3af/src/ui/Features/Video/TextToSpeech/Engines/Qwen3TtsCrispAsr.cs#L740-L776","documentation":"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.","triggerScenarios":"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.","commonSituations":"Old imports done before the transcript step existed; manual file placement that bypassed the importer; transcript file truncated to zero bytes.","solutions":["Re-import the reference voice through the voice settings and enter the exact words spoken in the WAV when prompted.","If you prefer manual setup, create <voice-name>.txt next to <voice-name>.wav containing the verbatim transcript, then refresh voices.","Verify the transcript file is non-empty and readable (TryReadUsableTranscript returns null for whitespace-only content).","Ensure the .txt stem exactly matches the .wav stem, since the server resolves them by basename."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"if (modelKey == ModelKeyClone && string.IsNullOrWhiteSpace(TryReadUsableTranscript(qwen3Voice.FilePath)))\n    throw new InvalidOperationException(\"Clone model requires a non-empty transcript sidecar.\");","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always import via the settings flow so the transcript prompt is mandatory.","Keep the <name>.txt stem in sync with the <name>.wav stem.","Validate the sidecar is non-empty/non-whitespace before synthesis."],"tags":["tts","qwen3-tts","voice-cloning","transcript","validation","csharp"],"backgroundTag":null,"analyzedSha":"17a9f0748781032255db3526b7215d2fb891e3af","analyzedAt":"2026-08-13T18:11:43.374Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}