SubtitleEdit/subtitleedit · error · ArgumentException
Voice is not a CosyVoice3Voice
Error message
Voice is not a CosyVoice3Voice
What it means
ArgumentException from CosyVoice3CrispAsr.Speak: the passed Voice's EngineVoice is not a CosyVoice3Voice. Each TTS engine owns a concrete voice type; Speak pattern-matches and refuses to synthesise with another engine's voice object. Unlike the missing-config errors below, this is a hard throw (the comment notes a throw escapes the per-segment generate loop and aborts the whole run).
Source
Thrown at src/ui/Features/Video/TextToSpeech/Engines/CosyVoice3CrispAsr.cs:471
public Task<string[]> GetModels() => Task.FromResult(new[] { ModelKeyQ4K, ModelKeyF16 });
public Task<TtsLanguage[]> GetLanguages(Voice voice, string? model) => Task.FromResult(CosyVoice3Languages.All);
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 CosyVoice3Voice cosyVoice)
{
throw new ArgumentException("Voice is not a CosyVoice3Voice");
}
// Either Preset OR FilePath must be set. Preset wins if both are populated (defensive
// — shouldn't happen with the constructors but guards against future drift).
// Error results, not throws: a throw from Speak escapes the generate loop's per-segment
// handling and aborts the entire run - reachable from cast rows and the review window,
// where the main window's transcript prompt never had a chance to fire.
var voiceArg = !string.IsNullOrEmpty(cosyVoice.Preset) ? cosyVoice.Preset : cosyVoice.FilePath;
if (string.IsNullOrEmpty(voiceArg))
{
var error = "CosyVoice3 (CrispASR) requires a preset or an imported reference WAV. "
+ "Pick one of the baked presets (zero_shot / fleurs-*) or import a 16 kHz mono "
+ "reference WAV with an adjacent .txt transcription sidecar.";
Se.WriteToolsLog("CosyVoice3 (CrispASR): " + error, true);
return new TtsResult { Text = text, FileName = string.Empty, Error = true, ErrorMessage = error };
}
var isClone = string.IsNullOrEmpty(cosyVoice.Preset);View on GitHub (pinned to 17a9f07487)
Solutions
- Ensure the Voice passed to CosyVoice3CrispAsr.Speak was built by CosyVoice3CrispAsr.GetVoices/RefreshVoices.
- Route each Voice to the engine that owns it — key voices by engine before dispatch.
- Add a unit test that asserts every voice in a cast resolves to the engine that produced it.
Example fix
// before — wrong engine's voice handed to CosyVoice3
await cosyVoice3Engine.Speak(text, out, edgeVoice, lang, region, model, ct);
// after — dispatch each voice to its owning engine
var engine = voice.EngineVoice switch
{
CosyVoice3Voice _ => cosyVoice3Engine,
EdgeTtsVoice _ => edgeTtsEngine,
_ => throw new InvalidOperationException($"No engine for {voice.EngineVoice.GetType().Name}"),
};
await engine.Speak(text, out, voice, lang, region, model, ct); Defensive patterns
Strategy: type-guard
Validate before calling
if (voice.EngineVoice is not CosyVoice3Voice) return;
// or, before dispatch:
if (voice.EngineVoice is not CosyVoice3Voice cosy) {
Se.WriteToolsLog($"Skipping CosyVoice3 synthesis: voice is {voice.EngineVoice?.GetType().Name}", true);
return;
} Type guard
static bool IsCosyVoice3(Voice voice) => voice?.EngineVoice is CosyVoice3Voice;
Try / catch
try { await cosyVoice3Engine.Speak(text, out, voice, lang, region, model, ct); }
catch (ArgumentException ex) when (ex.Message == "Voice is not a CosyVoice3Voice")
{
Se.WriteToolsLog($"Voice/engine mismatch for CosyVoice3: {voice.EngineVoice?.GetType().Name}", true);
} Prevention
- Always route a Voice to the engine that produced it — key voices by engine at construction.
- Unit-test that every cast/review voice resolves to its producing engine.
- Never construct a Voice from a different engine's EngineVoice subtype.
When it happens
Trigger: Calling cosyVoice3Engine.Speak(...) with a Voice constructed from an EdgeTtsVoice, ElevenLabVoice, F5TtsVoice, or any non-CosyVoice3 EngineVoice; a cast/mapping bug in the voice-combo → engine dispatch; mixing voices across engines in a cast row.
Common situations: A cast row or review window passes the global selected voice (from a different engine) into the CosyVoice3 engine; deserialised voice JSON rehydrated as the wrong subtype; refactor that changed EngineVoice typing.
Related errors
- Voice is not an EdgeTtsVoice
- 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/abfb93ded68d0170.
Report an issue: GitHub.