{"record":{"id":"b29822d60826c25c","repo":"SubtitleEdit/subtitleedit","slug":"voice-is-not-an-f5ttsvoice","errorCode":null,"errorMessage":"Voice is not an F5TtsVoice","messagePattern":"Voice is not an F5TtsVoice","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/ui/Features/Video/TextToSpeech/Engines/F5TtsCrispAsr.cs","lineNumber":363,"sourceCode":"    public Task<string[]> GetModels() => Task.FromResult(new[] { ModelKeyF16 });\n\n    public Task<TtsLanguage[]> GetLanguages(Voice voice, string? model) => Task.FromResult(Array.Empty<TtsLanguage>());\n\n    public Task<Voice[]> RefreshVoices(string language, CancellationToken cancellationToken) =>\n        GetVoices(language);\n\n    public async Task<TtsResult> Speak(\n        string text,\n        string outputFolder,\n        Voice voice,\n        TtsLanguage? language,\n        string? region,\n        string? model,\n        CancellationToken cancellationToken)\n    {\n        if (voice.EngineVoice is not F5TtsVoice f5Voice)\n        {\n            throw new ArgumentException(\"Voice is not an F5TtsVoice\");\n        }\n\n        if (string.IsNullOrEmpty(f5Voice.FilePath))\n        {\n            throw new InvalidOperationException(\n                \"F5-TTS (CrispASR) requires a reference voice WAV. \"\n                + \"Import one via the voice settings, then pick it in the voice combo. \"\n                + \"Reference WAV should be 24 kHz mono (3-10 s of clean speech) with an \"\n                + \"adjacent .txt file holding the spoken transcription.\");\n        }\n\n        var refText = TryReadRefText(f5Voice.FilePath);\n        var modelKey = ResolveModelKey(model);\n        await EnsureServerRunningAsync(modelKey, f5Voice.FilePath, refText, cancellationToken);\n\n        var outputFileName = Path.Combine(TtsOutputFolder.Resolve(outputFolder, GetSetFolder), Guid.NewGuid() + \".wav\");\n\n        var speed = Math.Clamp(Se.Settings.Video.TextToSpeech.F5TtsCrispAsrSpeed, 0.25, 4.0);","sourceCodeStart":345,"sourceCodeEnd":381,"githubUrl":"https://github.com/SubtitleEdit/subtitleedit/blob/17a9f0748781032255db3526b7215d2fb891e3af/src/ui/Features/Video/TextToSpeech/Engines/F5TtsCrispAsr.cs#L345-L381","documentation":"ArgumentException from F5TtsCrispAsr.Speak: the passed Voice's EngineVoice is not an F5TtsVoice. F5-TTS requires its concrete voice type because it reads f5Voice.FilePath (the reference WAV) to clone from. Any other engine's voice is rejected before the file checks run.","triggerScenarios":"Calling f5TtsEngine.Speak(...) with a Voice whose EngineVoice is not an F5TtsVoice; a dispatch bug; deserialised voice rehydrated to the wrong subtype.","commonSituations":"Cast row passing a global voice from another engine into F5-TTS; refactor that changed EngineVoice typing; voice JSON rehydration choosing the wrong concrete type.","solutions":["Pass only voices produced by F5TtsCrispAsr.GetVoices/RefreshVoices into F5TtsCrispAsr.Speak.","Dispatch each Voice to its owning engine by EngineVoice type.","Add a test asserting each cast voice maps to its producing engine."],"exampleFix":"// before\nawait f5TtsEngine.Speak(text, out, edgeVoice, lang, region, model, ct);\n\n// after\nvar engine = voice.EngineVoice switch\n{\n    F5TtsVoice _ => f5TtsEngine,\n    EdgeTtsVoice _ => edgeTtsEngine,\n    _ => throw new InvalidOperationException($\"No engine for {voice.EngineVoice.GetType().Name}\"),\n};\nawait engine.Speak(text, out, voice, lang, region, model, ct);","handlingStrategy":"type-guard","validationCode":"if (voice.EngineVoice is not F5TtsVoice) {\n    Se.WriteToolsLog($\"Skipping F5-TTS synthesis: voice is {voice.EngineVoice?.GetType().Name}\", true);\n    return;\n}","typeGuard":"static bool IsF5TtsVoice(Voice voice) => voice?.EngineVoice is F5TtsVoice;","tryCatchPattern":"try { await f5TtsEngine.Speak(text, out, voice, lang, region, model, ct); }\ncatch (ArgumentException ex) when (ex.Message == \"Voice is not an F5TtsVoice\")\n{ Se.WriteToolsLog($\"Voice/engine mismatch for F5-TTS: {voice.EngineVoice?.GetType().Name}\", true); }","preventionTips":["Route each Voice to the engine that produced it.","Unit-test cast/review voices resolve to their producing engine.","Never wrap another engine's EngineVoice in a Voice for F5-TTS."],"tags":["tts","f5-tts","crispasr","type-mismatch","voice-dispatch","argument-exception"],"backgroundTag":null,"analyzedSha":"17a9f0748781032255db3526b7215d2fb891e3af","analyzedAt":"2026-08-13T18:11:43.374Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}