diff options
| author | Paul Buetow <paul@buetow.org> | 2026-04-02 17:46:07 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-04-02 17:46:07 +0300 |
| commit | f3d057fec20aeef584cb6340c6a002280a019f15 (patch) | |
| tree | 018be28f954243414969e8b8970fb3f3be8da112 | |
| parent | 1fc2ade33b72dd299fc987943ddd09dee7c609ad (diff) | |
task 002: restore Gemini warning output
| -rw-r--r-- | internal/audio/fallbacks.go | 5 | ||||
| -rw-r--r-- | internal/audio/voices_test.go | 6 | ||||
| -rw-r--r-- | internal/gui/generator.go | 8 | ||||
| -rw-r--r-- | internal/gui/generator_test.go | 2 | ||||
| -rw-r--r-- | internal/processor/processor.go | 12 | ||||
| -rw-r--r-- | internal/processor/processor_test.go | 50 |
6 files changed, 70 insertions, 13 deletions
diff --git a/internal/audio/fallbacks.go b/internal/audio/fallbacks.go index 0c3ed83..f22301f 100644 --- a/internal/audio/fallbacks.go +++ b/internal/audio/fallbacks.go @@ -6,7 +6,7 @@ import ( ) // RunWithVoiceFallbacks tries the selected Gemini voice first, then the remaining known voices. -func RunWithVoiceFallbacks(initialVoice string, generate func(voice string) error) (usedVoice string, err error) { +func RunWithVoiceFallbacks(initialVoice string, generate func(voice string) error, warnNoAudio func(voice string)) (usedVoice string, err error) { attempted := make([]string, 0, len(GeminiVoices)) var lastErr error @@ -22,6 +22,9 @@ func RunWithVoiceFallbacks(initialVoice string, generate func(voice string) erro } lastErr = err + if warnNoAudio != nil { + warnNoAudio(voice) + } } return "", fmt.Errorf("Gemini returned no audio for voices %s: %w", strings.Join(attempted, ", "), lastErr) diff --git a/internal/audio/voices_test.go b/internal/audio/voices_test.go index 3d936d1..6d79634 100644 --- a/internal/audio/voices_test.go +++ b/internal/audio/voices_test.go @@ -76,7 +76,7 @@ func TestRunWithVoiceFallbacks(t *testing.T) { return ErrGeminiNoAudioData } return nil - }) + }, nil) if err != nil { t.Fatalf("RunWithVoiceFallbacks() unexpected error: %v", err) } @@ -94,7 +94,7 @@ func TestRunWithVoiceFallbacks(t *testing.T) { usedVoice, err := RunWithVoiceFallbacks("Charon", func(voice string) error { attempted = append(attempted, voice) return sentinel - }) + }, nil) if !errors.Is(err, sentinel) { t.Fatalf("error = %v, want %v", err, sentinel) } @@ -111,7 +111,7 @@ func TestRunWithVoiceFallbacks(t *testing.T) { usedVoice, err := RunWithVoiceFallbacks("Charon", func(voice string) error { attempted = append(attempted, voice) return ErrGeminiNoAudioData - }) + }, nil) if !errors.Is(err, ErrGeminiNoAudioData) { t.Fatalf("error = %v, want wrapped ErrGeminiNoAudioData", err) } diff --git a/internal/gui/generator.go b/internal/gui/generator.go index f3fe9b5..72c7c51 100644 --- a/internal/gui/generator.go +++ b/internal/gui/generator.go @@ -187,7 +187,7 @@ func (a *Application) generateAudio(ctx context.Context, word string, cardDir st fmt.Printf("Retrying Gemini audio with voice: %s\n", candidate) } return a.generateAudioFile(ctx, word, outputFile, candidate, speed) - }) + }, nil) } else { err = a.generateAudioFile(ctx, word, outputFile, voice, speed) } @@ -229,7 +229,7 @@ func (a *Application) generateAudioFront(ctx context.Context, word string, cardD fmt.Printf("Retrying Gemini audio with voice: %s\n", candidate) } return a.generateAudioFile(ctx, word, frontFile, candidate, speed) - }) + }, nil) } else { err = a.generateAudioFile(ctx, word, frontFile, voice, speed) } @@ -269,7 +269,7 @@ func (a *Application) generateAudioBack(ctx context.Context, text string, cardDi fmt.Printf("Retrying Gemini audio with voice: %s\n", candidate) } return a.generateAudioFile(ctx, text, backFile, candidate, speed) - }) + }, nil) } else { err = a.generateAudioFile(ctx, text, backFile, voice, speed) } @@ -325,7 +325,7 @@ func (a *Application) generateAudioBgBg(ctx context.Context, front, back, cardDi fmt.Printf("Retrying Gemini audio with voice: %s\n", candidate) } return runPair(candidate) - }) + }, nil) } else { err = runPair(voice) } diff --git a/internal/gui/generator_test.go b/internal/gui/generator_test.go index fcb8f51..6892fb1 100644 --- a/internal/gui/generator_test.go +++ b/internal/gui/generator_test.go @@ -400,7 +400,7 @@ func TestGenerateGeminiAudioWithFallbacksRetriesAlternateVoice(t *testing.T) { voice, err := audio.RunWithVoiceFallbacks("Charon", func(candidate string) error { return app.generateAudioFile(context.Background(), "ябълка", outputPath, candidate, 1.0) - }) + }, nil) if err != nil { t.Fatalf("RunWithVoiceFallbacks() unexpected error: %v", err) } diff --git a/internal/processor/processor.go b/internal/processor/processor.go index f6eb706..2343120 100644 --- a/internal/processor/processor.go +++ b/internal/processor/processor.go @@ -28,6 +28,7 @@ type Processor struct { translator *translation.Translator translationCache *translation.TranslationCache phoneticFetcher *phonetic.Fetcher + randomIntn func(n int) int } var newOpenAIImageClient = func(config *image.OpenAIConfig) image.ImageSearcher { @@ -51,6 +52,7 @@ func NewProcessor(flags *cli.Flags) *Processor { translator: translation.NewTranslator(&translation.Config{Provider: translationProvider, OpenAIKey: openAIKey, GoogleAPIKey: googleAPIKey}), translationCache: translation.NewTranslationCache(), phoneticFetcher: phonetic.NewFetcher(&phonetic.Config{Provider: phoneticProvider, OpenAIKey: openAIKey, GoogleAPIKey: googleAPIKey}), + randomIntn: rand.Intn, } } @@ -317,12 +319,18 @@ func (p *Processor) audioVoiceForProvider() string { return voice } voices := p.audioVoicesForProvider() + if p.randomIntn != nil { + return voices[p.randomIntn(len(voices))] + } return voices[rand.Intn(len(voices))] default: if voice := p.openAIVoice(); voice != "" { return voice } voices := p.audioVoicesForProvider() + if p.randomIntn != nil { + return voices[p.randomIntn(len(voices))] + } return voices[rand.Intn(len(voices))] } } @@ -361,6 +369,8 @@ func (p *Processor) generateAudio(word string) error { fmt.Printf(" Retrying Gemini audio with voice: %s\n", candidate) } return p.generateAudioWithVoice(word, candidate) + }, func(candidate string) { + fmt.Printf(" Warning: Gemini returned no audio for voice %s\n", candidate) }) return err } @@ -411,6 +421,8 @@ func (p *Processor) generateAudioBgBg(front, back string) error { fmt.Printf(" Retrying Gemini audio with voice: %s\n", candidate) } return generatePair(candidate) + }, func(candidate string) { + fmt.Printf(" Warning: Gemini returned no audio for voice %s\n", candidate) }) return err } diff --git a/internal/processor/processor_test.go b/internal/processor/processor_test.go index 9f59bac..dcb8e06 100644 --- a/internal/processor/processor_test.go +++ b/internal/processor/processor_test.go @@ -1,6 +1,7 @@ package processor import ( + "bytes" "context" "errors" "fmt" @@ -100,6 +101,39 @@ func (f *fakeAudioProvider) IsAvailable() error { return nil } +func captureStdout(t *testing.T, fn func()) (output string) { + t.Helper() + + originalStdout := os.Stdout + reader, writer, err := os.Pipe() + if err != nil { + t.Fatalf("failed to create stdout pipe: %v", err) + } + + os.Stdout = writer + outputCh := make(chan string, 1) + defer func() { + os.Stdout = originalStdout + if err := writer.Close(); err != nil { + t.Fatalf("failed to close stdout pipe: %v", err) + } + output = <-outputCh + if err := reader.Close(); err != nil { + t.Fatalf("failed to close stdout reader: %v", err) + } + }() + + go func() { + var buf bytes.Buffer + _, _ = io.Copy(&buf, reader) + outputCh <- buf.String() + }() + + fn() + + return output +} + func TestNewProcessor(t *testing.T) { t.Setenv("OPENAI_API_KEY", "test-openai-key") t.Setenv("GOOGLE_API_KEY", "test-google-key") @@ -712,10 +746,18 @@ func TestGenerateGeminiAudioWithFallbacksRetriesAlternateVoice(t *testing.T) { flags.AudioProvider = "gemini" p := NewProcessor(flags) - if _, err := audio.RunWithVoiceFallbacks("Charon", func(voice string) error { - return p.generateAudioWithVoice("ябълка", voice) - }); err != nil { - t.Fatalf("RunWithVoiceFallbacks() unexpected error: %v", err) + p.randomIntn = func(int) int { return 0 } + output := captureStdout(t, func() { + if err := p.generateAudio("ябълка"); err != nil { + t.Fatalf("generateAudio() unexpected error: %v", err) + } + }) + + if !strings.Contains(output, " Warning: Gemini returned no audio for voice Charon") { + t.Fatalf("stdout missing indented Gemini warning: %q", output) + } + if !strings.Contains(output, " Retrying Gemini audio with voice: Kore") { + t.Fatalf("stdout missing retry message: %q", output) } if got, want := strings.Join(attemptedVoices, ","), "Charon,Kore"; got != want { |
