SubtitleEdit/subtitleedit · error · ArgumentException
Voice is not an ElevenLabVoice
Error message
Voice is not an ElevenLabVoice
What it means
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.
Source
Thrown at src/ui/Features/Video/TextToSpeech/Engines/ElevenLabs.cs:282
{
var ms = new MemoryStream();
await _ttsDownloadService.DownloadElevenLabsVoiceList(ms, null, cancellationToken);
await File.WriteAllBytesAsync(Path.Combine(GetSetElevenLabsFolder(), JsonFileName), ms.ToArray(), cancellationToken);
return await GetVoices(language);
}
public async Task<TtsResult> Speak(
string text,
string outputFolder,
Voice voice,
TtsLanguage? language,
string? region,
string? model,
CancellationToken cancellationToken)
{
if (voice.EngineVoice is not ElevenLabVoice elevenLabVoice)
{
throw new ArgumentException("Voice is not an ElevenLabVoice");
}
// Callers pass null when this engine is not the globally selected one (per-actor cast
// rows, cast-dialog voice test) - fall back to the saved/default model instead of
// throwing, which aborted the whole generation run at the first ElevenLabs row.
if (string.IsNullOrEmpty(model))
{
model = Se.Settings.Video.TextToSpeech.ElevenLabsModel;
}
if (string.IsNullOrEmpty(model))
{
model = "eleven_multilingual_v2";
}
Se.WriteToolsLog($"ElevenLabs: voice={elevenLabVoice.Voice}, voiceId={elevenLabVoice.VoiceId}, model={model}, textLen={text.Length}");
var ms = new MemoryStream();View on GitHub (pinned to 17a9f07487)
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.
Example fix
// before
await elevenLabsEngine.Speak(text, out, edgeVoice, lang, region, null, ct);
// after
var engine = voice.EngineVoice switch
{
ElevenLabVoice _ => elevenLabsEngine,
EdgeTtsVoice _ => edgeTtsEngine,
_ => throw new InvalidOperationException($"No engine for {voice.EngineVoice.GetType().Name}"),
};
await engine.Speak(text, out, voice, lang, region, model, ct); Defensive patterns
Strategy: type-guard
Validate before calling
if (voice.EngineVoice is not ElevenLabVoice) {
Se.WriteToolsLog($"Skipping ElevenLabs synthesis: voice is {voice.EngineVoice?.GetType().Name}", true);
return;
} Type guard
static bool IsElevenLabVoice(Voice voice) => voice?.EngineVoice is ElevenLabVoice;
Try / catch
try { await elevenLabsEngine.Speak(text, out, voice, lang, region, model, ct); }
catch (ArgumentException ex) when (ex.Message == "Voice is not an ElevenLabVoice")
{ Se.WriteToolsLog($"Voice/engine mismatch for ElevenLabs: {voice.EngineVoice?.GetType().Name}", true); } Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Voice is not a CosyVoice3Voice
- Voice is not an EdgeTtsVoice
- Voice is not an F5TtsVoice
- Voice is not a AllTalkVoice
- Voice is not an AzureVoice
AI-assisted analysis of SubtitleEdit/subtitleedit@17a9f07487 (2026-08-13).
Data as JSON: /api/errors/59895011697176ba.
Report an issue: GitHub.