x

Programs, configuration and documentation that don't fit anywhere else
Log | Files | Refs | README | LICENSE

transcription.go (2697B)


      1 package openai
      2 
      3 import (
      4 	"bytes"
      5 	"encoding/json"
      6 	"fmt"
      7 	"io"
      8 	"mime/multipart"
      9 	"net/http"
     10 	"os"
     11 	"strconv"
     12 	"strings"
     13 )
     14 
     15 type Transcription struct {
     16 	Language string
     17 	Text     string
     18 	Segments []struct {
     19 		Text  string
     20 		Start float32
     21 		End   float32
     22 	}
     23 }
     24 
     25 func (t Transcription) SubRip() string {
     26 	b := &strings.Builder{}
     27 	for i := 0; i < len(t.Segments); i++ {
     28 		fmt.Fprintln(b, strconv.Itoa(i+1))
     29 		fmt.Fprintf(b, "%s --> %s\n", secondsToTimecode(t.Segments[i].Start), secondsToTimecode(t.Segments[i].End))
     30 		// TODO: trim space?
     31 		fmt.Fprintln(b, t.Segments[i].Text)
     32 		fmt.Fprintln(b)
     33 	}
     34 	return b.String()
     35 }
     36 
     37 // TODO: use time.Duration with rounding to milliseconds instead.
     38 func secondsToTimecode(seconds float32) string {
     39 	totalMs := int(seconds * 1000)
     40 	hours := totalMs / 3600000
     41 	totalMs %= 3600000
     42 	minutes := totalMs / 60000
     43 	totalMs %= 60000
     44 	secondsPart := totalMs / 1000
     45 	ms := totalMs % 1000
     46 
     47 	// Format with leading zeros
     48 	return fmt.Sprintf("%02d:%02d:%02d,%03d", hours, minutes, secondsPart, ms)
     49 }
     50 
     51 // language must be in [ISO-639-1] format such as "en" for English.
     52 //
     53 // [ISO-639-1]: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
     54 func (c *Client) Transcribe(r io.Reader, language, model string, timestamps bool) (*Transcription, error) {
     55 	buf := &bytes.Buffer{}
     56 	w := multipart.NewWriter(buf)
     57 	if err := w.WriteField("model", model); err != nil {
     58 		return nil, err
     59 	}
     60 	if err := w.WriteField("language", language); err != nil {
     61 		return nil, err
     62 	}
     63 	if timestamps {
     64 		if err := w.WriteField("timestamp_granularities", "segment"); err != nil {
     65 			return nil, err
     66 		}
     67 	}
     68 	field, err := w.CreateFormFile("file", "sample.mp3")
     69 	if err != nil {
     70 		return nil, err
     71 	}
     72 	if _, err = io.Copy(field, r); err != nil {
     73 		return nil, fmt.Errorf("copy audio to request body: %w", err)
     74 	}
     75 	if err := w.Close(); err != nil {
     76 		return nil, err
     77 	}
     78 
     79 	req, err := http.NewRequest(http.MethodPost, c.BaseURL+"/audio/transcriptions", buf)
     80 	if err != nil {
     81 		return nil, err
     82 	}
     83 	req.Header.Set("Content-Type", w.FormDataContentType())
     84 	resp, err := c.do(req)
     85 	if err != nil {
     86 		return nil, err
     87 	}
     88 	defer resp.Body.Close()
     89 	if resp.StatusCode == http.StatusUnauthorized {
     90 		return nil, fmt.Errorf("unauthorised")
     91 	} else if resp.StatusCode >= 400 && resp.StatusCode <= 499 {
     92 		var aerr apiError
     93 
     94 		io.Copy(os.Stderr, resp.Body)
     95 		return nil, fmt.Errorf(resp.Status)
     96 
     97 		if err := json.NewDecoder(resp.Body).Decode(&aerr); err != nil {
     98 			return nil, fmt.Errorf(resp.Status)
     99 		}
    100 		return nil, aerr
    101 	}
    102 
    103 	var tscript Transcription
    104 	if err := json.NewDecoder(resp.Body).Decode(&tscript); err != nil {
    105 		return nil, fmt.Errorf("decode transciption response: %w", err)
    106 	}
    107 	return &tscript, nil
    108 }