# Transcription

The Go AI SDK provides the [`ai.Transcribe()`](https://goaisdk.com/docs/reference/ai/transcribe.md) function to transcribe audio using a transcription model.

```go
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

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

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

### From URL

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

### From Base64 String

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

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

## Accessing Results

To access the generated transcript and metadata:

```go
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:

```go
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`:

```go
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:

```go
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:

```go
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:

```go
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:

```go
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:

```go
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:

```go
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

```go
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

```go
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

```go
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

```go
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

```go
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

```go
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:

| Provider | Model | Notes |
|----------|-------|-------|
| **OpenAI** | `whisper-1` | General-purpose transcription |
| **OpenAI** | `gpt-4o-transcribe` | GPT-4o with transcription |
| **OpenAI** | `gpt-4o-mini-transcribe` | Faster, cost-effective |
| **ElevenLabs** | `scribe_v1` | High-quality transcription |
| **ElevenLabs** | `scribe_v1_experimental` | Experimental features |
| **Groq** | `whisper-large-v3-turbo` | Fast transcription |
| **Groq** | `whisper-large-v3` | High accuracy |
| **Azure OpenAI** | `whisper-1` | Azure-hosted Whisper |
| **Azure OpenAI** | `gpt-4o-transcribe` | Azure GPT-4o |
| **Azure OpenAI** | `gpt-4o-mini-transcribe` | Azure GPT-4o Mini |
| **Rev.ai** | `machine` | Standard machine transcription |
| **Rev.ai** | `low_cost` | Budget-friendly option |
| **Rev.ai** | `fusion` | Hybrid human+machine |
| **Deepgram** | `base` | Base model (+ variants) |
| **Deepgram** | `enhanced` | Enhanced accuracy |
| **Deepgram** | `nova` | Latest model |
| **Deepgram** | `nova-2` | Nova 2nd generation |
| **Deepgram** | `nova-3` | Nova 3rd generation |
| **Gladia** | `default` | Standard transcription |
| **AssemblyAI** | `best` | Highest accuracy |
| **AssemblyAI** | `nano` | Fast, lightweight |
| **Fal** | `whisper` | Whisper model |
| **Fal** | `wizper` | Optimized variant |

### Example with Different Providers

```go
// 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

```go
// 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

```go
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

- Learn about [Speech Generation](https://goaisdk.com/docs/ai-sdk-core/speech.md)
- Explore [Error Handling](https://goaisdk.com/docs/ai-sdk-core/error-handling.md)
- See [Provider Documentation](https://goaisdk.com/docs/providers/overview.md)

## See Also

- [Providers](https://goaisdk.com/docs/providers/overview.md)
- [Settings](https://goaisdk.com/docs/ai-sdk-core/settings.md)
- [Error Handling](https://goaisdk.com/docs/ai-sdk-core/error-handling.md)
- [API Reference: Transcribe](https://goaisdk.com/docs/reference/ai/transcribe.md)
