Skip to main content

Transcription

The Go AI SDK provides the ai.Transcribe() function to transcribe audio using a transcription model.

package main

import (
"context"
"fmt"
"log"
"os"

"github.com/digitallysavvy/go-ai/pkg/ai"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
)

func main() {
ctx := context.Background()

provider := openai.New(openai.Config{APIKey: os.Getenv("OPENAI_API_KEY")})
transcriptionModel, _ := provider.TranscriptionModel("whisper-1")

// Read audio file
audioData, err := os.ReadFile("audio.mp3")
if err != nil {
log.Fatal(err)
}

result, err := ai.Transcribe(ctx, ai.TranscribeOptions{
Model: transcriptionModel,
Audio: audioData,
})
if err != nil {
log.Fatal(err)
}

fmt.Println("Transcript:", result.Text)
}

Audio Input Formats​

Transcribe accepts raw bytes, base64/base64url data, or a URL that the SDK downloads before calling the provider.

From File​

audioData, err := os.ReadFile("audio.mp3")
if err != nil {
log.Fatal(err)
}

result, _ := ai.Transcribe(ctx, ai.TranscribeOptions{
Model: transcriptionModel,
Audio: audioData,
})

From URL​

result, _ := ai.Transcribe(ctx, ai.TranscribeOptions{
Model: transcriptionModel,
AudioURL: "https://example.com/audio.mp3",
})

From Base64 String​

base64Audio := "UklGRi..." // base64 encoded audio

result, _ := ai.Transcribe(ctx, ai.TranscribeOptions{
Model: transcriptionModel,
AudioBase64: base64Audio,
})

Accessing Results​

To access the generated transcript and metadata:

result, err := ai.Transcribe(ctx, ai.TranscribeOptions{
Model: transcriptionModel,
Audio: audioData,
})
if err != nil {
log.Fatal(err)
}

// Transcript text
text := result.Text // e.g. "Hello, world!"

// Segments with timestamps (if available)
for _, segment := range result.Segments {
fmt.Printf("[%.2f-%.2f] %s\n", segment.StartSecond, segment.EndSecond, segment.Text)
}

// Detected language (if available)
if result.Language != "" {
fmt.Printf("Detected language: %s\n", result.Language)
}

// Audio duration
if result.DurationInSeconds != nil {
fmt.Printf("Duration: %.2f seconds\n", *result.DurationInSeconds)
}

Settings​

Provider Options​

Provider-specific controls such as language hints, diarization, timestamp granularity, and formatting are passed through ProviderOptions under the provider key:

result, err := ai.Transcribe(ctx, ai.TranscribeOptions{
Model: transcriptionModel,
Audio: audioData,
ProviderOptions: map[string]interface{}{
"openai": map[string]interface{}{
"language": "en",
},
},
})

Timestamps​

Providers return timestamped transcript portions through Segments when the provider response includes them. Request provider-specific timestamp behavior with ProviderOptions:

result, err := ai.Transcribe(ctx, ai.TranscribeOptions{
Model: transcriptionModel,
Audio: audioData,
ProviderOptions: map[string]interface{}{
"openai": map[string]interface{}{
"timestampGranularities": []string{"word"},
},
},
})

for _, segment := range result.Segments {
fmt.Printf("[%.2f-%.2f] %s\n", segment.StartSecond, segment.EndSecond, segment.Text)
}

MIME Type​

Specify the audio format explicitly:

result, err := ai.Transcribe(ctx, ai.TranscribeOptions{
Model: transcriptionModel,
Audio: audioData,
MimeType: "audio/mpeg", // or "audio/wav", "audio/webm", etc.
})

Provider-Specific Settings​

Transcription models often have provider- or model-specific settings which you can set using the ProviderOptions parameter:

result, err := ai.Transcribe(ctx, ai.TranscribeOptions{
Model: transcriptionModel,
Audio: audioData,
MimeType: "audio/wav",
ProviderOptions: map[string]interface{}{
"xai": map[string]interface{}{
"language": "en",
"diarize": true,
"keyterm": []string{"Go-AI", "Grok"},
"fillerWords": true,
},
},
})

Timeouts with Context​

Use Go's context for timeouts and cancellation:

import "time"

// Timeout after 30 seconds
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

result, err := ai.Transcribe(ctx, ai.TranscribeOptions{
Model: transcriptionModel,
Audio: audioData,
})
if err != nil {
if ctx.Err() == context.DeadlineExceeded {
fmt.Println("Transcription timed out")
}
log.Fatal(err)
}

Custom Headers​

Add custom headers to the transcription request:

result, err := ai.Transcribe(ctx, ai.TranscribeOptions{
Model: transcriptionModel,
Audio: audioData,
Headers: map[string]string{
"X-Custom-Header": "custom-value",
},
})

Warnings​

If the model returns warnings, they will be available in the Warnings field:

