diff --git a/README.md b/README.md index 6671f4f..36c3415 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,8 @@ Set the `XI_API_KEY` environment variable, and pipe it some text to give it a wh ## Example Code +### Text-to-Speech Example + To use this library, create a new client and send a TTS request to a voice. The following code block illustrates how one might replicate the say/espeak command, using the streaming endpoint. @@ -90,3 +92,49 @@ func main() { <-done } ``` + +### Sound Generation Example + +The following example demonstrates how to generate sound effects using the Sound Generation API: + +```go +package main + +import ( + "context" + "os" + + "github.com/taigrr/elevenlabs/client" +) + +func main() { + ctx := context.Background() + // Create a new client with your API key + client := client.New(os.Getenv("XI_API_KEY")) + + // Generate a sound effect and save it to a file + f, err := os.Create("footsteps.mp3") + if err != nil { + panic(err) + } + defer f.Close() + + // Basic usage (using default duration and prompt influence) + err = client.SoundGenerationWriter(ctx, f, "footsteps on wooden floor", 0, 0) + if err != nil { + panic(err) + } + + // Advanced usage with custom duration and prompt influence + audio, err := client.SoundGeneration( + ctx, + "heavy rain on a tin roof", + 5.0, // Set duration to 5 seconds + 0.5, // Set prompt influence to 0.5 + ) + if err != nil { + panic(err) + } + os.WriteFile("rain.mp3", audio, 0644) +} +``` diff --git a/client/sound_gen.go b/client/sound_gen.go new file mode 100644 index 0000000..55bdcab --- /dev/null +++ b/client/sound_gen.go @@ -0,0 +1,98 @@ +package client + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/taigrr/elevenlabs/client/types" +) + +// SoundGenerationWriter generates a sound effect from text and writes it to the provided writer. +// If durationSeconds is 0, it will be omitted from the request and the API will determine the optimal duration. +// If promptInfluence is 0, it will default to 0.3. +func (c Client) SoundGenerationWriter(ctx context.Context, w io.Writer, text string, durationSeconds, promptInfluence float64) error { + params := types.SoundGeneration{ + Text: text, + PromptInfluence: 0.3, // default value + } + + if promptInfluence != 0 { + params.PromptInfluence = promptInfluence + } + if durationSeconds != 0 { + params.DurationSeconds = durationSeconds + } + + body, err := c.requestSoundGeneration(ctx, params) + if err != nil { + return err + } + defer body.Close() + _, err = io.Copy(w, body) + return err +} + +// SoundGeneration generates a sound effect from text and returns the audio as bytes. +// If durationSeconds is 0, it will be omitted from the request and the API will determine the optimal duration. +// If promptInfluence is 0, it will default to 0.3. +func (c Client) SoundGeneration(ctx context.Context, text string, durationSeconds, promptInfluence float64) ([]byte, error) { + params := types.SoundGeneration{ + Text: text, + PromptInfluence: 0.3, // default value + } + + if promptInfluence != 0 { + params.PromptInfluence = promptInfluence + } + if durationSeconds != 0 { + params.DurationSeconds = durationSeconds + } + + body, err := c.requestSoundGeneration(ctx, params) + if err != nil { + return nil, err + } + defer body.Close() + + var b bytes.Buffer + _, err = io.Copy(&b, body) + if err != nil { + return nil, err + } + return b.Bytes(), nil +} + +func (c Client) requestSoundGeneration(ctx context.Context, params types.SoundGeneration) (io.ReadCloser, error) { + url := c.endpoint + "/v1/sound-generation" + client := &http.Client{} + + b, err := json.Marshal(params) + if err != nil { + return nil, err + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(b)) + if err != nil { + return nil, err + } + + req.Header.Set("xi-api-key", c.apiKey) + req.Header.Set("User-Agent", "github.com/taigrr/elevenlabs") + req.Header.Set("accept", "audio/mpeg") + + res, err := client.Do(req) + if err != nil { + return nil, err + } + + if res.StatusCode != http.StatusOK { + res.Body.Close() + return nil, fmt.Errorf("unexpected status code: %d", res.StatusCode) + } + + return res.Body, nil +} diff --git a/client/tts.go b/client/tts.go index a448bbf..127f437 100644 --- a/client/tts.go +++ b/client/tts.go @@ -12,34 +12,101 @@ import ( "github.com/taigrr/elevenlabs/client/types" ) -func (c Client) TTSWriter(ctx context.Context, w io.Writer, text, modelID, voiceID string, options types.SynthesisOptions) error { - options.Clamp() - url := fmt.Sprintf(c.endpoint+"/v1/text-to-speech/%s", voiceID) - opts := types.TTS{ - Text: text, - ModelID: modelID, - VoiceSettings: options, +func WithPreviousText(previousText string) types.TTSParam { + return func(tts *types.TTS) { + tts.PreviousText = previousText } - b, _ := json.Marshal(opts) - client := &http.Client{} - req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(b)) +} + +func WithNextText(nextText string) types.TTSParam { + return func(tts *types.TTS) { + tts.NextText = nextText + } +} + +func (c Client) TTSWriter(ctx context.Context, w io.Writer, text, modelID, voiceID string, options types.SynthesisOptions, optionalParams ...types.TTSParam) error { + params := types.TTS{ + Text: text, + VoiceID: voiceID, + ModelID: modelID, + } + for _, p := range optionalParams { + p(¶ms) + } + + body, err := c.requestTTS(ctx, params, options) if err != nil { return err } + defer body.Close() + io.Copy(w, body) + return nil +} + +func (c Client) TTS(ctx context.Context, text, voiceID, modelID string, options types.SynthesisOptions, optionalParams ...types.TTSParam) ([]byte, error) { + params := types.TTS{ + Text: text, + VoiceID: voiceID, + ModelID: modelID, + } + for _, p := range optionalParams { + p(¶ms) + } + + body, err := c.requestTTS(ctx, params, options) + if err != nil { + return []byte{}, err + } + defer body.Close() + b := bytes.Buffer{} + io.Copy(&b, body) + return b.Bytes(), nil +} + +func (c Client) TTSStream(ctx context.Context, w io.Writer, text, voiceID string, options types.SynthesisOptions, optionalParams ...types.TTSParam) error { + params := types.TTS{ + Text: text, + VoiceID: voiceID, + Stream: true, + } + for _, p := range optionalParams { + p(¶ms) + } + + body, err := c.requestTTS(ctx, params, options) + if err != nil { + return err + } + defer body.Close() + io.Copy(w, body) + return nil +} + +func (c Client) requestTTS(ctx context.Context, params types.TTS, options types.SynthesisOptions) (io.ReadCloser, error) { + options.Clamp() + url := fmt.Sprintf(c.endpoint+"/v1/text-to-speech/%s", params.VoiceID) + if params.Stream { + url += "/stream" + } + client := &http.Client{} + b, _ := json.Marshal(params) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(b)) + if err != nil { + return nil, err + } req.Header.Set("xi-api-key", c.apiKey) req.Header.Set("User-Agent", "github.com/taigrr/elevenlabs") req.Header.Set("accept", "audio/mpeg") res, err := client.Do(req) if err != nil { - return err + return nil, err } + switch res.StatusCode { case 401: - return ErrUnauthorized + return nil, ErrUnauthorized case 200: - defer res.Body.Close() - io.Copy(w, res.Body) - return nil + return res.Body, nil case 422: fallthrough default: @@ -51,93 +118,6 @@ func (c Client) TTSWriter(ctx context.Context, w io.Writer, text, modelID, voice } else { err = errors.Join(err, ve) } - return err - } -} - -func (c Client) TTS(ctx context.Context, text, voiceID, modelID string, options types.SynthesisOptions) ([]byte, error) { - options.Clamp() - url := fmt.Sprintf(c.endpoint+"/v1/text-to-speech/%s", voiceID) - client := &http.Client{} - opts := types.TTS{ - Text: text, - ModelID: modelID, - VoiceSettings: options, - } - b, _ := json.Marshal(opts) - req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(b)) - if err != nil { - return []byte{}, err - } - req.Header.Set("xi-api-key", c.apiKey) - req.Header.Set("User-Agent", "github.com/taigrr/elevenlabs") - req.Header.Set("accept", "audio/mpeg") - res, err := client.Do(req) - if err != nil { - return []byte{}, err - } - switch res.StatusCode { - case 401: - return []byte{}, ErrUnauthorized - case 200: - b := bytes.Buffer{} - - defer res.Body.Close() - io.Copy(&b, res.Body) - return b.Bytes(), nil - case 422: - fallthrough - default: - ve := types.ValidationError{} - defer res.Body.Close() - jerr := json.NewDecoder(res.Body).Decode(&ve) - if jerr != nil { - err = errors.Join(err, jerr) - } else { - err = errors.Join(err, ve) - } - return []byte{}, err - } -} - -func (c Client) TTSStream(ctx context.Context, w io.Writer, text, voiceID string, options types.SynthesisOptions) error { - options.Clamp() - url := fmt.Sprintf(c.endpoint+"/v1/text-to-speech/%s/stream", voiceID) - opts := types.TTS{ - Text: text, - VoiceSettings: options, - } - b, _ := json.Marshal(opts) - client := &http.Client{} - req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(b)) - if err != nil { - return err - } - req.Header.Set("xi-api-key", c.apiKey) - req.Header.Set("User-Agent", "github.com/taigrr/elevenlabs") - req.Header.Set("accept", "audio/mpeg") - res, err := client.Do(req) - if err != nil { - return err - } - switch res.StatusCode { - case 401: - return ErrUnauthorized - case 200: - defer res.Body.Close() - io.Copy(w, res.Body) - return nil - case 422: - fallthrough - default: - ve := types.ValidationError{} - defer res.Body.Close() - jerr := json.NewDecoder(res.Body).Decode(&ve) - if jerr != nil { - err = errors.Join(err, jerr) - } else { - err = errors.Join(err, ve) - } - return err + return nil, err } } diff --git a/client/types/types.go b/client/types/types.go index 92bc22c..19cd954 100644 --- a/client/types/types.go +++ b/client/types/types.go @@ -19,11 +19,17 @@ type Voice struct { Labels string `json:"labels,omitempty"` // Serialized labels dictionary for the voice. } type TTS struct { + VoiceID string `json:"voice_id"` // The ID of the voice that will be used to generate the speech. ModelID string `json:"model_id,omitempty"` - Text string `json:"text"` // The text that will get converted into speech. Currently only English text is supported. + Text string `json:"text"` // The text that will get converted into speech. + PreviousText string `json:"previous_text,omitempty"` // The text that was used to generate the previous audio file. + NextText string `json:"next_text,omitempty"` // The text that will be used to generate the next audio file. VoiceSettings SynthesisOptions `json:"voice_settings,omitempty"` // Voice settings are applied only on the given TTS request. + Stream bool `json:"stream,omitempty"` // If true, the response will be a stream of audio data. } +type TTSParam func(*TTS) + func (so *SynthesisOptions) Clamp() { if so.Stability > 1 || so.Stability < 0 { so.Stability = 0.75 @@ -218,3 +224,9 @@ type VoiceResponseModel struct { Sharing SharingOptions `json:"sharing"` HighQualityBaseModelIds []string `json:"high_quality_base_model_ids"` } + +type SoundGeneration struct { + Text string `json:"text"` // The text that will get converted into a sound effect. + DurationSeconds float64 `json:"duration_seconds"` // The duration of the sound which will be generated in seconds. + PromptInfluence float64 `json:"prompt_influence"` // A higher prompt influence makes your generation follow the prompt more closely. +} diff --git a/client/voices.go b/client/voices.go index 1928140..1e9e249 100644 --- a/client/voices.go +++ b/client/voices.go @@ -195,46 +195,6 @@ func (c Client) EditVoice(ctx context.Context, voiceID, name, description string } } -func (c Client) defaultVoiceSettings(ctx context.Context) (types.SynthesisOptions, error) { - url := c.endpoint + "/v1/voices/settings/default" - client := &http.Client{} - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) - if err != nil { - return types.SynthesisOptions{}, err - } - req.Header.Set("xi-api-key", c.apiKey) - req.Header.Set("User-Agent", "github.com/taigrr/elevenlabs") - req.Header.Set("accept", "application/json") - res, err := client.Do(req) - if err != nil { - return types.SynthesisOptions{}, err - } - switch res.StatusCode { - case 401: - return types.SynthesisOptions{}, ErrUnauthorized - case 200: - so := types.SynthesisOptions{} - defer res.Body.Close() - jerr := json.NewDecoder(res.Body).Decode(&so) - if jerr != nil { - return types.SynthesisOptions{}, jerr - } - return so, nil - case 422: - fallthrough - default: - ve := types.ValidationError{} - defer res.Body.Close() - jerr := json.NewDecoder(res.Body).Decode(&ve) - if jerr != nil { - err = errors.Join(err, jerr) - } else { - err = errors.Join(err, ve) - } - return types.SynthesisOptions{}, err - } -} - func (c Client) GetVoiceSettings(ctx context.Context, voiceID string) (types.SynthesisOptions, error) { url := fmt.Sprintf(c.endpoint+"/v1/voices/%s/settings", voiceID) client := &http.Client{} diff --git a/cmd/say/main.go b/cmd/say/main.go index dd5157c..93480f5 100644 --- a/cmd/say/main.go +++ b/cmd/say/main.go @@ -6,8 +6,8 @@ import ( "io" "log" "os" - "time" "strings" + "time" "github.com/faiface/beep" "github.com/faiface/beep/mp3" diff --git a/go.mod b/go.mod index 426b3ed..ac029b2 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,6 @@ require ( github.com/hajimehoshi/oto v1.0.1 // indirect github.com/pkg/errors v0.9.1 // indirect golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 // indirect - golang.org/x/exp/shiny v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/image v0.12.0 // indirect golang.org/x/mobile v0.0.0-20230906132913-2077a3224571 // indirect golang.org/x/sys v0.12.0 // indirect diff --git a/go.sum b/go.sum index 6217831..9591809 100644 --- a/go.sum +++ b/go.sum @@ -8,12 +8,10 @@ github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebK github.com/go-audio/audio v1.0.0/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs= github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498= github.com/go-audio/wav v1.0.0/go.mod h1:3yoReyQOsiARkvPl3ERCi8JFjihzG6WhjYpZCf5zAWE= -github.com/hajimehoshi/go-mp3 v0.3.0 h1:fTM5DXjp/DL2G74HHAs/aBGiS9Tg7wnp+jkU38bHy4g= github.com/hajimehoshi/go-mp3 v0.3.0/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= github.com/hajimehoshi/go-mp3 v0.3.4 h1:NUP7pBYH8OguP4diaTZ9wJbUbk3tC0KlfzsEpWmYj68= github.com/hajimehoshi/go-mp3 v0.3.4/go.mod h1:fRtZraRFcWb0pu7ok0LqyFhCUrPeMsGRSVop0eemFmo= github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= -github.com/hajimehoshi/oto v0.7.1 h1:I7maFPz5MBCwiutOrz++DLdbr4rTzBsbBuV2VpgU9kk= github.com/hajimehoshi/oto v0.7.1/go.mod h1:wovJ8WWMfFKvP587mhHgot/MBr4DnNy9m6EepeVGnos= github.com/hajimehoshi/oto v1.0.1 h1:8AMnq0Yr2YmzaiqTg/k1Yzd6IygUGk2we9nmjgbgPn4= github.com/hajimehoshi/oto v1.0.1/go.mod h1:wovJ8WWMfFKvP587mhHgot/MBr4DnNy9m6EepeVGnos= @@ -33,26 +31,15 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8 h1:idBdZTd9UioThJp8KpM/rTSinK/ChZFBE43/WtIy8zg= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 h1:estk1glOnSVeJ9tdEZZc5mAMDZk5lNJNyJ6DvrBkTEU= golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= -golang.org/x/exp/shiny v0.0.0-20230817173708-d852ddb80c63 h1:3AGKexOYqL+ztdWdkB1bDwXgPBuTS/S8A4WzuTvJ8Cg= -golang.org/x/exp/shiny v0.0.0-20230817173708-d852ddb80c63/go.mod h1:UH99kUObWAZkDnWqppdQe5ZhPYESUw8I0zVV1uWBR+0= -golang.org/x/exp/shiny v0.0.0-20230905200255-921286631fa9 h1:rvxT0xShhCtCvCCmF3wMK57nkbTYSaf/0Tp7TAllhMs= -golang.org/x/exp/shiny v0.0.0-20230905200255-921286631fa9/go.mod h1:UH99kUObWAZkDnWqppdQe5ZhPYESUw8I0zVV1uWBR+0= golang.org/x/image v0.0.0-20190220214146-31aff87c08e9/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 h1:KYGJGHOQy8oSi1fDlSpcZF0+juKwk/hEMv5SiwHogR0= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo= -golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8= golang.org/x/image v0.12.0 h1:w13vZbU4o5rKOFFR8y7M+c4A5jXDC0uXTdHYRP8X2DQ= golang.org/x/image v0.12.0/go.mod h1:Lu90jvHG7GfemOIcldsh9A2hS01ocl6oNO7ype5mEnk= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6 h1:vyLBGJPIl9ZYbcQFM2USFmJBK6KI+t+z6jL0lbwjrnc= golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mobile v0.0.0-20230818142238-7088062f872d h1:Ouem7YgI783/xoG5NZUHbg/ggHFOutUUoq1ZRlCCTbM= -golang.org/x/mobile v0.0.0-20230818142238-7088062f872d/go.mod h1:kQNMt2gXlYXNazoSeytBi7knmDN7YS/JzMKFYxgoNxc= golang.org/x/mobile v0.0.0-20230906132913-2077a3224571 h1:QDvQ2KLFHHQWRID6IkZOBf6uLIh9tZ0G+mw61pFQxuo= golang.org/x/mobile v0.0.0-20230906132913-2077a3224571/go.mod h1:wEyOn6VvNW7tcf+bW/wBz1sehi2s2BZ4TimyR7qZen4= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= @@ -72,7 +59,6 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10= golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -80,8 +66,6 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -91,7 +75,6 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=