{"record":{"id":"247c51f563279f7b","repo":"SubtitleEdit/subtitleedit","slug":"voice-is-not-a-googlevoice","errorCode":null,"errorMessage":"Voice is not a GoogleVoice","messagePattern":"Voice is not a GoogleVoice","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/ui/Features/Video/TextToSpeech/Engines/GoogleSpeech.cs","lineNumber":161,"sourceCode":"    {\n        var ms = new MemoryStream();\n        await _ttsDownloadService.DownloadGoogleVoiceList(Se.Settings.Video.TextToSpeech.GoogleKeyFile, ms, cancellationToken);\n        await File.WriteAllBytesAsync(Path.Combine(GetSetGoogleFolder(), 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 GoogleVoice googleVoice)\n        {\n            throw new ArgumentException(\"Voice is not a GoogleVoice\");\n        }\n\n        Se.WriteToolsLog($\"GoogleSpeech: voice={googleVoice.Name}, languageCode={googleVoice.LanguageCode}, model={model ?? \"Standard\"}, textLen={text.Length}\");\n\n        var ms = new MemoryStream();\n        var ok = await _ttsDownloadService.DownloadGoogleVoiceSpeak(\n            text,\n            googleVoice,\n            model ?? \"Standard\",\n            Se.Settings.Video.TextToSpeech.GoogleKeyFile,\n            ms,\n            cancellationToken);\n\n        if (!ok)\n        {\n            Se.WriteToolsLog($\"GoogleSpeech: request failed (voice={googleVoice.Name})\");\n            return new TtsResult { Text = text, FileName = string.Empty, Error = true };\n        }","sourceCodeStart":143,"sourceCodeEnd":179,"githubUrl":"https://github.com/SubtitleEdit/subtitleedit/blob/17a9f0748781032255db3526b7215d2fb891e3af/src/ui/Features/Video/TextToSpeech/Engines/GoogleSpeech.cs#L143-L179","documentation":"A defensive type guard at the top of GoogleSpeech.Speak: it requires `voice.EngineVoice` to be a `GoogleVoice` instance. ArgumentException is thrown immediately if any other engine's voice type is passed, before any network call is made. This prevents a meaningless downstream cast/null-ref inside DownloadGoogleVoiceSpeak.","triggerScenarios":"Passing a Voice whose EngineVoice is a KokoroVoice, AzureVoice, ElevenlabsVoice, etc. to the Google engine; a deserialized voice list whose EngineId no longer matches the engine instance; a stale voice combo selection persisted across an engine rename.","commonSituations":"User switched engines in the UI but the persisted selected voice belongs to the previous engine; a plugin/voice-import produced a Voice with the wrong concrete type; serialization round-trip lost the concrete subtype.","solutions":["Verify the Voice was obtained from GoogleSpeech.GetVoices()/RefreshVoices() and not from another engine.","Clear the persisted voice selection and re-pick a Google voice from the combo.","If building Voice objects in code, construct GoogleVoice (with Name + LanguageCode) and assign it to voice.EngineVoice.","Check that the engine registry maps the voice's EngineId to the GoogleSpeech instance."],"exampleFix":"// before\nvar voice = new Voice { EngineVoice = someKokoroVoice };\nawait googleEngine.Speak(text, out, voice, ...);\n\n// after\nvar voice = new Voice { EngineVoice = new GoogleVoice { Name = \"en-US-Wavenet-D\", LanguageCode = \"en-US\" } };\nawait googleEngine.Speak(text, out, voice, ...);","handlingStrategy":"type-guard","validationCode":"if (voice.EngineVoice is not GoogleVoice)\n{\n    throw new InvalidOperationException($\"Refusing to call GoogleSpeech with a {voice.EngineVoice?.GetType().Name} voice.\");\n}","typeGuard":"static bool IsGoogleVoice(Voice v) => v.EngineVoice is GoogleVoice;\n\n// usage\nif (!IsGoogleVoice(voice)) { /* re-pick a Google voice */ return; }","tryCatchPattern":"try { await googleEngine.Speak(...); }\ncatch (ArgumentException ex) when (ex.Message.Contains(\"not a GoogleVoice\"))\n{\n    // Voice/engine mismatch — clear selection and prompt user to re-pick.\n    ClearVoiceSelection();\n}","preventionTips":["Always obtain Voice objects from the same engine instance that will Speak them.","Clear the persisted voice selection when the user switches engines.","Bind each Voice to its source EngineId so mismatches are detectable upstream.","Unit-test cross-engine voice passing to catch regressions."],"tags":["google","type-guard","voice","tts","validation"],"backgroundTag":null,"analyzedSha":"17a9f0748781032255db3526b7215d2fb891e3af","analyzedAt":"2026-08-13T18:11:43.374Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}