diff options
| author | Paul Buetow <paul@buetow.org> | 2026-04-02 16:36:25 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-04-02 16:36:25 +0300 |
| commit | 6bd23a588bacee2e8c75f477150b7e2d345002ff (patch) | |
| tree | 67bfa78afe206e7560350bdd52c60e0e80ec3243 /internal/audio/gemini_provider.go | |
| parent | 6ce9123de04ffff961cbf1da73648679216ff637 (diff) | |
Release v0.9.0v0.9.0
Diffstat (limited to 'internal/audio/gemini_provider.go')
| -rw-r--r-- | internal/audio/gemini_provider.go | 63 |
1 files changed, 54 insertions, 9 deletions
diff --git a/internal/audio/gemini_provider.go b/internal/audio/gemini_provider.go index 5662454..2893278 100644 --- a/internal/audio/gemini_provider.go +++ b/internal/audio/gemini_provider.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "os" + "os/exec" "path/filepath" "strings" @@ -21,6 +22,11 @@ const ( geminiTTSBitsPerSample = 16 ) +var ErrGeminiNoAudioData = errors.New("no audio data returned from Gemini") + +var execLookPath = exec.LookPath +var execCommand = exec.Command + // GeminiProvider implements Provider interface for Gemini TTS. type GeminiProvider struct { client *genai.Client @@ -173,7 +179,12 @@ func extractAudioData(response *genai.GenerateContentResponse) ([]byte, string, } } - return nil, "", errors.New("no audio data returned from Gemini") + return nil, "", ErrGeminiNoAudioData +} + +// IsGeminiNoAudioDataError reports whether the error means Gemini returned no audio payload. +func IsGeminiNoAudioDataError(err error) bool { + return errors.Is(err, ErrGeminiNoAudioData) } func writeGeminiAudioFile(outputFile string, audioData []byte, mimeType string) error { @@ -182,20 +193,22 @@ func writeGeminiAudioFile(outputFile string, audioData []byte, mimeType string) } ext := strings.ToLower(filepath.Ext(outputFile)) - if ext != ".wav" { - return fmt.Errorf("gemini TTS only supports .wav output files, got %q", outputFile) - } - encoded, err := encodePCMAsWAV(audioData) if err != nil { return err } - if err := os.WriteFile(outputFile, encoded, 0644); err != nil { - return fmt.Errorf("failed to write output file: %w", err) + switch ext { + case ".wav": + if err := os.WriteFile(outputFile, encoded, 0644); err != nil { + return fmt.Errorf("failed to write output file: %w", err) + } + return nil + case ".mp3": + return transcodeWAVToMP3(encoded, outputFile) + default: + return fmt.Errorf("gemini TTS only supports .wav and .mp3 output files, got %q", outputFile) } - - return nil } func ensureOutputDirectory(outputFile string) error { @@ -263,3 +276,35 @@ func encodePCMAsWAV(pcmData []byte) ([]byte, error) { return buffer.Bytes(), nil } + +func transcodeWAVToMP3(wavData []byte, outputFile string) error { + ffmpegPath, err := execLookPath("ffmpeg") + if err != nil { + return fmt.Errorf("ffmpeg is required to convert Gemini audio to mp3: %w", err) + } + + cmd := execCommand( + ffmpegPath, + "-nostdin", + "-hide_banner", + "-loglevel", "error", + "-y", + "-f", "wav", + "-i", "pipe:0", + "-codec:a", "libmp3lame", + "-q:a", "4", + outputFile, + ) + cmd.Stdin = bytes.NewReader(wavData) + + output, err := cmd.CombinedOutput() + if err != nil { + message := strings.TrimSpace(string(output)) + if message == "" { + message = err.Error() + } + return fmt.Errorf("failed to convert Gemini audio to mp3: %s", message) + } + + return nil +} |
