SubtitleEdit/subtitleedit · error · ArgumentException
Voice is not an EdgeTtsVoice
Error message
Voice is not an EdgeTtsVoice
What it means
ArgumentException from EdgeTts.Speak: the passed Voice's EngineVoice is not an EdgeTtsVoice. EdgeTts pattern-matches its concrete voice type (which carries the edge-tts voice Name) and refuses any other engine's voice. The message is literal and non-negotiable.
Source
Thrown at src/ui/Features/Video/TextToSpeech/Engines/EdgeTts.cs:122
{
voices = GetFallbackVoices();
}
return voices.Select(v => new Voice(v)).ToArray();
}
public async Task<TtsResult> Speak(
string text,
string outputFolder,
Voice voice,
TtsLanguage? language,
string? region,
string? model,
CancellationToken cancellationToken)
{
if (voice.EngineVoice is not EdgeTtsVoice edgeVoice)
{
throw new ArgumentException("Voice is not an EdgeTtsVoice");
}
var folder = TtsOutputFolder.Resolve(outputFolder, GetSetEdgeTtsFolder);
var fileName = Path.Combine(folder, Guid.NewGuid() + ".mp3");
var escapedText = EscapeQuotedArgValue(text);
var escapedVoice = EscapeQuotedArgValue(edgeVoice.Name);
var args =
$"--voice \"{escapedVoice}\" --text \"{escapedText}\" --write-media \"{fileName}\"";
var rate = NormalizeProsodyValue(Se.Settings.Video.TextToSpeech.EdgeTtsRate, "%");
if (!string.IsNullOrEmpty(rate))
{
args += $" --rate={rate}";
}
var pitch = NormalizeProsodyValue(Se.Settings.Video.TextToSpeech.EdgeTtsPitch, "Hz");
if (!string.IsNullOrEmpty(pitch))
{View on GitHub (pinned to 17a9f07487)
Solutions
- Pass only voices produced by EdgeTts.GetVoices/RefreshVoices into EdgeTts.Speak.
- Dispatch each Voice to its owning engine by inspecting EngineVoice's type.
- Add a test asserting every cast voice maps to the engine that created it.
Example fix
// before
await edgeTtsEngine.Speak(text, out, cosyVoice, lang, region, model, ct);
// after
if (voice.EngineVoice is not EdgeTtsVoice)
throw new InvalidOperationException($"EdgeTts cannot speak {voice.EngineVoice.GetType().Name}");
await edgeTtsEngine.Speak(text, out, voice, lang, region, model, ct); Defensive patterns
Strategy: type-guard
Validate before calling
if (voice.EngineVoice is not EdgeTtsVoice) {
Se.WriteToolsLog($"Skipping EdgeTts synthesis: voice is {voice.EngineVoice?.GetType().Name}", true);
return;
} Type guard
static bool IsEdgeTtsVoice(Voice voice) => voice?.EngineVoice is EdgeTtsVoice;
Try / catch
try { await edgeTtsEngine.Speak(text, out, voice, lang, region, model, ct); }
catch (ArgumentException ex) when (ex.Message == "Voice is not an EdgeTtsVoice")
{ Se.WriteToolsLog($"Voice/engine mismatch for EdgeTts: {voice.EngineVoice?.GetType().Name}", true); } Prevention
- 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 EdgeTts.
When it happens
Trigger: Calling edgeTtsEngine.Speak(...) with a Voice whose EngineVoice is a CosyVoice3Voice, ElevenLabVoice, F5TtsVoice, ChatterboxVoice, etc.; a dispatch bug routing the wrong voice; deserialised voice rehydrated to the wrong subtype.
Common situations: Cast row using a globally-selected voice from a different engine; refactor that changed EngineVoice typing; voice JSON rehydration picking the wrong concrete type.
Related errors
- Voice is not a CosyVoice3Voice
- Voice is not an ElevenLabVoice
- 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/811c90e2d6ad9e85.
Report an issue: GitHub.