Add support for Groq Whisper API as an alternative to OpenAI: - groq-transcription: Fast transcription using whisper-large-v3 or whisper-large-v3-turbo - groq-translation: Translation to English (whisper-large-v3 only) Features: - New transcription adapters for both Groq services - Config validation with provider-specific model restrictions - API key support via config file or GROQ_API_KEY environment variable - Updated interactive configuration wizard with provider selection - Comprehensive test coverage for all providers - Shared WAV conversion utility for audio preprocessing Breaking changes: None Backward compatibility: Existing OpenAI configurations continue to work unchanged
98 lines
2.7 KiB
Go
98 lines
2.7 KiB
Go
package transcriber
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"log"
|
|
"time"
|
|
|
|
"github.com/sashabaranov/go-openai"
|
|
)
|
|
|
|
// GroqTranscriptionAdapter implements TranscriptionAdapter for Groq Whisper API
|
|
type GroqTranscriptionAdapter struct {
|
|
client *openai.Client
|
|
config Config
|
|
}
|
|
|
|
func NewGroqTranscriptionAdapter(config Config) *GroqTranscriptionAdapter {
|
|
clientConfig := openai.DefaultConfig(config.APIKey)
|
|
clientConfig.BaseURL = "https://api.groq.com/openai/v1"
|
|
client := openai.NewClientWithConfig(clientConfig)
|
|
|
|
return &GroqTranscriptionAdapter{
|
|
client: client,
|
|
config: config,
|
|
}
|
|
}
|
|
|
|
func (a *GroqTranscriptionAdapter) Transcribe(ctx context.Context, audioData []byte) (string, error) {
|
|
if len(audioData) == 0 {
|
|
return "", nil
|
|
}
|
|
|
|
// Convert raw PCM to WAV format
|
|
wavData, err := convertToWAV(audioData)
|
|
if err != nil {
|
|
return "", fmt.Errorf("convert to WAV: %w", err)
|
|
}
|
|
|
|
// Create transcription request
|
|
req := openai.AudioRequest{
|
|
Model: a.config.Model,
|
|
Reader: bytes.NewReader(wavData),
|
|
FilePath: "audio.wav",
|
|
Language: a.config.Language,
|
|
}
|
|
|
|
start := time.Now()
|
|
resp, err := a.client.CreateTranscription(ctx, req)
|
|
duration := time.Since(start)
|
|
|
|
if err != nil {
|
|
log.Printf("groq-transcription-adapter: API call failed after %v: %v", duration, err)
|
|
return "", fmt.Errorf("groq transcription: %w", err)
|
|
}
|
|
|
|
log.Printf("groq-transcription-adapter: transcribed %d bytes in %v: %q", len(audioData), duration, resp.Text)
|
|
return resp.Text, nil
|
|
}
|
|
|
|
// convertToWAV converts raw 16-bit PCM audio to WAV format
|
|
func convertToWAV(rawAudio []byte) ([]byte, error) {
|
|
var buf bytes.Buffer
|
|
|
|
const sampleRate = 16000
|
|
const channels = 1
|
|
const bitsPerSample = 16
|
|
const byteRate = sampleRate * channels * bitsPerSample / 8
|
|
const blockAlign = channels * bitsPerSample / 8
|
|
|
|
dataSize := len(rawAudio)
|
|
fileSize := 36 + dataSize
|
|
|
|
// WAV header
|
|
buf.WriteString("RIFF")
|
|
binary.Write(&buf, binary.LittleEndian, uint32(fileSize))
|
|
buf.WriteString("WAVE")
|
|
|
|
// fmt chunk
|
|
buf.WriteString("fmt ")
|
|
binary.Write(&buf, binary.LittleEndian, uint32(16)) // fmt chunk size
|
|
binary.Write(&buf, binary.LittleEndian, uint16(1)) // PCM format
|
|
binary.Write(&buf, binary.LittleEndian, uint16(channels)) // number of channels
|
|
binary.Write(&buf, binary.LittleEndian, uint32(sampleRate)) // sample rate
|
|
binary.Write(&buf, binary.LittleEndian, uint32(byteRate)) // byte rate
|
|
binary.Write(&buf, binary.LittleEndian, uint16(blockAlign)) // block align
|
|
binary.Write(&buf, binary.LittleEndian, uint16(bitsPerSample)) // bits per sample
|
|
|
|
// data chunk
|
|
buf.WriteString("data")
|
|
binary.Write(&buf, binary.LittleEndian, uint32(dataSize))
|
|
buf.Write(rawAudio)
|
|
|
|
return buf.Bytes(), nil
|
|
}
|