result, err := ai.Transcribe(ctx, ai.TranscribeOptions{
Model: transcriptionModel,
Audio: audioData,
})
if err != nil {
log.Fatal(err)
}

for _, warning := range result.Warnings {
fmt.Printf("Warning: %s\n", warning.Message)
}

Error Handling​

When ai.Transcribe() cannot generate a valid transcript, it returns an error:

import "errors"

result, err := ai.Transcribe(ctx, ai.TranscribeOptions{
Model: transcriptionModel,
Audio: audioData,
})
if err != nil {
if ai.IsNoTranscriptGeneratedError(err) {
fmt.Println("No transcript was generated")
var noTranscript *ai.NoTranscriptGeneratedError
if errors.As(err, &noTranscript) {
fmt.Printf("Responses: %d\n", len(noTranscript.Responses))
}
} else {
fmt.Printf("Transcription error: %v\n", err)
}
return
}

Common transcription errors:

  • NoTranscriptGeneratedError: The model returned no transcript text
  • ProviderError: Provider-specific HTTP or API failure
  • InvalidArgumentError: Invalid input such as negative MaxRetries or invalid base64 audio
  • context.DeadlineExceeded: Operation timed out
  • context.Canceled: Operation was canceled

Practical Examples​

Transcribe with Automatic Language Detection​

func transcribeAudio(ctx context.Context, model provider.TranscriptionModel, audioPath string) (*ai.TranscribeResult, error) {
audioData, err := os.ReadFile(audioPath)
if err != nil {
return nil, fmt.Errorf("failed to read audio: %w", err)
}

result, err := ai.Transcribe(ctx, ai.TranscribeOptions{
Model: model,
Audio: audioData,
})
if err != nil {
return nil, err
}

fmt.Printf("Detected language: %s\n", result.Language)
if result.DurationInSeconds != nil {
fmt.Printf("Duration: %.2f seconds\n", *result.DurationInSeconds)
}

return result, nil
}

Batch Transcription​

func transcribeMultipleFiles(ctx context.Context, model provider.TranscriptionModel, audioPaths []string) ([]string, error) {
transcripts := make([]string, len(audioPaths))

for i, path := range audioPaths {
audioData, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to read %s: %w", path, err)
}

result, err := ai.Transcribe(ctx, ai.TranscribeOptions{
Model: model,
Audio: audioData,
})
if err != nil {
return nil, fmt.Errorf("failed to transcribe %s: %w", path, err)
}

transcripts[i] = result.Text
fmt.Printf("Transcribed %s: %d characters\n", path, len(result.Text))
}

return transcripts, nil
}

Parallel Transcription with Goroutines​

func transcribeParallel(ctx context.Context, model provider.TranscriptionModel, audioPaths []string) ([]string, error) {
type result struct {
index int
text string
err error
}

resultChan := make(chan result, len(audioPaths))
transcripts := make([]string, len(audioPaths))

// Start goroutines
for i, path := range audioPaths {
go func(index int, audioPath string) {
audioData, err := os.ReadFile(audioPath)
if err != nil {
resultChan <- result{index: index, err: err}
return
}

transcription, err := ai.Transcribe(ctx, ai.TranscribeOptions{
Model: model,
Audio: audioData,
})
if err != nil {
resultChan <- result{index: index, err: err}
return
}

resultChan <- result{index: index, text: transcription.Text}
}(i, path)
}

// Collect results
for i := 0; i < len(audioPaths); i++ {
res := <-resultChan
if res.err != nil {
return nil, fmt.Errorf("transcription failed for file %d: %w", res.index, res.err)
}
transcripts[res.index] = res.text
}

return transcripts, nil
}

Transcription with Timestamps​

func transcribeWithTimestamps(ctx context.Context, model provider.TranscriptionModel, audioPath string) error {
audioData, err := os.ReadFile(audioPath)
if err != nil {
return err
}

result, err := ai.Transcribe(ctx, ai.TranscribeOptions{
Model: model,
Audio: audioData,
ProviderOptions: map[string]interface{}{
"openai": map[string]interface{}{
"timestampGranularities": []string{"word"},
},
},
})
if err != nil {
return err
}

// Print timestamped transcript
fmt.Println("Timestamped Transcript:")
fmt.Println("======================")

for _, segment := range result.Segments {
minutes := int(segment.StartSecond) / 60
seconds := int(segment.StartSecond) % 60
fmt.Printf("[%02d:%02d] %s\n", minutes, seconds, segment.Text)
}

return nil
}

Transcription with Retry Logic​

func transcribeWithRetry(ctx context.Context, model provider.TranscriptionModel, audioData []byte, maxRetries int) (*ai.TranscribeResult, error) {
return ai.Transcribe(ctx, ai.TranscribeOptions{
Model: model,
Audio: audioData,
MaxRetries: &maxRetries,
})
}

Save Transcript to File​

