From fdee8dfa21f32b8981bd048b5620cb4772322990 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Thu, 2 Apr 2026 08:27:06 +0300 Subject: Fix Gemini voice selection in processor --- internal/processor/processor.go | 84 ++++++++++++++++++------------ internal/processor/processor_test.go | 99 ++++++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+), 33 deletions(-) (limited to 'internal/processor') diff --git a/internal/processor/processor.go b/internal/processor/processor.go index 92e7b29..ecd4c1a 100644 --- a/internal/processor/processor.go +++ b/internal/processor/processor.go @@ -291,36 +291,56 @@ func (p *Processor) geminiVoice() string { return "" } +func (p *Processor) audioVoicesForProvider() []string { + switch p.audioProviderName() { + case "gemini": + return audio.GeminiVoices + default: + return audio.OpenAIVoices + } +} + +func (p *Processor) audioVoiceForProvider() string { + switch p.audioProviderName() { + case "gemini": + if voice := p.geminiVoice(); voice != "" { + return voice + } + default: + if p.flags.OpenAIVoice != "" { + return p.flags.OpenAIVoice + } + } + + voices := p.audioVoicesForProvider() + return voices[rand.Intn(len(voices))] +} + // generateAudio generates audio files for a word func (p *Processor) generateAudio(word string) error { provider := p.audioProviderName() - // Get list of voices to use. + // Get the provider-specific voice list. var voices []string - switch provider { - case "gemini": - if p.flags.AllVoices { - voices = audio.GeminiVoices - } else if voice := p.geminiVoice(); voice != "" { - voices = []string{voice} - fmt.Printf(" Using specified Gemini voice: %s\n", voice) - } else { - voices = []string{""} - fmt.Printf(" Using Gemini model default voice\n") - } - default: - if p.flags.AllVoices { - voices = audio.OpenAIVoices - } else if p.flags.OpenAIVoice != "" { - // Use explicitly specified voice - voices = []string{p.flags.OpenAIVoice} - fmt.Printf(" Using specified voice: %s\n", p.flags.OpenAIVoice) - } else { - // Select a random voice - randomVoice := audio.OpenAIVoices[rand.Intn(len(audio.OpenAIVoices))] - voices = []string{randomVoice} - fmt.Printf(" Using random voice: %s\n", randomVoice) + if p.flags.AllVoices { + voices = p.audioVoicesForProvider() + } else { + voice := p.audioVoiceForProvider() + switch provider { + case "gemini": + if p.geminiVoice() != "" { + fmt.Printf(" Using specified Gemini voice: %s\n", voice) + } else { + fmt.Printf(" Using random Gemini voice: %s\n", voice) + } + default: + if p.flags.OpenAIVoice != "" { + fmt.Printf(" Using specified voice: %s\n", voice) + } else { + fmt.Printf(" Using random voice: %s\n", voice) + } } + voices = []string{voice} } // Generate audio for each voice @@ -340,22 +360,20 @@ func (p *Processor) generateAudio(word string) error { func (p *Processor) generateAudioBgBg(front, back string) error { provider := p.audioProviderName() - voice := "" + voice := p.audioVoiceForProvider() switch provider { case "gemini": - voice = p.geminiVoice() - if voice != "" { - fmt.Printf(" Using Gemini voice: %s\n", voice) + if p.geminiVoice() != "" { + fmt.Printf(" Using specified Gemini voice: %s\n", voice) } else { - fmt.Printf(" Using Gemini model default voice\n") + fmt.Printf(" Using random Gemini voice: %s\n", voice) } default: - // Select a random voice (same voice for both sides for consistency) - voice = audio.OpenAIVoices[rand.Intn(len(audio.OpenAIVoices))] if p.flags.OpenAIVoice != "" { - voice = p.flags.OpenAIVoice + fmt.Printf(" Using specified voice: %s\n", voice) + } else { + fmt.Printf(" Using random voice: %s\n", voice) } - fmt.Printf(" Using voice: %s\n", voice) } // Find or create the word directory ONCE (for the front word) diff --git a/internal/processor/processor_test.go b/internal/processor/processor_test.go index 69250bf..64b8131 100644 --- a/internal/processor/processor_test.go +++ b/internal/processor/processor_test.go @@ -580,6 +580,105 @@ func TestGenerateAudioUsesConfiguredGeminiVoiceAndModel(t *testing.T) { } } +func TestGenerateAudioUsesSharedGeminiVoicesWhenVoiceNotSet(t *testing.T) { + originalFactory := newAudioProvider + t.Cleanup(func() { + newAudioProvider = originalFactory + }) + + originalVoices := append([]string(nil), audio.GeminiVoices...) + t.Cleanup(func() { + audio.GeminiVoices = originalVoices + }) + + audio.GeminiVoices = []string{"sentinel-gemini-voice"} + + fakeProvider := &fakeAudioProvider{} + var capturedConfig *audio.Config + newAudioProvider = func(config *audio.Config) (audio.Provider, error) { + copyConfig := *config + capturedConfig = ©Config + return fakeProvider, nil + } + + originalConfig := viper.New() + *originalConfig = *viper.GetViper() + defer func() { + *viper.GetViper() = *originalConfig + }() + viper.Reset() + viper.Set("audio.provider", "gemini") + + flags := cli.NewFlags() + flags.OutputDir = t.TempDir() + flags.AudioFormat = "mp3" + flags.AudioProvider = "gemini" + + p := NewProcessor(flags) + if err := p.generateAudio("ябълка"); err != nil { + t.Fatalf("generateAudio() unexpected error: %v", err) + } + + if capturedConfig == nil { + t.Fatal("expected audio provider config to be captured") + } + if capturedConfig.GeminiVoice != "sentinel-gemini-voice" { + t.Fatalf("captured GeminiVoice = %q, want %q", capturedConfig.GeminiVoice, "sentinel-gemini-voice") + } +} + +func TestGenerateAudioBgBgUsesSharedGeminiVoicesWhenVoiceNotSet(t *testing.T) { + originalFactory := newAudioProvider + t.Cleanup(func() { + newAudioProvider = originalFactory + }) + + originalVoices := append([]string(nil), audio.GeminiVoices...) + t.Cleanup(func() { + audio.GeminiVoices = originalVoices + }) + + audio.GeminiVoices = []string{"sentinel-bg-gemini-voice"} + + fakeProvider := &fakeAudioProvider{} + var capturedConfigs []*audio.Config + newAudioProvider = func(config *audio.Config) (audio.Provider, error) { + copyConfig := *config + capturedConfigs = append(capturedConfigs, ©Config) + return fakeProvider, nil + } + + originalConfig := viper.New() + *originalConfig = *viper.GetViper() + defer func() { + *viper.GetViper() = *originalConfig + }() + viper.Reset() + viper.Set("audio.provider", "gemini") + + flags := cli.NewFlags() + flags.OutputDir = t.TempDir() + flags.AudioFormat = "mp3" + flags.AudioProvider = "gemini" + + p := NewProcessor(flags) + if err := p.generateAudioBgBg("ябълка", "круша"); err != nil { + t.Fatalf("generateAudioBgBg() unexpected error: %v", err) + } + + if len(capturedConfigs) != 2 { + t.Fatalf("captured config count = %d, want %d", len(capturedConfigs), 2) + } + for i, capturedConfig := range capturedConfigs { + if capturedConfig.GeminiVoice != "sentinel-bg-gemini-voice" { + t.Fatalf("captured config %d GeminiVoice = %q, want %q", i, capturedConfig.GeminiVoice, "sentinel-bg-gemini-voice") + } + } + if fakeProvider.generateCalls != 2 { + t.Fatalf("GenerateAudio() calls = %d, want %d", fakeProvider.generateCalls, 2) + } +} + func TestGenerateAudioUsesConfiguredAudioFormatWhenOpenAIConfigIsSetOnly(t *testing.T) { originalFactory := newAudioProvider t.Cleanup(func() { -- cgit v1.2.3