11 Commits

Author SHA1 Message Date
c5609d07e9 update dep 2025-04-30 10:26:33 -07:00
Jose Ramirez
d79325ad71 add WithHTTPClient (#13) 2025-04-30 10:21:50 -07:00
fba03ed0be update error code for 400 2025-03-13 20:39:03 -07:00
aa701237ff add new error type 2025-03-13 14:04:06 -07:00
Jose Ramirez
29fa401714 s/speeh/speech (#11) 2025-03-04 00:20:55 -08:00
77d17e20fb update deps 2025-03-03 11:17:49 -08:00
Jose Ramirez
93af72dc7c add support for speech-to-text endpoint (#10) 2025-03-03 11:16:04 -08:00
Lachlan Laycock
db0a2e1760 Add sound generation api (#9)
* Add missing attributes for VoiceResponseModel

* Updating module to point to forked repo

* Tidying up go.mod

* Adding missing voice settings

* Adding support for request stitching

* Adding support for request stitching

* Fix dup SharingOptions struct from merge

* Add Sound Generation API

* Fix: revert user-agent/package url to original
2024-11-25 21:39:34 -08:00
samy kamkar
c585531fae Support command line text & new API attributes (#8)
* support `style` and `use_speaker_boost` API attrs

* support optional command line string as text

* print out time it took to run
2024-07-24 12:14:00 -07:00
Lachlan Laycock
41f142ec2c Add missing attributes for VoiceResponseModel (#5) 2023-10-17 13:49:20 -07:00
6ebcddb891 bump deps 2023-09-20 23:53:35 -07:00
15 changed files with 609 additions and 220 deletions

View File

@@ -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.
@@ -69,7 +71,7 @@ func main() {
text, _ := reader.ReadString('\n')
go func() {
// stream audio from elevenlabs using the first voice we found
err = client.TTSStream(ctx, pipeWriter, text, ids[0], types.SynthesisOptions{Stability: 0.75, SimilarityBoost: 0.75})
err = client.TTSStream(ctx, pipeWriter, text, ids[0], types.SynthesisOptions{Stability: 0.75, SimilarityBoost: 0.75, Style: 0.0, UseSpeakerBoost: true})
if err != nil {
panic(err)
}
@@ -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)
}
```

View File

@@ -2,6 +2,7 @@ package client
import (
"errors"
"net/http"
)
const apiEndpoint = "https://api.elevenlabs.io"
@@ -12,14 +13,16 @@ var (
)
type Client struct {
apiKey string
endpoint string
apiKey string
endpoint string
httpClient *http.Client
}
func New(apiKey string) Client {
return Client{
apiKey: apiKey,
endpoint: apiEndpoint,
apiKey: apiKey,
endpoint: apiEndpoint,
httpClient: &http.Client{},
}
}
@@ -27,3 +30,14 @@ func (c Client) WithEndpoint(endpoint string) Client {
c.endpoint = endpoint
return c
}
func (c Client) WithAPIKey(apiKey string) Client {
c.apiKey = apiKey
return c
}
// WithHTTPClient allows users to provide their own http.Client
func (c Client) WithHTTPClient(hc *http.Client) Client {
c.httpClient = hc
return c
}

View File

@@ -15,8 +15,6 @@ import (
func (c Client) HistoryDelete(ctx context.Context, historyItemID string) (bool, error) {
url := fmt.Sprintf(c.endpoint+"/v1/history/%s", historyItemID)
client := &http.Client{}
req, err := http.NewRequestWithContext(ctx, http.MethodDelete, url, nil)
if err != nil {
return false, err
@@ -24,7 +22,7 @@ func (c Client) HistoryDelete(ctx context.Context, historyItemID string) (bool,
req.Header.Set("accept", "application/json")
req.Header.Set("xi-api-key", c.apiKey)
req.Header.Set("User-Agent", "github.com/taigrr/elevenlabs")
res, err := client.Do(req)
res, err := c.httpClient.Do(req)
switch res.StatusCode {
case 401:
@@ -55,7 +53,6 @@ func (c Client) HistoryDownloadZipWriter(ctx context.Context, w io.Writer, id1,
toDownload := types.HistoryPost{
HistoryItemIds: downloads,
}
client := &http.Client{}
body, _ := json.Marshal(toDownload)
bodyReader := bytes.NewReader(body)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bodyReader)
@@ -66,7 +63,7 @@ func (c Client) HistoryDownloadZipWriter(ctx context.Context, w io.Writer, id1,
req.Header.Set("accept", "archive/zip")
req.Header.Set("xi-api-key", c.apiKey)
req.Header.Set("User-Agent", "github.com/taigrr/elevenlabs")
res, err := client.Do(req)
res, err := c.httpClient.Do(req)
switch res.StatusCode {
case 401:
@@ -99,7 +96,6 @@ func (c Client) HistoryDownloadZip(ctx context.Context, id1, id2 string, additio
toDownload := types.HistoryPost{
HistoryItemIds: downloads,
}
client := &http.Client{}
body, _ := json.Marshal(toDownload)
bodyReader := bytes.NewReader(body)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bodyReader)
@@ -110,7 +106,7 @@ func (c Client) HistoryDownloadZip(ctx context.Context, id1, id2 string, additio
req.Header.Set("accept", "archive/zip")
req.Header.Set("xi-api-key", c.apiKey)
req.Header.Set("User-Agent", "github.com/taigrr/elevenlabs")
res, err := client.Do(req)
res, err := c.httpClient.Do(req)
switch res.StatusCode {
case 401:
@@ -142,7 +138,6 @@ func (c Client) HistoryDownloadZip(ctx context.Context, id1, id2 string, additio
func (c Client) HistoryDownloadAudioWriter(ctx context.Context, w io.Writer, ID string) error {
url := fmt.Sprintf(c.endpoint+"/v1/history/%s/audio", ID)
client := &http.Client{}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return err
@@ -150,7 +145,7 @@ func (c Client) HistoryDownloadAudioWriter(ctx context.Context, w io.Writer, ID
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)
res, err := c.httpClient.Do(req)
if err != nil {
return err
}
@@ -179,7 +174,6 @@ func (c Client) HistoryDownloadAudioWriter(ctx context.Context, w io.Writer, ID
func (c Client) HistoryDownloadAudio(ctx context.Context, ID string) ([]byte, error) {
url := fmt.Sprintf(c.endpoint+"/v1/history/%s/audio", ID)
client := &http.Client{}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return []byte{}, err
@@ -187,7 +181,7 @@ func (c Client) HistoryDownloadAudio(ctx context.Context, ID string) ([]byte, er
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)
res, err := c.httpClient.Do(req)
if err != nil {
return []byte{}, err
}
@@ -219,7 +213,6 @@ func (c Client) HistoryDownloadAudio(ctx context.Context, ID string) ([]byte, er
func (c Client) GetHistoryItemList(ctx context.Context) ([]types.HistoryItemList, error) {
url := c.endpoint + "/v1/history"
client := &http.Client{}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return []types.HistoryItemList{}, err
@@ -227,7 +220,7 @@ func (c Client) GetHistoryItemList(ctx context.Context) ([]types.HistoryItemList
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)
res, err := c.httpClient.Do(req)
if err != nil {
return []types.HistoryItemList{}, err
}

View File

@@ -15,7 +15,6 @@ import (
func (c Client) DeleteVoiceSample(ctx context.Context, voiceID, sampleID string) (bool, error) {
url := fmt.Sprintf(c.endpoint+"/v1/voices/%s/samples/%s", voiceID, sampleID)
client := &http.Client{}
req, err := http.NewRequestWithContext(ctx, http.MethodDelete, url, nil)
if err != nil {
return false, err
@@ -24,7 +23,7 @@ func (c Client) DeleteVoiceSample(ctx context.Context, voiceID, sampleID string)
req.Header.Set("accept", "application/json")
req.Header.Set("xi-api-key", c.apiKey)
req.Header.Set("User-Agent", "github.com/taigrr/elevenlabs")
res, err := client.Do(req)
res, err := c.httpClient.Do(req)
if err != nil {
return false, err
}
@@ -50,7 +49,6 @@ func (c Client) DeleteVoiceSample(ctx context.Context, voiceID, sampleID string)
func (c Client) DownloadVoiceSampleWriter(ctx context.Context, w io.Writer, voiceID, sampleID string) error {
url := fmt.Sprintf(c.endpoint+"/v1/voices/%s/samples/%s/audio", voiceID, sampleID)
client := &http.Client{}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return err
@@ -58,7 +56,7 @@ func (c Client) DownloadVoiceSampleWriter(ctx context.Context, w io.Writer, voic
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)
res, err := c.httpClient.Do(req)
if err != nil {
return err
}
@@ -87,7 +85,6 @@ func (c Client) DownloadVoiceSampleWriter(ctx context.Context, w io.Writer, voic
func (c Client) DownloadVoiceSample(ctx context.Context, voiceID, sampleID string) ([]byte, error) {
url := fmt.Sprintf(c.endpoint+"/v1/voices/%s/samples/%s/audio", voiceID, sampleID)
client := &http.Client{}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return []byte{}, err
@@ -95,7 +92,7 @@ func (c Client) DownloadVoiceSample(ctx context.Context, voiceID, sampleID strin
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)
res, err := c.httpClient.Do(req)
if err != nil {
return []byte{}, err
}

96
client/sound_gen.go Normal file
View File

@@ -0,0 +1,96 @@
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"
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 := c.httpClient.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
}

126
client/stt.go Normal file
View File

@@ -0,0 +1,126 @@
package client
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"github.com/taigrr/elevenlabs/client/types"
)
// ConvertSpeechToText converts audio to text using the specified file path
func (c *Client) ConvertSpeechToText(ctx context.Context, audioFilePath string, request types.SpeechToTextRequest) (*types.SpeechToTextResponse, error) {
file, err := os.Open(audioFilePath)
if err != nil {
return nil, fmt.Errorf("failed to open audio file: %w", err)
}
defer file.Close()
return c.ConvertSpeechToTextFromReader(ctx, file, filepath.Base(audioFilePath), request)
}
// ConvertSpeechToTextFromReader converts audio to text using the provided reader
func (c *Client) ConvertSpeechToTextFromReader(ctx context.Context, reader io.Reader, filename string, request types.SpeechToTextRequest) (*types.SpeechToTextResponse, error) {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
if err := writer.WriteField("model_id", string(request.ModelID)); err != nil {
return nil, fmt.Errorf("failed to write model_id field: %w", err)
}
part, err := writer.CreateFormFile("file", filename)
if err != nil {
return nil, fmt.Errorf("failed to create form file: %w", err)
}
if _, err = io.Copy(part, reader); err != nil {
return nil, fmt.Errorf("failed to copy audio data: %w", err)
}
if request.LanguageCode != "" {
if err := writer.WriteField("language_code", request.LanguageCode); err != nil {
return nil, fmt.Errorf("failed to write language_code field: %w", err)
}
}
if request.NumSpeakers != 0 {
if err := writer.WriteField("num_speakers", fmt.Sprintf("%d", request.NumSpeakers)); err != nil {
return nil, fmt.Errorf("failed to write num_speakers field: %w", err)
}
}
if request.TagAudioEvents {
if err := writer.WriteField("tag_audio_events", "true"); err != nil {
return nil, fmt.Errorf("failed to write tag_audio_events field: %w", err)
}
}
if request.TimestampsGranularity != "" {
if err := writer.WriteField("timestamps_granularity", string(request.TimestampsGranularity)); err != nil {
return nil, fmt.Errorf("failed to write timestamps_granularity field: %w", err)
}
}
if request.Diarize {
if err := writer.WriteField("diarize", "true"); err != nil {
return nil, fmt.Errorf("failed to write diarize field: %w", err)
}
}
if err = writer.Close(); err != nil {
return nil, fmt.Errorf("failed to close multipart writer: %w", err)
}
url := fmt.Sprintf(c.endpoint + "/v1/speech-to-text")
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, body)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", writer.FormDataContentType())
req.Header.Set("User-Agent", "github.com/taigrr/elevenlabs")
req.Header.Set("xi-api-key", c.apiKey)
res, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to send request: %w", err)
}
switch res.StatusCode {
case 401:
return nil, ErrUnauthorized
case 200:
var sttResponse types.SpeechToTextResponse
if err := json.NewDecoder(res.Body).Decode(&sttResponse); err != nil {
return nil, fmt.Errorf("failed to parse API response: %w", err)
}
return &sttResponse, nil
case 422:
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 nil, err
case 400:
fallthrough
default:
ve := types.ParamError{}
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 nil, err
}
}

View File

@@ -12,34 +12,100 @@ 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(&params)
}
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(&params)
}
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(&params)
}
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"
}
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)
res, err := c.httpClient.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 +117,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
}
}

View File

@@ -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
@@ -31,11 +37,35 @@ func (so *SynthesisOptions) Clamp() {
if so.SimilarityBoost > 1 || so.SimilarityBoost < 0 {
so.SimilarityBoost = 0.75
}
if so.Style > 1 || so.Style < 0 {
so.Style = 0.0
}
if so.UseSpeakerBoost != true && so.UseSpeakerBoost != false {
so.UseSpeakerBoost = true
}
}
type SynthesisOptions struct {
Stability float64 `json:"stability"`
SimilarityBoost float64 `json:"similarity_boost"`
Style float64 `json:"style"`
UseSpeakerBoost bool `json:"use_speaker_boost"`
}
type SharingOptions struct {
Status string `json:"status"`
HistoryItemSampleId string `json:"history_item_sample_id"`
OriginalVoiceId string `json:"original_voice_id"`
PublicOwnerId string `json:"public_owner_id"`
LikedByCount int32 `json:"liked_by_count"`
ClonedByCount int32 `json:"cloned_by_count"`
WhitelistedEmails []string `json:"whitelisted_emails"`
Name string `json:"name"`
Labels map[string]string `json:"labels"`
Description string `json:"description"`
ReviewStatus string `json:"review_status"`
ReviewMessage string `json:"review_message"`
EnabledInLibrary bool `json:"enabled_in_library"`
}
type ExtendedSubscriptionResponseModel struct {
@@ -172,6 +202,17 @@ func (ve ValidationError) Error() string {
return fmt.Sprintf("%s %s: ", ve.Type_, ve.Msg)
}
type ParamError struct {
Detail struct {
Status string `json:"status"`
Message string `json:"message"`
} `json:"detail"`
}
func (pe ParamError) Error() string {
return fmt.Sprintf("%s %s: ", pe.Detail.Status, pe.Detail.Message)
}
type VerificationAttemptResponseModel struct {
Text string `json:"text"`
DateUnix int32 `json:"date_unix"`
@@ -181,14 +222,95 @@ type VerificationAttemptResponseModel struct {
Recording *RecordingResponseModel `json:"recording"`
}
type VoiceResponseModel struct {
VoiceID string `json:"voice_id"`
Name string `json:"name"`
Samples []Sample `json:"samples"`
Category string `json:"category"`
FineTuning FineTuningResponseModel `json:"fine_tuning"`
Labels map[string]string `json:"labels"`
Description string `json:"description"`
PreviewURL string `json:"preview_url"`
AvailableForTiers []string `json:"available_for_tiers"`
Settings SynthesisOptions `json:"settings"`
VoiceID string `json:"voice_id"`
Name string `json:"name"`
Samples []Sample `json:"samples"`
Category string `json:"category"`
FineTuning FineTuningResponseModel `json:"fine_tuning"`
Labels map[string]string `json:"labels"`
Description string `json:"description"`
PreviewURL string `json:"preview_url"`
AvailableForTiers []string `json:"available_for_tiers"`
Settings SynthesisOptions `json:"settings"`
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.
}
type TimestampsGranularity string
const (
// TimestampsGranularityNone represents no timestamps
TimestampsGranularityNone TimestampsGranularity = "none"
// TimestampsGranularityWord represents word-level timestamps
TimestampsGranularityWord TimestampsGranularity = "word"
// TimestampsGranularityCharacter represents character-level timestamps
TimestampsGranularityCharacter TimestampsGranularity = "character"
)
type SpeechToTextModel string
const (
SpeechToTextModelScribeV1 SpeechToTextModel = "scribe_v1"
)
// SpeechToTextRequest represents a request to the speech-to-text API
type SpeechToTextRequest struct {
// The ID of the model to use for transcription (currently only 'scribe_v1')
ModelID SpeechToTextModel `json:"model_id"`
// ISO-639-1 or ISO-639-3 language code. If not specified, language is auto-detected
LanguageCode string `json:"language_code,omitempty"`
// Whether to tag audio events like (laughter), (footsteps), etc.
TagAudioEvents bool `json:"tag_audio_events,omitempty"`
// Number of speakers (1-32). If not specified, uses model's maximum supported
NumSpeakers int `json:"num_speakers,omitempty"`
// Granularity of timestamps: "none", "word", or "character"
TimestampsGranularity TimestampsGranularity `json:"timestamps_granularity,omitempty"`
// Whether to annotate speaker changes (limits input to 8 minutes)
Diarize bool `json:"diarize,omitempty"`
}
// SpeechToTextResponse represents the response from the speech-to-text API
type SpeechToTextResponse struct {
// ISO-639-1 language code
LanguageCode string `json:"language_code"`
// The probability of the detected language
LanguageProbability float64 `json:"language_probability"`
// The transcribed text
Text string `json:"text"`
// Detailed word-level information
Words []TranscriptionWord `json:"words"`
// Error message, if any
Error string `json:"error,omitempty"`
}
// TranscriptionWord represents a word or spacing in the transcription
type TranscriptionWord struct {
// The text content of the word/spacing
Text string `json:"text"`
// Type of segment ("word" or "spacing")
Type string `json:"type"`
// Start time in seconds
Start float64 `json:"start"`
// End time in seconds
End float64 `json:"end"`
// Speaker identifier for multi-speaker transcriptions
SpeakerID string `json:"speaker_id,omitempty"`
// Character-level information
Characters []TranscriptionCharacter `json:"characters,omitempty"`
}
// TranscriptionCharacter represents character-level information in the transcription
type TranscriptionCharacter struct {
// The text content of the character
Text string `json:"text"`
// Start time in seconds
Start float64 `json:"start"`
// End time in seconds
End float64 `json:"end"`
}

View File

@@ -11,7 +11,6 @@ import (
func (c Client) GetUserInfo(ctx context.Context) (types.UserResponseModel, error) {
url := c.endpoint + "/v1/user"
client := &http.Client{}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return types.UserResponseModel{}, err
@@ -19,7 +18,7 @@ func (c Client) GetUserInfo(ctx context.Context) (types.UserResponseModel, error
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)
res, err := c.httpClient.Do(req)
if err != nil {
return types.UserResponseModel{}, err
}

View File

@@ -17,8 +17,6 @@ import (
func (c Client) CreateVoice(ctx context.Context, name, description string, labels []string, files []*os.File) error {
url := c.endpoint + "/v1/voices/add"
client := &http.Client{}
var b bytes.Buffer
w := multipart.NewWriter(&b)
for _, r := range files {
@@ -43,7 +41,7 @@ func (c Client) CreateVoice(ctx context.Context, name, description string, label
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)
res, err := c.httpClient.Do(req)
if err != nil {
return err
}
@@ -69,7 +67,6 @@ func (c Client) CreateVoice(ctx context.Context, name, description string, label
func (c Client) DeleteVoice(ctx context.Context, voiceID string) error {
url := fmt.Sprintf(c.endpoint+"/v1/voices/%s", voiceID)
client := &http.Client{}
req, err := http.NewRequestWithContext(ctx, http.MethodDelete, url, nil)
if err != nil {
return err
@@ -77,7 +74,7 @@ func (c Client) DeleteVoice(ctx context.Context, voiceID string) error {
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)
res, err := c.httpClient.Do(req)
if err != nil {
return err
}
@@ -103,7 +100,7 @@ func (c Client) DeleteVoice(ctx context.Context, voiceID string) error {
func (c Client) EditVoiceSettings(ctx context.Context, voiceID string, settings types.SynthesisOptions) error {
url := fmt.Sprintf(c.endpoint+"/v1/voices/%s/settings/edit", voiceID)
client := &http.Client{}
b, _ := json.Marshal(settings)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(b))
@@ -113,7 +110,7 @@ func (c Client) EditVoiceSettings(ctx context.Context, voiceID string, settings
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)
res, err := c.httpClient.Do(req)
if err != nil {
return err
}
@@ -145,7 +142,6 @@ func (c Client) EditVoiceSettings(ctx context.Context, voiceID string, settings
func (c Client) EditVoice(ctx context.Context, voiceID, name, description string, labels []string, files []*os.File) error {
url := fmt.Sprintf(c.endpoint+"/v1/voices/%s/edit", voiceID)
client := &http.Client{}
var b bytes.Buffer
w := multipart.NewWriter(&b)
@@ -171,7 +167,7 @@ func (c Client) EditVoice(ctx context.Context, voiceID, name, description string
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)
res, err := c.httpClient.Do(req)
if err != nil {
return err
}
@@ -195,49 +191,9 @@ 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{}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return types.SynthesisOptions{}, err
@@ -245,7 +201,7 @@ func (c Client) GetVoiceSettings(ctx context.Context, voiceID string) (types.Syn
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)
res, err := c.httpClient.Do(req)
if err != nil {
return types.SynthesisOptions{}, err
}
@@ -277,7 +233,7 @@ func (c Client) GetVoiceSettings(ctx context.Context, voiceID string) (types.Syn
func (c Client) GetVoice(ctx context.Context, voiceID string) (types.VoiceResponseModel, error) {
url := fmt.Sprintf(c.endpoint+"/v1/voices/%s", voiceID)
client := &http.Client{}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return types.VoiceResponseModel{}, err
@@ -285,7 +241,7 @@ func (c Client) GetVoice(ctx context.Context, voiceID string) (types.VoiceRespon
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)
res, err := c.httpClient.Do(req)
switch res.StatusCode {
case 401:
return types.VoiceResponseModel{}, ErrUnauthorized
@@ -319,7 +275,7 @@ func (c Client) GetVoice(ctx context.Context, voiceID string) (types.VoiceRespon
func (c Client) GetVoices(ctx context.Context) ([]types.VoiceResponseModel, error) {
url := c.endpoint + "/v1/voices"
client := &http.Client{}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return []types.VoiceResponseModel{}, err
@@ -327,7 +283,7 @@ func (c Client) GetVoices(ctx context.Context) ([]types.VoiceResponseModel, erro
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)
res, err := c.httpClient.Do(req)
switch res.StatusCode {
case 401:
return []types.VoiceResponseModel{}, ErrUnauthorized

View File

@@ -6,6 +6,7 @@ import (
"io"
"log"
"os"
"strings"
"time"
"github.com/faiface/beep"
@@ -25,12 +26,23 @@ func main() {
}
pipeReader, pipeWriter := io.Pipe()
reader := bufio.NewReader(os.Stdin)
b, _ := io.ReadAll(reader)
text := string(b)
// record how long it takes to run and print out on exit
start := time.Now()
defer func() {
log.Println(time.Since(start))
}()
var text string
if len(os.Args) > 1 {
text = strings.Join(os.Args[1:], " ")
} else {
reader := bufio.NewReader(os.Stdin)
b, _ := io.ReadAll(reader)
text = string(b)
}
go func() {
err = client.TTSStream(ctx, pipeWriter, text, ids[0], types.SynthesisOptions{Stability: 0.75, SimilarityBoost: 0.75})
err = client.TTSStream(ctx, pipeWriter, text, ids[0], types.SynthesisOptions{Stability: 0.75, SimilarityBoost: 0.75, Style: 0.0, UseSpeakerBoost: false})
if err != nil {
panic(err)
}

3
cmd/transcribe/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
*.mp3
main
transcribe

34
cmd/transcribe/main.go Normal file
View File

@@ -0,0 +1,34 @@
package main
import (
"context"
"encoding/json"
"fmt"
"os"
"github.com/taigrr/elevenlabs/client"
"github.com/taigrr/elevenlabs/client/types"
)
func main() {
ctx := context.Background()
client := client.New(os.Getenv("XI_API_KEY"))
filePath := os.Args[1]
resp, err := client.ConvertSpeechToText(ctx, filePath, types.SpeechToTextRequest{
ModelID: types.SpeechToTextModelScribeV1,
TimestampsGranularity: types.TimestampsGranularityWord,
Diarize: true,
})
if err != nil {
panic(err)
}
bytes, err := json.Marshal(resp)
if err != nil {
panic(err)
}
fmt.Println(string(bytes))
}

12
go.mod
View File

@@ -1,6 +1,8 @@
module github.com/taigrr/elevenlabs
go 1.20
go 1.23.0
toolchain go1.24.0
require github.com/faiface/beep v1.1.0
@@ -9,8 +11,8 @@ 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-20230817173708-d852ddb80c63 // indirect
golang.org/x/image v0.11.0 // indirect
golang.org/x/mobile v0.0.0-20230818142238-7088062f872d // indirect
golang.org/x/sys v0.11.0 // indirect
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
golang.org/x/image v0.26.0 // indirect
golang.org/x/mobile v0.0.0-20250408133729-978277e7eaf7 // indirect
golang.org/x/sys v0.32.0 // indirect
)

38
go.sum
View File

@@ -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,22 +31,29 @@ 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-20250228200357-dead58393ab7 h1:VxTRg3kpOpYQ+S2PlDH9x2j/ZOQMxVsPgdYYRvkErNY=
golang.org/x/exp/shiny v0.0.0-20250228200357-dead58393ab7/go.mod h1:ygj7T6vSGhhm/9yTpOQQNvuAUFziTH7RUiH74EoE2C8=
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 h1:tMSqXTK+AQdW3LpCbfatHSRPHeW6+2WuxaVQuHftn80=
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:ygj7T6vSGhhm/9yTpOQQNvuAUFziTH7RUiH74EoE2C8=
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/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c=
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/mobile v0.0.0-20250218173827-cd096645fcd3 h1:0V/7Y1FEaFdAzb9DkVDh4QFp4vL4yYCiJ5cjk80lZyA=
golang.org/x/mobile v0.0.0-20250218173827-cd096645fcd3/go.mod h1:j5VYNgQ6lZYZlzHFjdgS2UeqRSZunDk+/zXVTAIA3z4=
golang.org/x/mobile v0.0.0-20250408133729-978277e7eaf7 h1:8MGTx39304caZ/OMsjPfuxUoDGI2tRas92F5x97tIYc=
golang.org/x/mobile v0.0.0-20250408133729-978277e7eaf7/go.mod h1:ftACcHgQ7vaOnQbHOHvXt9Y6bEPHrs5Ovk67ClwrPJA=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@@ -66,7 +71,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=
@@ -74,8 +78,12 @@ 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/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -83,7 +91,7 @@ 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=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=