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 synthesiseView on GitHub (pinned to 17a9f07487)
Solutions
- Obtain the Voice from IndexTtsCrispAsr.GetVoices() so EngineVoice is an IndexTtsVoice.
- Re-select the voice in the UI combo after switching to the IndexTTS engine.
- 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
- Source Voice lists only from IndexTtsCrispAsr.GetVoices().
- Re-select the voice after switching into the IndexTTS engine.
- Persist the concrete voice subtype alongside EngineId.
- Guard at the UI layer so a wrong-type voice never reaches Speak.
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
- Voice is not a GoogleVoice
- IndexTTS (CrispASR) requires a reference voice WAV. Import o
- IndexTTS (CrispASR) — the crispasr server crashed during syn
- IndexTTS (CrispASR) request failed — the connection to the c
- IndexTTS (CrispASR) synthesis failed ({(int)response.StatusC
AI-assisted analysis of SubtitleEdit/subtitleedit@17a9f07487 (2026-08-13).
Data as JSON: /api/errors/d10348054c617e66.
Report an issue: GitHub.