SubtitleEdit/subtitleedit · error · ArgumentException

Voice is not an IndexTtsVoice

Error message

Voice is not an IndexTtsVoice

What it means

Type guard at the top of IndexTtsCrispAsr.Speak: requires `voice.EngineVoice` to be an `IndexTtsVoice`. Throws ArgumentException before any server interaction if a different voice subtype is supplied, preventing a bad cast in the cloning pipeline.

Source

Thrown at src/ui/Features/Video/TextToSpeech/Engines/IndexTtsCrispAsr.cs:352

    public Task<string[]> GetModels() => Task.FromResult(new[] { ModelKeyQ4K, ModelKeyQ8_0, ModelKeyF16 });

    public Task<TtsLanguage[]> GetLanguages(Voice voice, string? model) => Task.FromResult(Array.Empty<TtsLanguage>());

    public Task<Voice[]> RefreshVoices(string language, CancellationToken cancellationToken) =>
        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 IndexTtsVoice indexVoice)
        {
            throw new ArgumentException("Voice is not an IndexTtsVoice");
        }

        if (string.IsNullOrEmpty(indexVoice.FilePath))
        {
            throw new InvalidOperationException(
                "IndexTTS (CrispASR) requires a reference voice WAV. "
                + "Import one via the voice settings, then pick it in the voice combo. "
                + "Reference WAV should be 24 kHz mono (3-10 s of clean speech).");
        }

        var modelKey = ResolveModelKey(model);
        await EnsureServerRunningAsync(modelKey, indexVoice.FilePath, cancellationToken);

        var outputFileName = Path.Combine(TtsOutputFolder.Resolve(outputFolder, GetSetFolder), Guid.NewGuid() + ".wav");
        var inputText = text;

        // OpenAI-compatible /v1/audio/speech payload. CrispASR's indextts backend uses:
        //   - `input`             — the text to synthesise

View on GitHub (pinned to 17a9f07487)

Solutions

  1. Obtain the Voice from IndexTtsCrispAsr.GetVoices() so EngineVoice is an IndexTtsVoice.
  2. Re-select the voice in the UI combo after switching to the IndexTTS engine.
  3. If constructing in code, set `voice.EngineVoice = new IndexTtsVoice { FilePath = "..." }`.

Example fix

// before
var voice = new Voice { EngineVoice = new MossTtsVoice() };
await indexEngine.Speak(text, out, voice, ...);

// after
var voice = new Voice { EngineVoice = new IndexTtsVoice { FilePath = refWavPath } };
await indexEngine.Speak(text, out, voice, ...);
Defensive patterns

Strategy: type-guard

Validate before calling

if (voice.EngineVoice is not IndexTtsVoice)
{
    throw new InvalidOperationException($"Refusing IndexTTS speak with a {voice.EngineVoice?.GetType().Name} voice.");
}

Type guard

static bool IsIndexTtsVoice(Voice v) => v.EngineVoice is IndexTtsVoice;

if (!IsIndexTtsVoice(voice)) { /* re-pick an IndexTTS voice */ return; }

Try / catch

try { await indexEngine.Speak(...); }
catch (ArgumentException ex) when (ex.Message.Contains("not an IndexTtsVoice"))
{
    ClearVoiceSelection(); // engine/voice mismatch
}

Prevention

When it happens

Trigger: Passing a non-IndexTts Voice (e.g. MossTtsVoice, KokoroVoice) into the IndexTTS engine; a saved project whose selected voice's EngineId no longer maps to IndexTTS; cross-engine voice list merging.

Common situations: User switched from MOSS-TTS to IndexTTS without re-selecting a voice; an import assigned the wrong concrete voice type; engine registry mismatch after a refactor.

Related errors


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