SubtitleEdit/subtitleedit · error · ArgumentException

Voice is not a GoogleVoice

Error message

Voice is not a GoogleVoice

What it means

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.

Source

Thrown at src/ui/Features/Video/TextToSpeech/Engines/GoogleSpeech.cs:161

    {
        var ms = new MemoryStream();
        await _ttsDownloadService.DownloadGoogleVoiceList(Se.Settings.Video.TextToSpeech.GoogleKeyFile, ms, cancellationToken);
        await File.WriteAllBytesAsync(Path.Combine(GetSetGoogleFolder(), 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 GoogleVoice googleVoice)
        {
            throw new ArgumentException("Voice is not a GoogleVoice");
        }

        Se.WriteToolsLog($"GoogleSpeech: voice={googleVoice.Name}, languageCode={googleVoice.LanguageCode}, model={model ?? "Standard"}, textLen={text.Length}");

        var ms = new MemoryStream();
        var ok = await _ttsDownloadService.DownloadGoogleVoiceSpeak(
            text,
            googleVoice,
            model ?? "Standard",
            Se.Settings.Video.TextToSpeech.GoogleKeyFile,
            ms,
            cancellationToken);

        if (!ok)
        {
            Se.WriteToolsLog($"GoogleSpeech: request failed (voice={googleVoice.Name})");
            return new TtsResult { Text = text, FileName = string.Empty, Error = true };
        }

View on GitHub (pinned to 17a9f07487)

Solutions

  1. Verify the Voice was obtained from GoogleSpeech.GetVoices()/RefreshVoices() and not from another engine.
  2. Clear the persisted voice selection and re-pick a Google voice from the combo.
  3. If building Voice objects in code, construct GoogleVoice (with Name + LanguageCode) and assign it to voice.EngineVoice.
  4. Check that the engine registry maps the voice's EngineId to the GoogleSpeech instance.

Example fix

// before
var voice = new Voice { EngineVoice = someKokoroVoice };
await googleEngine.Speak(text, out, voice, ...);

// after
var voice = new Voice { EngineVoice = new GoogleVoice { Name = "en-US-Wavenet-D", LanguageCode = "en-US" } };
await googleEngine.Speak(text, out, voice, ...);
Defensive patterns

Strategy: type-guard

Validate before calling

if (voice.EngineVoice is not GoogleVoice)
{
    throw new InvalidOperationException($"Refusing to call GoogleSpeech with a {voice.EngineVoice?.GetType().Name} voice.");
}

Type guard

static bool IsGoogleVoice(Voice v) => v.EngineVoice is GoogleVoice;

// usage
if (!IsGoogleVoice(voice)) { /* re-pick a Google voice */ return; }

Try / catch

try { await googleEngine.Speak(...); }
catch (ArgumentException ex) when (ex.Message.Contains("not a GoogleVoice"))
{
    // Voice/engine mismatch — clear selection and prompt user to re-pick.
    ClearVoiceSelection();
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of SubtitleEdit/subtitleedit@17a9f07487 (2026-08-13). Data as JSON: /api/errors/247c51f563279f7b. Report an issue: GitHub.