package nexara import ( "bytes" "context" "encoding/json" "fmt" "io" "mime/multipart" "net/http" "os" "path/filepath" "strings" "time" "github.com/postmet/transcribe/internal/models" ) type Client struct { apiURL string apiKey string model string httpClient *http.Client } func New(baseURL, apiKey, model string, timeout time.Duration) *Client { baseURL = strings.TrimRight(baseURL, "/") return &Client{ apiURL: baseURL + "/api/v1/audio/transcriptions", apiKey: apiKey, model: model, httpClient: &http.Client{ Timeout: timeout, }, } } func (c *Client) TranscribeFile(ctx context.Context, path string) (text, language string, segments []models.Segment, err error) { f, err := os.Open(path) if err != nil { return "", "", nil, fmt.Errorf("open file: %w", err) } defer f.Close() body := &bytes.Buffer{} writer := multipart.NewWriter(body) part, err := writer.CreateFormFile("file", filepath.Base(path)) if err != nil { return "", "", nil, err } if _, err := io.Copy(part, f); err != nil { return "", "", nil, err } if c.model != "" { if err := writer.WriteField("model", c.model); err != nil { return "", "", nil, err } } if err := writer.WriteField("response_format", "json"); err != nil { return "", "", nil, err } if err := writer.Close(); err != nil { return "", "", nil, err } req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.apiURL, body) if err != nil { return "", "", nil, err } req.Header.Set("Content-Type", writer.FormDataContentType()) req.Header.Set("Authorization", "Bearer "+c.apiKey) resp, err := c.httpClient.Do(req) if err != nil { return "", "", nil, fmt.Errorf("request: %w", err) } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { return "", "", nil, err } if resp.StatusCode != http.StatusOK { return "", "", nil, fmt.Errorf("status %d: %s", resp.StatusCode, string(respBody)) } var raw map[string]any if err := json.Unmarshal(respBody, &raw); err != nil { return "", "", nil, fmt.Errorf("parse: %w", err) } if t, ok := raw["text"].(string); ok { text = t } if lang, ok := raw["language"].(string); ok { language = lang } if segs, ok := raw["segments"].([]any); ok { for _, s := range segs { m, ok := s.(map[string]any) if !ok { continue } var seg models.Segment if v, ok := m["start"].(float64); ok { seg.Start = v } if v, ok := m["end"].(float64); ok { seg.End = v } if v, ok := m["text"].(string); ok { seg.Text = v } segments = append(segments, seg) } } return text, language, segments, nil }