{"record":{"id":"c4113de403fe4b13","repo":"SubtitleEdit/subtitleedit","slug":"omnivoice-tts-voice-cloning-requires-a-transcript","errorCode":null,"errorMessage":"OmniVoice TTS voice cloning requires a transcript file at {refTextPath}. Re-import the voice to provide its transcript.","messagePattern":"OmniVoice TTS voice cloning requires a transcript file at (.+?)\\. Re-import the voice to provide its transcript\\.","errorType":"exception","errorClass":"FileNotFoundException","httpStatus":null,"severity":"error","filePath":"src/ui/Features/Video/TextToSpeech/Engines/OmniVoiceTtsCpp.cs","lineNumber":296,"sourceCode":"        // auto-detect, not \"language not found\".\n        if (language != null && !string.IsNullOrEmpty(language.Code))\n        {\n            psi.ArgumentList.Add(\"--lang\");\n            psi.ArgumentList.Add(language.Code);\n        }\n\n        // Voice cloning: omnivoice-tts requires both --ref-wav and --ref-text.\n        // Pair each voices/<name>.wav with a sibling <name>.txt holding the transcript.\n        // The transcript is captured at import time; if it has been deleted out from under\n        // us we fail loudly so the user knows custom voice cloning isn't happening, rather\n        // than silently producing the default speaker.\n        var usingReference = !string.IsNullOrEmpty(omniVoice.FilePath) && File.Exists(omniVoice.FilePath);\n        if (usingReference)\n        {\n            var refTextPath = Path.ChangeExtension(omniVoice.FilePath, \".txt\");\n            if (!File.Exists(refTextPath))\n            {\n                throw new FileNotFoundException(\n                    $\"OmniVoice TTS voice cloning requires a transcript file at {refTextPath}. \"\n                    + \"Re-import the voice to provide its transcript.\",\n                    refTextPath);\n            }\n\n            psi.ArgumentList.Add(\"--ref-wav\");\n            psi.ArgumentList.Add(omniVoice.FilePath);\n            psi.ArgumentList.Add(\"--ref-text\");\n            psi.ArgumentList.Add(refTextPath);\n        }\n\n        // Voice-design keywords (--instruct) only shape the voice when there is no reference\n        // audio - with --ref-wav omnivoice-tts takes the voice from the clone and ignores them.\n        if (!usingReference)\n        {\n            var instruction = (Se.Settings.Video.TextToSpeech.OmniVoiceTtsCppInstruction ?? string.Empty).Trim();\n            if (!string.IsNullOrEmpty(instruction))\n            {","sourceCodeStart":278,"sourceCodeEnd":314,"githubUrl":"https://github.com/SubtitleEdit/subtitleedit/blob/17a9f0748781032255db3526b7215d2fb891e3af/src/ui/Features/Video/TextToSpeech/Engines/OmniVoiceTtsCpp.cs#L278-L314","documentation":"FileNotFoundException thrown when a cloned voice (omniVoice.FilePath points to an existing WAV) is selected but the sibling .txt transcript file (same basename, .txt extension) does not exist. omnivoice-tts requires --ref-text whenever --ref-wav is set, so SE fails loudly rather than silently producing the default speaker voice.","triggerScenarios":"usingReference is true (omniVoice.FilePath is non-empty and the WAV exists), but Path.ChangeExtension(omniVoice.FilePath, \".txt\") does not exist. This happens when the transcript sidecar was manually deleted, or the voice was copied/imported without its transcript.","commonSituations":"The user deleted the .txt file next to a voice WAV to save space; the voice was imported via a path that didn't carry the transcript (ImportVoice with a null transcript and no source sidecar); a file-sync tool excluded .txt files but synced .wav files; the voice was seeded from another engine's folder but the sidecar filter rejected the text as an unusable attribution blurb.","solutions":["Re-import the voice via the Import Voice dialog and provide the transcript text — it writes the .txt sidecar automatically.","Manually create the .txt file next to the voice WAV with the spoken transcription of the reference audio.","If the transcript is unknown, use OmniVoiceCrispAsr.TryWriteRefTextSidecar(voiceWavPath, transcript) to write it after import.","Alternatively, select the 'Default' voice (no FilePath) which does not require a transcript."],"exampleFix":"// before — throws if the sidecar was deleted\nvar refTextPath = Path.ChangeExtension(omniVoice.FilePath, \".txt\");\nif (!File.Exists(refTextPath))\n    throw new FileNotFoundException(\"...requires a transcript file at \" + refTextPath);\n\n// after — fall back to the default voice with a warning if transcript is missing\nvar refTextPath = Path.ChangeExtension(omniVoice.FilePath, \".txt\");\nif (usingReference && !File.Exists(refTextPath))\n{\n    Se.WriteToolsLog($\"Transcript missing for {omniVoice.FilePath}; falling back to default voice.\");\n    usingReference = false;\n}","handlingStrategy":"validation","validationCode":"// Before synthesis, check for the transcript sidecar\nif (!string.IsNullOrEmpty(omniVoice.FilePath) && File.Exists(omniVoice.FilePath))\n{\n    var refTextPath = Path.ChangeExtension(omniVoice.FilePath, \".txt\");\n    if (!File.Exists(refTextPath))\n    {\n        await PromptForTranscript(omniVoice.FilePath);\n        return;\n    }\n}","typeGuard":"null","tryCatchPattern":"catch (FileNotFoundException ex) when (ex.Message.Contains(\"transcript file\"))\n{\n    var path = ex.FileName; // the missing .txt path\n    await PromptForTranscript(Path.ChangeExtension(path, \".wav\"));\n    // After providing transcript, retry\n}","preventionTips":["Always provide a transcript when importing a voice (ImportVoice(fileName, transcript)).","Do not delete .txt sidecars next to voice WAVs.","Use the 'Default' voice if no transcript is available."],"tags":["file-not-found","tts","omnivoice","voice-cloning","csharp"],"backgroundTag":null,"analyzedSha":"17a9f0748781032255db3526b7215d2fb891e3af","analyzedAt":"2026-08-13T18:11:43.374Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}