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

  1. Pass only voices produced by EdgeTts.GetVoices/RefreshVoices into EdgeTts.Speak.
  2. Dispatch each Voice to its owning engine by inspecting EngineVoice's type.
  3. 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

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


AI-assisted analysis of SubtitleEdit/subtitleedit@17a9f07487 (2026-08-13). Data as JSON: /api/errors/811c90e2d6ad9e85. Report an issue: GitHub.