SubtitleEdit/subtitleedit · error · ArgumentException
Voice is not a VibeVoice
Error message
Voice is not a VibeVoice
What it means
ArgumentException thrown at the top of VibeVoiceCrispAsr.Speak when voice.EngineVoice is not a VibeVoice. VibeVoiceCrispAsr only does voice cloning and its voice list is populated exclusively with Voice(new VibeVoice(...)); passing any other engine's voice is a contract violation.
Source
Thrown at src/ui/Features/Video/TextToSpeech/Engines/VibeVoiceCrispAsr.cs:340
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 VibeVoice vibeVoice)
{
throw new ArgumentException("Voice is not a VibeVoice");
}
if (string.IsNullOrEmpty(vibeVoice.FilePath))
{
throw new InvalidOperationException(
"VibeVoice (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.");
}
var modelKey = ResolveModelKey(model);
await EnsureServerRunningAsync(modelKey, cancellationToken);
var outputFileName = Path.Combine(TtsOutputFolder.Resolve(outputFolder, GetSetFolder), Guid.NewGuid() + ".wav");
var inputText = text;
// OpenAI-compatible /v1/audio/speech payload. CrispASR's vibevoice backends look at:
// - `input` — the text to synthesiseView on GitHub (pinned to 17a9f07487)
Solutions
- Pass only voices obtained from VibeVoiceCrispAsr.GetVoices into its Speak.
- Clear/re-resolve the selected voice on engine switch.
- Filter the voice combo in the UI so non-VibeVoice entries can't be selected for this engine.
- In tests, build voices as `new Voice(new VibeVoice(...))`.
Example fix
// before await vibeEngine.Speak(text, folder, qwen3Voice, ...); // after — resolve a VibeVoice from this engine var voices = await vibeEngine.GetVoices(language); var voice = voices.Single(v => ((VibeVoice)v.EngineVoice).Voice == desiredName); await vibeEngine.Speak(text, folder, voice, ...);
Defensive patterns
Strategy: type-guard
Validate before calling
if (voice.EngineVoice is not VibeVoice)
throw new ArgumentException("Voice is not a VibeVoice"); Type guard
static bool IsVibeVoice(Voice v) => v.EngineVoice is VibeVoice;
Prevention
- Resolve voices from VibeVoiceCrispAsr.GetVoices only.
- Clear the selected voice on engine switch.
- Filter the combo by VibeVoice subtype.
When it happens
Trigger: A voice from Qwen3TtsCpp, Qwen3TtsCrispAsr, or a cloud TTS engine was handed to this engine's Speak; engine-switch race left the previous engine's voice selected; tests share a voice fixture across engines.
Common situations: UI engine switch that didn't re-resolve the selected voice; deserialized Voice that lost its subtype; cross-engine test fixtures.
Related errors
- Voice is not a Qwen3TtsVoice
- Voice is not a AllTalkVoice
- Voice is not an AzureVoice
- Voice is not a ChatterboxVoice
- VibeVoice (CrispASR) requires a reference voice WAV. Import
AI-assisted analysis of SubtitleEdit/subtitleedit@17a9f07487 (2026-08-13).
Data as JSON: /api/errors/7da1c5994283d4c4.
Report an issue: GitHub.