{"record":{"id":"f60293397829e535","repo":"SubtitleEdit/subtitleedit","slug":"voice-is-not-an-azurevoice","errorCode":null,"errorMessage":"Voice is not an AzureVoice","messagePattern":"Voice is not an AzureVoice","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/ui/Features/Video/TextToSpeech/Engines/AzureSpeech.cs","lineNumber":140,"sourceCode":"        // ElevenLabs-shaped JSON that Map() cannot parse, permanently emptying the voice combo.\n        var ms = new MemoryStream();\n        await _ttsDownloadService.DownloadAzureVoiceList(ms, null, cancellationToken);\n        await File.WriteAllBytesAsync(Path.Combine(GetSetAzureFolder(), JsonFileName), ms.ToArray(), cancellationToken);\n        return await GetVoices(language);\n    }\n\n    public async Task<TtsResult> Speak(\n        string text, \n        string outputFolder, \n        Voice voice, \n        TtsLanguage? language,\n        string? region,\n        string? model,\n        CancellationToken cancellationToken)\n    {\n        if (voice.EngineVoice is not AzureVoice azureVoice)\n        {\n            throw new ArgumentException(\"Voice is not an AzureVoice\");\n        }\n\n        // Callers pass null region/model when this engine is not the globally selected one\n        // (per-actor cast rows, cast-dialog voice test) - fall back to the saved settings\n        // instead of dereferencing null into the request URL, which produced a nonsense host\n        // (https://.tts.speech...) and aborted the whole generation run.\n        if (string.IsNullOrWhiteSpace(region))\n        {\n            region = Se.Settings.Video.TextToSpeech.AzureRegion;\n        }\n\n        if (string.IsNullOrWhiteSpace(region))\n        {\n            var error = \"Azure region is not set - enter it in the Azure engine settings.\";\n            Se.WriteToolsLog(\"AzureSpeech: \" + error, true);\n            return new TtsResult { Text = text, FileName = string.Empty, Error = true, ErrorMessage = error };\n        }\n","sourceCodeStart":122,"sourceCodeEnd":158,"githubUrl":"https://github.com/SubtitleEdit/subtitleedit/blob/17a9f0748781032255db3526b7215d2fb891e3af/src/ui/Features/Video/TextToSpeech/Engines/AzureSpeech.cs#L122-L158","documentation":"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.","triggerScenarios":"A Voice whose EngineVoice is not AzureVoice is passed to AzureSpeech.Speak.","commonSituations":"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.","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."],"exampleFix":"// before\nawait _azure.Speak(text, out, voice, lang, region, model, ct);\n// after\nif (voice.EngineVoice is not AzureVoice)\n    throw new InvalidOperationException($\"Voice {voice.Name} is not an Azure voice.\");\nawait _azure.Speak(text, out, voice, lang, region, model, ct);","handlingStrategy":"type-guard","validationCode":"if (voice.EngineVoice is not AzureVoice) return Invalid($\"Voice {voice.Name} is not an Azure voice\");","typeGuard":"static bool IsAzureVoice(Voice v) => v.EngineVoice is AzureVoice;","tryCatchPattern":"try { await azure.Speak(...); }\ncatch (ArgumentException ex) when (ex.Message.Contains(\"AzureVoice\"))\n{ /* re-select the voice from the Azure list */ }","preventionTips":["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."],"tags":["tts","voice","type-mismatch","azure","text-to-speech"],"backgroundTag":null,"analyzedSha":"17a9f0748781032255db3526b7215d2fb891e3af","analyzedAt":"2026-08-13T18:11:43.374Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}