SubtitleEdit/subtitleedit · error · ArgumentException
Voice is not an AzureVoice
Error message
Voice is not an AzureVoice
What it means
AzureSpeech.Speak pattern-matches voice.EngineVoice to AzureVoice; any other engine's voice triggers ArgumentException. Note this method immediately afterwards falls back region/model from settings when the caller passed null (per-actor cast rows), so the type check is the first guard.
Source
Thrown at src/ui/Features/Video/TextToSpeech/Engines/AzureSpeech.cs:140
// ElevenLabs-shaped JSON that Map() cannot parse, permanently emptying the voice combo.
var ms = new MemoryStream();
await _ttsDownloadService.DownloadAzureVoiceList(ms, null, cancellationToken);
await File.WriteAllBytesAsync(Path.Combine(GetSetAzureFolder(), JsonFileName), ms.ToArray(), cancellationToken);
return await 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 AzureVoice azureVoice)
{
throw new ArgumentException("Voice is not an AzureVoice");
}
// Callers pass null region/model when this engine is not the globally selected one
// (per-actor cast rows, cast-dialog voice test) - fall back to the saved settings
// instead of dereferencing null into the request URL, which produced a nonsense host
// (https://.tts.speech...) and aborted the whole generation run.
if (string.IsNullOrWhiteSpace(region))
{
region = Se.Settings.Video.TextToSpeech.AzureRegion;
}
if (string.IsNullOrWhiteSpace(region))
{
var error = "Azure region is not set - enter it in the Azure engine settings.";
Se.WriteToolsLog("AzureSpeech: " + error, true);
return new TtsResult { Text = text, FileName = string.Empty, Error = true, ErrorMessage = error };
}
View on GitHub (pinned to 17a9f07487)
Solutions
- Re-select the voice from the Azure voice list after switching to AzureSpeech.
- Validate per-actor voice mappings reference Azure voices before batch generation.
- Filter the voice picker to the active engine.
Example fix
// before
await _azure.Speak(text, out, voice, lang, region, model, ct);
// after
if (voice.EngineVoice is not AzureVoice)
throw new InvalidOperationException($"Voice {voice.Name} is not an Azure voice.");
await _azure.Speak(text, out, voice, lang, region, model, ct); Defensive patterns
Strategy: type-guard
Validate before calling
if (voice.EngineVoice is not AzureVoice) return Invalid($"Voice {voice.Name} is not an Azure voice"); Type guard
static bool IsAzureVoice(Voice v) => v.EngineVoice is AzureVoice;
Try / catch
try { await azure.Speak(...); }
catch (ArgumentException ex) when (ex.Message.Contains("AzureVoice"))
{ /* re-select the voice from the Azure list */ } Prevention
- Validate per-actor voice mappings reference Azure voices before batch TTS.
- Re-select voices after switching to the Azure engine.
- Guard the type at the call site with the voice name in the message.
When it happens
Trigger: A Voice whose EngineVoice is not AzureVoice is passed to AzureSpeech.Speak.
Common situations: Same as 187: stale mapping after engine switch; per-actor cast row still pointing at a non-Azure voice; voice list/engine desync in the cast dialog.
Related errors
- Voice is not a AllTalkVoice
- Voice is not a ChatterboxVoice
- Voice is not a Qwen3TtsVoice
- Voice is not a VibeVoice
- Voice is not a CosyVoice3Voice
AI-assisted analysis of SubtitleEdit/subtitleedit@17a9f07487 (2026-08-13).
Data as JSON: /api/errors/f60293397829e535.
Report an issue: GitHub.