func transcribeAndSave(ctx context.Context, model provider.TranscriptionModel, audioPath, outputPath string) error {
audioData, err := os.ReadFile(audioPath)
if err != nil {
return fmt.Errorf("failed to read audio: %w", err)
}

result, err := ai.Transcribe(ctx, ai.TranscribeOptions{
Model: model,
Audio: audioData,
})
if err != nil {
return fmt.Errorf("transcription failed: %w", err)
}

// Save to file
err = os.WriteFile(outputPath, []byte(result.Text), 0644)
if err != nil {
return fmt.Errorf("failed to write transcript: %w", err)
}

fmt.Printf("Transcript saved to %s\n", outputPath)
return nil
}

Transcription Models​

Several providers offer transcription models:

ProviderModelNotes
OpenAIwhisper-1General-purpose transcription
OpenAIgpt-4o-transcribeGPT-4o with transcription
OpenAIgpt-4o-mini-transcribeFaster, cost-effective
ElevenLabsscribe_v1High-quality transcription
ElevenLabsscribe_v1_experimentalExperimental features
Groqwhisper-large-v3-turboFast transcription
Groqwhisper-large-v3High accuracy
Azure OpenAIwhisper-1Azure-hosted Whisper
Azure OpenAIgpt-4o-transcribeAzure GPT-4o
Azure OpenAIgpt-4o-mini-transcribeAzure GPT-4o Mini
Rev.aimachineStandard machine transcription
Rev.ailow_costBudget-friendly option
Rev.aifusionHybrid human+machine
DeepgrambaseBase model (+ variants)
DeepgramenhancedEnhanced accuracy
DeepgramnovaLatest model
Deepgramnova-2Nova 2nd generation
Deepgramnova-3Nova 3rd generation
GladiadefaultStandard transcription
AssemblyAIbestHighest accuracy
AssemblyAInanoFast, lightweight
FalwhisperWhisper model
FalwizperOptimized variant

Example with Different Providers​

// OpenAI
openaiProvider := openai.New(openai.Config{APIKey: os.Getenv("OPENAI_API_KEY")})
whisperModel, _ := openaiProvider.TranscriptionModel("whisper-1")

// Groq
groqProvider := groq.New(groq.Config{APIKey: os.Getenv("GROQ_API_KEY")})
groqModel, _ := groqProvider.TranscriptionModel("whisper-large-v3-turbo")

// AssemblyAI
assemblyProvider := assemblyai.New(assemblyai.Config{APIKey: os.Getenv("ASSEMBLYAI_API_KEY")})
assemblyModel, _ := assemblyProvider.TranscriptionModel("best")

// Use any model with the same API
result, _ := ai.Transcribe(ctx, ai.TranscribeOptions{
Model: whisperModel, // or groqModel, or assemblyModel
Audio: audioData,
})

Supported Audio Formats​

Most transcription providers support common audio formats:

  • MP3 (audio/mpeg, audio/mp3)
  • WAV (audio/wav)
  • M4A (audio/mp4, audio/m4a)
  • WebM (audio/webm)
  • OGG (audio/ogg)
  • FLAC (audio/flac)

Maximum file sizes and durations vary by provider. Check provider documentation for specific limits.

Best Practices​

  1. Choose the Right Model: Balance speed, accuracy, and cost based on your needs
  2. Set Timeouts: Transcription can be slow; always use context with timeout
  3. Handle Errors: Implement proper error handling and retry logic
  4. Provide Language Hints: When known, language hints improve accuracy
  5. Use Appropriate Format: Some providers work better with specific audio formats
  6. Monitor Costs: Track transcription usage and costs
  7. Respect Rate Limits: Implement rate limiting for bulk transcription
  8. Use Parallel Processing: For multiple files, use goroutines
  9. Validate Audio: Check file size and format before transcription
  10. Cache Results: Store transcripts to avoid redundant API calls

Performance Tips​

Optimize Audio Files​

// Check file size before transcription
func checkAudioSize(path string, maxSizeMB int) error {
info, err := os.Stat(path)
if err != nil {
return err
}

sizeMB := float64(info.Size()) / (1024 * 1024)
if sizeMB > float64(maxSizeMB) {
return fmt.Errorf("audio file too large: %.2f MB (max: %d MB)", sizeMB, maxSizeMB)
}

return nil
}

Rate Limiting​

import "golang.org/x/time/rate"

// Create rate limiter (e.g., 10 requests per minute)
limiter := rate.NewLimiter(rate.Every(6*time.Second), 1)

func transcribeWithRateLimit(ctx context.Context, model provider.TranscriptionModel, audioData []byte) (*ai.TranscribeResult, error) {
// Wait for rate limiter
if err := limiter.Wait(ctx); err != nil {
return nil, err
}

return ai.Transcribe(ctx, ai.TranscribeOptions{
Model: model,
Audio: audioData,
})
}

Next Steps​

See Also​