diff --git a/.golangci.yml b/.golangci.yml index ae49b45..db1dc2f 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -16,7 +16,7 @@ linters-settings: main: allow: - $gostd - - nhooyr.io/websocket + - github.com/coder/websocket - github.com/google - github.com/stretchr/testify - github.com/cenkalti/backoff diff --git a/README.md b/README.md index d22aacc..95e7332 100644 --- a/README.md +++ b/README.md @@ -17,15 +17,17 @@ A Go client library for accessing [AssemblyAI](https://assemblyai.com). ## Overview - [AssemblyAI Go SDK](#assemblyai-go-sdk) - - [Overview](#overview) - - [Documentation](#documentation) - - [Quickstart](#quickstart) - - [Installation](#installation) - - [Examples](#examples) - - [Core Transcription](#core-transcription) - - [Audio Intelligence](#audio-intelligence) - - [Real-Time Transcription](#real-time-transcription) - - [Playgrounds](#playgrounds) + - [Overview](#overview) + - [Documentation](#documentation) + - [Quickstart](#quickstart) + - [Installation](#installation) + - [Examples](#examples) + - [Core Transcription](#core-transcription) + - [Audio Intelligence](#audio-intelligence) + - [Real-Time Transcription](#real-time-transcription) + - [Playgrounds](#playgrounds) + - [Tips and tricks](#tips-and-tricks) + - [Inspect API errors](#inspect-api-errors) ## Documentation @@ -54,28 +56,28 @@ Before you begin, you need to have your API key. If you don't have one yet, [**s package main import ( - "context" - "log" - "os" + "context" + "log" + "os" - "github.com/AssemblyAI/assemblyai-go-sdk" + "github.com/AssemblyAI/assemblyai-go-sdk" ) func main() { - apiKey := os.Getenv("ASSEMBLYAI_API_KEY") + apiKey := os.Getenv("ASSEMBLYAI_API_KEY") - ctx := context.Background() + ctx := context.Background() - audioURL := "https://example.org/audio.mp3" + audioURL := "https://example.org/audio.mp3" - client := assemblyai.NewClient(apiKey) + client := assemblyai.NewClient(apiKey) - transcript, err := client.Transcripts.TranscribeFromURL(ctx, audioURL, nil) - if err != nil { - log.Fatal("Something bad happened:", err) - } + transcript, err := client.Transcripts.TranscribeFromURL(ctx, audioURL, nil) + if err != nil { + log.Fatal("Something bad happened:", err) + } - log.Println(*transcript.Text) + log.Println(*transcript.Text) } ``` @@ -87,32 +89,32 @@ func main() { package main import ( - "context" - "log" - "os" + "context" + "log" + "os" - "github.com/AssemblyAI/assemblyai-go-sdk" + "github.com/AssemblyAI/assemblyai-go-sdk" ) func main() { - apiKey := os.Getenv("ASSEMBLYAI_API_KEY") + apiKey := os.Getenv("ASSEMBLYAI_API_KEY") - ctx := context.Background() + ctx := context.Background() - client := assemblyai.NewClient(apiKey) + client := assemblyai.NewClient(apiKey) - f, err := os.Open("./my-local-audio-file.wav") - if err != nil { - log.Fatal("Couldn't open audio file:", err) - } - defer f.Close() + f, err := os.Open("./my-local-audio-file.wav") + if err != nil { + log.Fatal("Couldn't open audio file:", err) + } + defer f.Close() - transcript, err := client.Transcripts.TranscribeFromReader(ctx, f, nil) - if err != nil { - log.Fatal("Something bad happened:", err) - } + transcript, err := client.Transcripts.TranscribeFromReader(ctx, f, nil) + if err != nil { + log.Fatal("Something bad happened:", err) + } - log.Println(*transcript.Text) + log.Println(*transcript.Text) } ``` @@ -127,36 +129,36 @@ func main() { package main import ( - "context" - "log" - "os" + "context" + "log" + "os" - "github.com/AssemblyAI/assemblyai-go-sdk" + "github.com/AssemblyAI/assemblyai-go-sdk" ) func main() { - apiKey := os.Getenv("ASSEMBLYAI_API_KEY") + apiKey := os.Getenv("ASSEMBLYAI_API_KEY") - ctx := context.Background() + ctx := context.Background() - audioURL := "https://example.org/audio.mp3" + audioURL := "https://example.org/audio.mp3" - client := assemblyai.NewClient(apiKey) + client := assemblyai.NewClient(apiKey) - opts := &assemblyai.TranscriptParams{ - EntityDetection: assemblyai.Bool(true), - } + opts := &assemblyai.TranscriptParams{ + EntityDetection: assemblyai.Bool(true), + } - transcript, err := client.Transcripts.TranscribeFromURL(ctx, audioURL, opts) - if err != nil { - log.Fatal("Something bad happened:", err) - } + transcript, err := client.Transcripts.TranscribeFromURL(ctx, audioURL, opts) + if err != nil { + log.Fatal("Something bad happened:", err) + } - for _, entity := range transcript.Entities { - log.Println(*entity.Text) - log.Println(entity.EntityType) - log.Printf("Timestamp: %v - %v", *entity.Start, *entity.End) - } + for _, entity := range transcript.Entities { + log.Println(*entity.Text) + log.Println(entity.EntityType) + log.Printf("Timestamp: %v - %v", *entity.Start, *entity.End) + } } ``` @@ -172,3 +174,22 @@ Visit one of our Playgrounds: - [LeMUR Playground](https://www.assemblyai.com/playground/v2/source) - [Transcription Playground](https://www.assemblyai.com/playground) + +## Tips and tricks + +### Inspect API errors + +If you receive an API error, you can inspect the HTTP response returned by the API for more details: + +```go +transcript, err := client.Transcripts.TranscribeFromURL(ctx, audioURL, nil) +if err != nil { + var apierr aai.APIError + if errors.As(err, &apierr) { + // apierr.Response is the *http.Response from the API call. + fmt.Println(apierr.Response.StatusCode) + } else { + // err is not an API error. + } +} +``` diff --git a/assemblyai.go b/assemblyai.go index 67b7b66..a35e801 100644 --- a/assemblyai.go +++ b/assemblyai.go @@ -6,13 +6,14 @@ import ( "encoding/json" "fmt" "io" + "mime" "net/http" "net/url" "os" ) const ( - version = "1.8.1" + version = "1.9.0" defaultBaseURLScheme = "https" defaultBaseURLHost = "api.assemblyai.com" ) @@ -137,23 +138,46 @@ func (c *Client) newRequest(ctx context.Context, method, path string, body io.Re return req, err } -func (c *Client) do(req *http.Request, v interface{}) (*http.Response, error) { +func (c *Client) do(req *http.Request, v interface{}) error { resp, err := c.httpClient.Do(req) if err != nil { - return nil, err + return err } defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { + contentType := resp.Header.Get("Content-Type") + + mimeType, _, err := mime.ParseMediaType(contentType) + if err != nil { + return err + } + + isJSONResponse := mimeType == "application/json" + isAPIError := resp.StatusCode < 200 || resp.StatusCode >= 400 + + if isAPIError { + var buf bytes.Buffer + + _, err := io.Copy(&buf, resp.Body) + if err != nil { + return err + } + var apierr APIError - if err := json.NewDecoder(resp.Body).Decode(&apierr); err != nil { - return nil, err + if isJSONResponse { + if err := json.Unmarshal(buf.Bytes(), &apierr); err != nil { + return err + } } + // Reset response body so that clients can read it again. + resp.Body = io.NopCloser(bytes.NewBuffer(buf.Bytes())) + apierr.Status = resp.StatusCode + apierr.Response = resp - return nil, apierr + return apierr } if v != nil { @@ -161,9 +185,11 @@ func (c *Client) do(req *http.Request, v interface{}) (*http.Response, error) { case *[]byte: *val, err = io.ReadAll(resp.Body) default: - err = json.NewDecoder(resp.Body).Decode(v) + if isJSONResponse { + err = json.NewDecoder(resp.Body).Decode(v) + } } } - return resp, err + return err } diff --git a/assemblyai_test.go b/assemblyai_test.go index d2bde37..e71309e 100644 --- a/assemblyai_test.go +++ b/assemblyai_test.go @@ -4,6 +4,7 @@ import ( "net/http" "net/http/httptest" "os" + "path/filepath" "testing" "github.com/stretchr/testify/require" @@ -22,6 +23,10 @@ func setup() (*Client, *http.ServeMux, func()) { func writeFileResponse(t *testing.T, w http.ResponseWriter, filename string) { t.Helper() + if filepath.Ext(filename) == ".json" { + w.Header().Set("Content-Type", "application/json") + } + b, err := os.ReadFile(filename) require.NoError(t, err) diff --git a/errors.go b/errors.go index 82e427d..93e709a 100644 --- a/errors.go +++ b/errors.go @@ -1,9 +1,13 @@ package assemblyai +import "net/http" + // APIError represents an error returned by the AssemblyAI API. type APIError struct { Status int `json:"-"` Message string `json:"error"` + + Response *http.Response `json:"-"` } // Error returns the API error message. diff --git a/examples/realtime/go.mod b/examples/realtime/go.mod index 1a99bf9..979015b 100644 --- a/examples/realtime/go.mod +++ b/examples/realtime/go.mod @@ -1,4 +1,4 @@ -module github.com/AssemblyAI/DeepLearning/assemblyai/developer_tools/go/examples/realtime +module github.com/AssemblyAI/assemblyai-go-sdk/examples/realtime go 1.21 diff --git a/examples/upload-with-progress/go.mod b/examples/upload-with-progress/go.mod new file mode 100644 index 0000000..55d038a --- /dev/null +++ b/examples/upload-with-progress/go.mod @@ -0,0 +1,19 @@ +module github.com/AssemblyAI/assemblyai-go-sdk/examples/upload-with-progress + +go 1.21 + +require ( + github.com/AssemblyAI/assemblyai-go-sdk v1.5.1 + github.com/schollz/progressbar/v3 v3.14.6 +) + +require ( + github.com/cenkalti/backoff v2.2.1+incompatible // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/klauspost/compress v1.10.3 // indirect + github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect + github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/term v0.22.0 // indirect + nhooyr.io/websocket v1.8.7 // indirect +) diff --git a/examples/upload-with-progress/go.sum b/examples/upload-with-progress/go.sum new file mode 100644 index 0000000..7f093f8 --- /dev/null +++ b/examples/upload-with-progress/go.sum @@ -0,0 +1,85 @@ +github.com/AssemblyAI/assemblyai-go-sdk v1.5.1 h1:FQRncG9p+GCjtmG7iiRvHAYpjdmo6nBzhdtKzTWv1UM= +github.com/AssemblyAI/assemblyai-go-sdk v1.5.1/go.mod h1:ytTvsjAVL+nXZnzBfDagQ/LxDQaKL9W/eTiCo3ZuPJA= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= +github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= +github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= +github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8= +github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= +github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/schollz/progressbar/v3 v3.14.6 h1:GyjwcWBAf+GFDMLziwerKvpuS7ZF+mNTAXIB2aspiZs= +github.com/schollz/progressbar/v3 v3.14.6/go.mod h1:Nrzpuw3Nl0srLY0VlTvC4V6RL50pcEymjy6qyJAaLa0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= +nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= diff --git a/examples/upload-with-progress/main.go b/examples/upload-with-progress/main.go new file mode 100644 index 0000000..9647b9d --- /dev/null +++ b/examples/upload-with-progress/main.go @@ -0,0 +1,79 @@ +package main + +import ( + "context" + "fmt" + "io" + "os" + + "github.com/AssemblyAI/assemblyai-go-sdk" + "github.com/schollz/progressbar/v3" +) + +func main() { + apiKey := os.Getenv("ASSEMBLYAI_API_KEY") + + if apiKey == "" { + fmt.Fprintln(os.Stderr, "No API key has been configured. Please export ASSEMBLYAI_API_KEY and run the command again.") + os.Exit(1) + } + + args := os.Args[1:] + + var filePath string + + if len(args) == 1 { + filePath = args[0] + } else { + fmt.Fprintf(os.Stderr, "Error: Expected 1 argument, but got %d.\n", len(args)) + os.Exit(1) + } + + f, err := os.Open(filePath) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: Unable to open %s: %v\n", filePath, err) + os.Exit(1) + } + defer f.Close() + + ctx := context.Background() + + fi, err := f.Stat() + if err != nil { + fmt.Fprintf(os.Stderr, "Error: Unable to get file information for %s: %v\n", filePath, err) + os.Exit(1) + } + + progressReader := io.TeeReader(f, newProgressReader(fi.Size())) + + client := assemblyai.NewClient(apiKey) + + url, err := client.Upload(ctx, progressReader) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: Unable to upload file %s: %v\n", filePath, err) + os.Exit(1) + } + + fmt.Printf("Successfully uploaded %s to AssemblyAI.\n\n", filePath) + fmt.Printf("Use the following URL to transcribe the file:\n\n%s\n\n", url) + fmt.Println("The URL is only accessible from AssemblyAI's servers.") +} + +// progressReader implements the io.Writer interface and updates the progress +// bar when data is written. +type progressReader struct { + progress *progressbar.ProgressBar +} + +func newProgressReader(maxBytes int64) *progressReader { + return &progressReader{ + progress: progressbar.DefaultBytes(maxBytes), + } +} + +func (p *progressReader) Write(b []byte) (int, error) { + if err := p.progress.Add(len(b)); err != nil { + return 0, err + } + return len(b), nil +} diff --git a/go.mod b/go.mod index 484db89..4698f6f 100644 --- a/go.mod +++ b/go.mod @@ -4,15 +4,14 @@ go 1.18 require ( github.com/cenkalti/backoff v2.2.1+incompatible + github.com/coder/websocket v1.8.12 github.com/google/go-querystring v1.1.0 github.com/stretchr/testify v1.9.0 - nhooyr.io/websocket v1.8.7 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/google/go-cmp v0.5.9 // indirect - github.com/klauspost/compress v1.10.3 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 2991bc7..9b61275 100644 --- a/go.sum +++ b/go.sum @@ -1,72 +1,20 @@ github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo= +github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= -github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= -github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= -github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= -github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= -github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8= -github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= -github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= -github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= -github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= -nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= diff --git a/integration_test.go b/integration_test.go index b20fb49..4b390b6 100644 --- a/integration_test.go +++ b/integration_test.go @@ -186,7 +186,7 @@ func TestIntegration_RealTime_WithoutPartialTranscripts(t *testing.T) { err = client.Disconnect(ctx, true) require.NoError(t, err) - require.False(t, partialTranscriptInvoked) + require.True(t, partialTranscriptInvoked) require.True(t, finalTranscriptInvoked) t.Log("disconnected from real-time API") diff --git a/lemur.go b/lemur.go index 5b53b94..f850df9 100644 --- a/lemur.go +++ b/lemur.go @@ -61,7 +61,7 @@ func (s *LeMURService) Question(ctx context.Context, params LeMURQuestionAnswerP return LeMURQuestionAnswerResponse{}, err } - if _, err := s.client.do(req, &response); err != nil { + if err := s.client.do(req, &response); err != nil { return LeMURQuestionAnswerResponse{}, err } @@ -79,7 +79,7 @@ func (s *LeMURService) Summarize(ctx context.Context, params LeMURSummaryParams) var response LeMURSummaryResponse - if _, err := s.client.do(req, &response); err != nil { + if err := s.client.do(req, &response); err != nil { return LeMURSummaryResponse{}, err } @@ -97,7 +97,7 @@ func (s *LeMURService) ActionItems(ctx context.Context, params LeMURActionItemsP var response LeMURActionItemsResponse - if _, err := s.client.do(req, &response); err != nil { + if err := s.client.do(req, &response); err != nil { return LeMURActionItemsResponse{}, err } @@ -115,7 +115,7 @@ func (s *LeMURService) Task(ctx context.Context, params LeMURTaskParams) (LeMURT var response LeMURTaskResponse - if _, err := s.client.do(req, &response); err != nil { + if err := s.client.do(req, &response); err != nil { return LeMURTaskResponse{}, err } @@ -130,7 +130,7 @@ func (s *LeMURService) PurgeRequestData(ctx context.Context, requestID string) ( var response PurgeLeMURRequestDataResponse - if _, err := s.client.do(req, &response); err != nil { + if err := s.client.do(req, &response); err != nil { return PurgeLeMURRequestDataResponse{}, err } @@ -144,7 +144,7 @@ func (s *LeMURService) GetResponseData(ctx context.Context, requestID string, re return err } - if _, err := s.client.do(req, response); err != nil { + if err := s.client.do(req, response); err != nil { return err } diff --git a/realtime.go b/realtime.go index 3e3f541..1c2426a 100644 --- a/realtime.go +++ b/realtime.go @@ -9,8 +9,8 @@ import ( "strconv" "sync" - "nhooyr.io/websocket" - "nhooyr.io/websocket/wsjson" + "github.com/coder/websocket" + "github.com/coder/websocket/wsjson" ) var ( @@ -550,11 +550,9 @@ func (svc *RealTimeService) CreateTemporaryToken(ctx context.Context, expiresIn } var tokenResponse RealtimeTemporaryTokenResponse - resp, err := svc.client.do(req, &tokenResponse) - if err != nil { + if err := svc.client.do(req, &tokenResponse); err != nil { return nil, err } - defer resp.Body.Close() return &tokenResponse, nil } diff --git a/realtime_test.go b/realtime_test.go index ed3d30b..516053b 100644 --- a/realtime_test.go +++ b/realtime_test.go @@ -5,14 +5,15 @@ import ( "fmt" "net/http" "net/http/httptest" + "os" "strings" "sync" "testing" "time" + "github.com/coder/websocket" + "github.com/coder/websocket/wsjson" "github.com/stretchr/testify/require" - "nhooyr.io/websocket" - "nhooyr.io/websocket/wsjson" ) const testTimeout = 5 * time.Second @@ -152,7 +153,7 @@ func TestRealTime_Connect(t *testing.T) { func TestRealTime_ConnectFailsDisconnect(t *testing.T) { t.Parallel() - // setup webhook server that fails to make a connection + // Set up a test server that fails to make a connection. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { apiKey := r.Header.Get("Authorization") require.Equal(t, "api-key", apiKey) @@ -168,10 +169,10 @@ func TestRealTime_ConnectFailsDisconnect(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - // try to connect, note error is returned, but ignore it and proceed to Disconnect + var err error + // Attempt to connect, ignore the resulting error and then disconnect. _ = client.Connect(ctx) - - err := client.Disconnect(ctx, true) + err = client.Disconnect(ctx, true) require.Errorf(t, err, "client connection does not exist") } @@ -423,6 +424,7 @@ func TestRealTime_TemporaryToken(t *testing.T) { ctx := r.Context() if r.URL.Path == "/v2/realtime/token" { + w.Header().Set("Content-Type", "application/json") fmt.Fprintln(w, `{"token":"temp-token"}`) return } @@ -583,3 +585,55 @@ func TestRealTime_EnablePartialTranscriptsIfCallback(t *testing.T) { err = client.Disconnect(ctx, true) require.NoError(t, err) } + +func TestRealtime_Regression_UnexpectedRSVBits(t *testing.T) { + path := "./testdata/gore-short.wav" + + // Transcript + f, err := os.Open(path) + require.NoError(t, err) + defer f.Close() + + transcriber := &RealTimeTranscriber{ + OnSessionBegins: func(event SessionBegins) { + t.Log("session started") + }, + OnPartialTranscript: func(event PartialTranscript) { + t.Log(event.Text) + }, + OnSessionTerminated: func(event SessionTerminated) { + t.Log("session terminated") + }, + } + + t.Parallel() + + apiKey := os.Getenv("ASSEMBLYAI_API_KEY") + if apiKey == "" { + t.Skip("ASSEMBLYAI_API_KEY not set") + } + + client := NewRealTimeClientWithOptions( + WithRealTimeAPIKey(apiKey), WithRealTimeTranscriber(transcriber), WithRealTimeSampleRate(8000), + ) + + ctx := context.Background() + + err = client.Connect(ctx) + require.NoError(t, err) + + buf := make([]byte, framesPerBufferTelephone) + + for { + _, err := f.Read(buf) + if err != nil { + break + } + + err = client.Send(ctx, buf) + require.NoError(t, err) + } + + err = client.Disconnect(ctx, false) + require.NoError(t, err) +} diff --git a/transcript.go b/transcript.go index 36813ea..c0b9ad9 100644 --- a/transcript.go +++ b/transcript.go @@ -56,11 +56,9 @@ func (s *TranscriptService) SubmitFromURL(ctx context.Context, audioURL string, return Transcript{}, err } - resp, err := s.client.do(req, &transcript) - if err != nil { + if err := s.client.do(req, &transcript); err != nil { return Transcript{}, err } - defer resp.Body.Close() return transcript, nil } @@ -86,11 +84,9 @@ func (s *TranscriptService) Delete(ctx context.Context, transcriptID string) (Tr var transcript Transcript - resp, err := s.client.do(req, &transcript) - if err != nil { + if err := s.client.do(req, &transcript); err != nil { return Transcript{}, err } - defer resp.Body.Close() return transcript, nil } @@ -106,11 +102,9 @@ func (s *TranscriptService) Get(ctx context.Context, transcriptID string) (Trans var transcript Transcript - resp, err := s.client.do(req, &transcript) - if err != nil { + if err := s.client.do(req, &transcript); err != nil { return Transcript{}, err } - defer resp.Body.Close() return transcript, nil } @@ -124,11 +118,9 @@ func (s *TranscriptService) GetSentences(ctx context.Context, transcriptID strin var results SentencesResponse - resp, err := s.client.do(req, &results) - if err != nil { + if err := s.client.do(req, &results); err != nil { return SentencesResponse{}, err } - defer resp.Body.Close() return results, nil } @@ -142,11 +134,9 @@ func (s *TranscriptService) GetParagraphs(ctx context.Context, transcriptID stri var results ParagraphsResponse - resp, err := s.client.do(req, &results) - if err != nil { + if err := s.client.do(req, &results); err != nil { return ParagraphsResponse{}, err } - defer resp.Body.Close() return results, nil } @@ -162,11 +152,9 @@ func (s *TranscriptService) GetRedactedAudio(ctx context.Context, transcriptID s var audio RedactedAudioResponse - resp, err := s.client.do(req, &audio) - if err != nil { + if err := s.client.do(req, &audio); err != nil { return RedactedAudioResponse{}, err } - defer resp.Body.Close() return audio, nil } @@ -189,11 +177,9 @@ func (s *TranscriptService) GetSubtitles(ctx context.Context, transcriptID strin var res []byte - resp, err := s.client.do(req, &res) - if err != nil { + if err := s.client.do(req, &res); err != nil { return nil, err } - defer resp.Body.Close() return res, nil } @@ -215,11 +201,9 @@ func (s *TranscriptService) List(ctx context.Context, options ListTranscriptPara var results TranscriptList - resp, err := s.client.do(req, &results) - if err != nil { + if err := s.client.do(req, &results); err != nil { return TranscriptList{}, err } - defer resp.Body.Close() return results, nil } @@ -279,11 +263,9 @@ func (s *TranscriptService) WordSearch(ctx context.Context, transcriptID string, var results WordSearchResponse - resp, err := s.client.do(req, &results) - if err != nil { + if err := s.client.do(req, &results); err != nil { return WordSearchResponse{}, err } - defer resp.Body.Close() return results, nil } diff --git a/transcript_test.go b/transcript_test.go index a973f2b..b1fa47f 100644 --- a/transcript_test.go +++ b/transcript_test.go @@ -285,6 +285,8 @@ func TestAPIError(t *testing.T) { handler.HandleFunc("/v2/transcript", func(w http.ResponseWriter, r *http.Request) { require.Equal(t, "POST", r.Method) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusBadRequest) fmt.Fprintf(w, `{"error": "something bad happened"}`) }) @@ -293,10 +295,14 @@ func TestAPIError(t *testing.T) { _, err := client.Transcripts.TranscribeFromURL(ctx, fakeAudioURL, nil) - require.IsType(t, APIError{}, err) + var apierr APIError + require.ErrorAs(t, err, &apierr) + + require.Equal(t, http.StatusBadRequest, apierr.Status) + require.Equal(t, "something bad happened", apierr.Message) + + b, err := io.ReadAll(apierr.Response.Body) + require.NoError(t, err) - require.Equal(t, APIError{ - Status: http.StatusBadRequest, - Message: "something bad happened", - }, err) + require.Equal(t, `{"error": "something bad happened"}`, string(b)) } diff --git a/types.go b/types.go index 8326a33..5aa6b18 100644 --- a/types.go +++ b/types.go @@ -76,6 +76,7 @@ type ContentSafetyLabelResult struct { // An array of results for the Content Moderation model, if it is enabled. // See [Content moderation](https://www.assemblyai.com/docs/models/content-moderation) for more information. type ContentSafetyLabelsResult struct { + // An array of results for the Content Moderation model Results []ContentSafetyLabelResult `json:"results,omitempty"` // A summary of the Content Moderation severity results for the entire audio file @@ -275,12 +276,16 @@ type PageDetails struct { } type ParagraphsResponse struct { + // The duration of the audio file in seconds AudioDuration *float64 `json:"audio_duration,omitempty"` + // The confidence score for the transcript Confidence *float64 `json:"confidence,omitempty"` + // The unique identifier of your transcript ID *string `json:"id,omitempty"` + // An array of paragraphs in the transcript Paragraphs []TranscriptParagraph `json:"paragraphs,omitempty"` } @@ -323,12 +328,16 @@ type RedactedAudioResponse struct { type RedactedAudioStatus string type SentencesResponse struct { + // The duration of the audio file in seconds AudioDuration *float64 `json:"audio_duration,omitempty"` + // The confidence score for the transcript Confidence *float64 `json:"confidence,omitempty"` + // The unique identifier for the transcript ID *string `json:"id,omitempty"` + // An array of sentences in the transcript Sentences []TranscriptSentence `json:"sentences,omitempty"` } @@ -336,6 +345,9 @@ type Sentiment string // The result of the Sentiment Analysis model type SentimentAnalysisResult struct { + // The channel of this utterance. The left and right channels are channels 1 and 2. Additional channels increment the channel number sequentially. + Channel *string `json:"channel,omitempty"` + // The confidence score for the detected sentiment of the sentence, from 0 to 1 Confidence *float64 `json:"confidence,omitempty"` @@ -402,6 +414,7 @@ type TopicDetectionModelResult struct { // The result of the topic detection model type TopicDetectionResult struct { + // An array of detected topics in the text Labels []struct { // The IAB taxonomical label for the label of the detected topic, where > denotes supertopic/subtopic relationship Label *string `json:"label,omitempty"` @@ -468,7 +481,7 @@ type Transcript struct { // Transcribe Filler Words, like "umm", in your media file; can be true or false Disfluencies *bool `json:"disfluencies,omitempty"` - // Whether [Dual channel transcription](https://www.assemblyai.com/docs/models/speech-recognition#dual-channel-transcription) was enabled in the transcription request, either true or false + // Deprecated: Whether [Dual channel transcription](https://www.assemblyai.com/docs/models/speech-recognition#dual-channel-transcription) was enabled in the transcription request, either true or false DualChannel *bool `json:"dual_channel,omitempty"` // An array of results for the Entity Detection model, if it is enabled. @@ -507,7 +520,6 @@ type Transcript struct { // The confidence threshold for the automatically detected language. // An error will be returned if the language confidence is below this threshold. - // Defaults to 0. LanguageConfidenceThreshold *float64 `json:"language_confidence_threshold,omitempty"` // Whether [Automatic language detection](https://www.assemblyai.com/docs/models/speech-recognition#automatic-language-detection) is enabled, either true or false @@ -516,6 +528,9 @@ type Transcript struct { // Deprecated: The language model that was used for the transcript LanguageModel *string `json:"language_model,omitempty"` + // Whether [Multichannel transcription](https://www.assemblyai.com/docs/models/speech-recognition#multichannel-transcription) was enabled in the transcription request, either true or false + Multichannel *bool `json:"multichannel,omitempty"` + // Whether Automatic Punctuation is enabled, either true or false Punctuate *bool `json:"punctuate,omitempty"` @@ -629,25 +644,33 @@ type TranscriptLanguageCode string // A list of transcripts. Transcripts are sorted from newest to oldest. The previous URL always points to a page with older transcripts. type TranscriptList struct { + // Details of the transcript page PageDetails PageDetails `json:"page_details,omitempty"` + // An array of transcripts Transcripts []TranscriptListItem `json:"transcripts,omitempty"` } type TranscriptListItem struct { + // The URL to the audio file AudioURL *string `json:"audio_url,omitempty"` + // The date and time the transcript was completed Completed *string `json:"completed,omitempty"` + // The date and time the transcript was created Created *string `json:"created,omitempty"` // Error message of why the transcript failed Error *string `json:"error,omitempty"` + // The unique identifier for the transcript ID *string `json:"id,omitempty"` + // The URL to retrieve the transcript ResourceURL *string `json:"resource_url,omitempty"` + // The status of the transcript Status TranscriptStatus `json:"status,omitempty"` } @@ -683,7 +706,7 @@ type TranscriptOptionalParams struct { // Transcribe Filler Words, like "umm", in your media file; can be true or false Disfluencies *bool `json:"disfluencies,omitempty"` - // Enable [Dual Channel](https://www.assemblyai.com/docs/models/speech-recognition#dual-channel-transcription) transcription, can be true or false. + // Deprecated: Enable [Dual Channel](https://www.assemblyai.com/docs/models/speech-recognition#dual-channel-transcription) transcription, can be true or false. DualChannel *bool `json:"dual_channel,omitempty"` // Enable [Entity Detection](https://www.assemblyai.com/docs/models/entity-detection), can be true or false @@ -710,6 +733,9 @@ type TranscriptOptionalParams struct { // Enable [Automatic language detection](https://www.assemblyai.com/docs/models/speech-recognition#automatic-language-detection), either true or false. LanguageDetection *bool `json:"language_detection,omitempty"` + // Enable [Multichannel](https://www.assemblyai.com/docs/models/speech-recognition#multichannel-transcription) transcription, can be true or false. + Multichannel *bool `json:"multichannel,omitempty"` + // Enable Automatic Punctuation, can be true or false Punctuate *bool `json:"punctuate,omitempty"` @@ -762,7 +788,9 @@ type TranscriptOptionalParams struct { // The header value to send back with the transcript completed or failed webhook requests for added security WebhookAuthHeaderValue *string `json:"webhook_auth_header_value,omitempty"` - // The URL to which we send webhook requests. We sends two different types of webhook requests. One request when a transcript is completed or failed, and one request when the redacted audio is ready if redact_pii_audio is enabled. + // The URL to which we send webhook requests. + // We sends two different types of webhook requests. + // One request when a transcript is completed or failed, and one request when the redacted audio is ready if redact_pii_audio is enabled. WebhookURL *string `json:"webhook_url,omitempty"` // The list of custom vocabulary to boost transcription probability for @@ -770,17 +798,19 @@ type TranscriptOptionalParams struct { } type TranscriptParagraph struct { + // The confidence score for the transcript of this paragraph Confidence *float64 `json:"confidence,omitempty"` + // The ending time, in milliseconds, of the paragraph End *int64 `json:"end,omitempty"` - // The speaker of the sentence if [Speaker Diarization](https://www.assemblyai.com/docs/models/speaker-diarization) is enabled, else null - Speaker *string `json:"speaker,omitempty"` - + // The starting time, in milliseconds, of the paragraph Start *int64 `json:"start,omitempty"` + // The transcript of the paragraph Text *string `json:"text,omitempty"` + // An array of words in the paragraph Words []TranscriptWord `json:"words,omitempty"` } @@ -805,17 +835,25 @@ type TranscriptReadyNotification struct { type TranscriptReadyStatus string type TranscriptSentence struct { + // The channel of the sentence. The left and right channels are channels 1 and 2. Additional channels increment the channel number sequentially. + Channel *string `json:"channel,omitempty"` + + // The confidence score for the transcript of this sentence Confidence *float64 `json:"confidence,omitempty"` + // The ending time, in milliseconds, for the sentence End *int64 `json:"end,omitempty"` // The speaker of the sentence if [Speaker Diarization](https://www.assemblyai.com/docs/models/speaker-diarization) is enabled, else null Speaker *string `json:"speaker,omitempty"` + // The starting time, in milliseconds, for the sentence Start *int64 `json:"start,omitempty"` + // The transcript of the sentence Text *string `json:"text,omitempty"` + // An array of words in the sentence Words []TranscriptWord `json:"words,omitempty"` } @@ -823,6 +861,9 @@ type TranscriptSentence struct { type TranscriptStatus string type TranscriptUtterance struct { + // The channel of this utterance. The left and right channels are channels 1 and 2. Additional channels increment the channel number sequentially. + Channel *string `json:"channel,omitempty"` + // The confidence score for the transcript of this utterance Confidence *float64 `json:"confidence,omitempty"` @@ -846,15 +887,22 @@ type TranscriptUtterance struct { type TranscriptWebhookNotification struct{} type TranscriptWord struct { + // The channel of the word. The left and right channels are channels 1 and 2. Additional channels increment the channel number sequentially. + Channel *string `json:"channel,omitempty"` + + // The confidence score for the transcript of this word Confidence *float64 `json:"confidence,omitempty"` + // The ending time, in milliseconds, for the word End *int64 `json:"end,omitempty"` - // The speaker of the sentence if [Speaker Diarization](https://www.assemblyai.com/docs/models/speaker-diarization) is enabled, else null + // The speaker of the word if [Speaker Diarization](https://www.assemblyai.com/docs/models/speaker-diarization) is enabled, else null Speaker *string `json:"speaker,omitempty"` + // The starting time, in milliseconds, for the word Start *int64 `json:"start,omitempty"` + // The text of the word Text *string `json:"text,omitempty"` } diff --git a/upload.go b/upload.go index 2a24685..af2dd2b 100644 --- a/upload.go +++ b/upload.go @@ -23,7 +23,7 @@ func (c *Client) Upload(ctx context.Context, data io.Reader) (string, error) { UploadURL string `json:"upload_url"` } - if _, err := c.do(req, &result); err != nil { + if err := c.do(req, &result); err != nil { return "", err } diff --git a/upload_test.go b/upload_test.go index bf9d396..32eac93 100644 --- a/upload_test.go +++ b/upload_test.go @@ -25,6 +25,7 @@ func TestUpload(t *testing.T) { require.Equal(t, "data", string(b)) + w.Header().Set("Content-Type", "application/json") fmt.Fprintf(w, "{\"upload_url\": %q}", fakeAudioURL) })