{"record":{"id":"59895011697176ba","repo":"SubtitleEdit/subtitleedit","slug":"voice-is-not-an-elevenlabvoice","errorCode":null,"errorMessage":"Voice is not an ElevenLabVoice","messagePattern":"Voice is not an ElevenLabVoice","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/ui/Features/Video/TextToSpeech/Engines/ElevenLabs.cs","lineNumber":282,"sourceCode":"    {\n        var ms = new MemoryStream();\n        await _ttsDownloadService.DownloadElevenLabsVoiceList(ms, null, cancellationToken);\n        await File.WriteAllBytesAsync(Path.Combine(GetSetElevenLabsFolder(), JsonFileName), ms.ToArray(), cancellationToken);\n        return await GetVoices(language);\n    }\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 ElevenLabVoice elevenLabVoice)\n        {\n            throw new ArgumentException(\"Voice is not an ElevenLabVoice\");\n        }\n\n        // Callers pass null when this engine is not the globally selected one (per-actor cast\n        // rows, cast-dialog voice test) - fall back to the saved/default model instead of\n        // throwing, which aborted the whole generation run at the first ElevenLabs row.\n        if (string.IsNullOrEmpty(model))\n        {\n            model = Se.Settings.Video.TextToSpeech.ElevenLabsModel;\n        }\n\n        if (string.IsNullOrEmpty(model))\n        {\n            model = \"eleven_multilingual_v2\";\n        }\n\n        Se.WriteToolsLog($\"ElevenLabs: voice={elevenLabVoice.Voice}, voiceId={elevenLabVoice.VoiceId}, model={model}, textLen={text.Length}\");\n\n        var ms = new MemoryStream();","sourceCodeStart":264,"sourceCodeEnd":300,"githubUrl":"https://github.com/SubtitleEdit/subtitleedit/blob/17a9f0748781032255db3526b7215d2fb891e3af/src/ui/Features/Video/TextToSpeech/Engines/ElevenLabs.cs#L264-L300","documentation":"ArgumentException from ElevenLabs.Speak: the passed Voice's EngineVoice is not an ElevenLabVoice. ElevenLabs needs its concrete voice type (carrying VoiceId used in the API call). Notably this engine tolerates a null model argument (falling back to settings then eleven_multilingual_v2), but a wrong voice type is a hard throw.","triggerScenarios":"Calling elevenLabsEngine.Speak(...) with a Voice whose EngineVoice is any non-ElevenLabVoice; a dispatch bug; deserialised voice rehydrated to the wrong subtype; mixing voices across engines in a cast.","commonSituations":"Cast row passing a global voice from another engine into ElevenLabs; refactor that changed EngineVoice typing; per-actor rows where the engine assignment is wrong.","solutions":["Pass only voices produced by ElevenLabs.GetVoices/RefreshVoices into ElevenLabs.Speak.","Dispatch each Voice to its owning engine by EngineVoice type.","Add a test asserting each cast voice resolves to its producing engine."],"exampleFix":"// before\nawait elevenLabsEngine.Speak(text, out, edgeVoice, lang, region, null, ct);\n\n// after\nvar engine = voice.EngineVoice switch\n{\n    ElevenLabVoice _ => elevenLabsEngine,\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 ElevenLabVoice) {\n    Se.WriteToolsLog($\"Skipping ElevenLabs synthesis: voice is {voice.EngineVoice?.GetType().Name}\", true);\n    return;\n}","typeGuard":"static bool IsElevenLabVoice(Voice voice) => voice?.EngineVoice is ElevenLabVoice;","tryCatchPattern":"try { await elevenLabsEngine.Speak(text, out, voice, lang, region, model, ct); }\ncatch (ArgumentException ex) when (ex.Message == \"Voice is not an ElevenLabVoice\")\n{ Se.WriteToolsLog($\"Voice/engine mismatch for ElevenLabs: {voice.EngineVoice?.GetType().Name}\", true); }","preventionTips":["Route each Voice to the engine that produced it.","Per-actor cast rows must assign the engine matching the voice subtype.","Unit-test cast voices resolve to their producing engine."],"tags":["tts","elevenlabs","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"}