{"record":{"id":"83e662fc42100451","repo":"SubtitleEdit/subtitleedit","slug":"qwen3-tts-crispasr-voice-cloning-requires-a-refe","errorCode":null,"errorMessage":"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.","messagePattern":"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\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/ui/Features/Video/TextToSpeech/Engines/Qwen3TtsCrispAsr.cs","lineNumber":750,"sourceCode":"        string? model,\n        CancellationToken cancellationToken)\n    {\n        if (voice.EngineVoice is not Qwen3TtsVoice qwen3Voice)\n        {\n            throw new ArgumentException(\"Voice is not a Qwen3TtsVoice\");\n        }\n\n        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","sourceCodeStart":732,"sourceCodeEnd":768,"githubUrl":"https://github.com/SubtitleEdit/subtitleedit/blob/17a9f0748781032255db3526b7215d2fb891e3af/src/ui/Features/Video/TextToSpeech/Engines/Qwen3TtsCrispAsr.cs#L732-L768","documentation":"InvalidOperationException thrown when the user selected the voice-clone (Base) model but the chosen Qwen3TtsVoice has no FilePath (no imported reference WAV). The check is done up-front so the user gets an actionable message instead of an opaque HTTP 500 'ref-text not set' from the crispasr server, which resolves the bare voice name against --voice-dir and needs the reference audio.","triggerScenarios":"Selecting the 'Clone' model while the voice combo holds only the built-in/default voice with no FilePath; the imported reference WAV was deleted from disk between import and synthesis; the voice was constructed without seeding a FilePath.","commonSituations":"User picks Clone before importing any reference voice; reference file was cleared by a cleanup tool; migration from an older config that didn't persist FilePath.","solutions":["Import a reference voice via the voice settings before selecting the Clone model.","Confirm the imported reference WAV still exists on disk and re-import if it was removed.","Gate the Clone model option in the UI on whether at least one voice with a FilePath exists.","If the voice list shows a clone voice but FilePath is empty, re-run GetSetVoicesFolder seeding."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"if (modelKey == ModelKeyClone && string.IsNullOrEmpty(qwen3Voice.FilePath))\n    throw new InvalidOperationException(\"Clone model requires an imported reference voice.\");","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Gate the Clone model option on at least one voice with a FilePath.","Persist and re-verify the imported reference WAV path on startup.","Prompt the user to import a reference before allowing Clone selection."],"tags":["tts","qwen3-tts","voice-cloning","validation","csharp"],"backgroundTag":null,"analyzedSha":"17a9f0748781032255db3526b7215d2fb891e3af","analyzedAt":"2026-08-13T18:11:43.374Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}