summaryrefslogtreecommitdiff
path: root/internal/processor
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-04-02 08:27:06 +0300
committerPaul Buetow <paul@buetow.org>2026-04-02 08:27:06 +0300
commitfdee8dfa21f32b8981bd048b5620cb4772322990 (patch)
tree2865ea22257e0d25dd585eff360ce3c20b192579 /internal/processor
parente905d0d7435a827825017c5612b6e61d1f1f5da7 (diff)
Fix Gemini voice selection in processor
Diffstat (limited to 'internal/processor')
-rw-r--r--internal/processor/processor.go84
-rw-r--r--internal/processor/processor_test.go99
2 files changed, 150 insertions, 33 deletions
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 = &copyConfig
+ 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, &copyConfig)
+ 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() {