summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-04-02 17:46:07 +0300
committerPaul Buetow <paul@buetow.org>2026-04-02 17:46:07 +0300
commitf3d057fec20aeef584cb6340c6a002280a019f15 (patch)
tree018be28f954243414969e8b8970fb3f3be8da112
parent1fc2ade33b72dd299fc987943ddd09dee7c609ad (diff)
task 002: restore Gemini warning output
-rw-r--r--internal/audio/fallbacks.go5
-rw-r--r--internal/audio/voices_test.go6
-rw-r--r--internal/gui/generator.go8
-rw-r--r--internal/gui/generator_test.go2
-rw-r--r--internal/processor/processor.go12
-rw-r--r--internal/processor/processor_test.go50
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 {