mirror of
https://github.com/taigrr/elevenlabs.git
synced 2026-04-02 03:08:57 -07:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fba03ed0be | |||
| aa701237ff | |||
|
|
29fa401714 | ||
| 77d17e20fb | |||
|
|
93af72dc7c | ||
|
|
db0a2e1760 | ||
|
|
c585531fae | ||
|
|
41f142ec2c | ||
|
6ebcddb891
|
|||
|
ae598ecc4b
|
|||
|
|
85bc7b2007 |
50
README.md
50
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.
|
||||
@@ -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)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -151,14 +151,14 @@ func (c Client) HistoryDownloadAudioWriter(ctx context.Context, w io.Writer, ID
|
||||
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:
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
io.Copy(w, res.Body)
|
||||
return nil
|
||||
@@ -188,14 +188,14 @@ func (c Client) HistoryDownloadAudio(ctx context.Context, ID string) ([]byte, er
|
||||
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:
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
b := bytes.Buffer{}
|
||||
w := bufio.NewWriter(&b)
|
||||
|
||||
@@ -228,15 +228,14 @@ func (c Client) GetHistoryItemList(ctx context.Context) ([]types.HistoryItemList
|
||||
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.HistoryItemList{}, err
|
||||
}
|
||||
|
||||
switch res.StatusCode {
|
||||
case 401:
|
||||
return []types.HistoryItemList{}, ErrUnauthorized
|
||||
case 200:
|
||||
if err != nil {
|
||||
return []types.HistoryItemList{}, err
|
||||
}
|
||||
|
||||
var history types.GetHistoryResponse
|
||||
defer res.Body.Close()
|
||||
jerr := json.NewDecoder(res.Body).Decode(&history)
|
||||
|
||||
@@ -25,14 +25,13 @@ func (c Client) DeleteVoiceSample(ctx context.Context, voiceID, sampleID string)
|
||||
req.Header.Set("xi-api-key", c.apiKey)
|
||||
req.Header.Set("User-Agent", "github.com/taigrr/elevenlabs")
|
||||
res, err := client.Do(req)
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
switch res.StatusCode {
|
||||
case 401:
|
||||
return false, ErrUnauthorized
|
||||
case 200:
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
case 422:
|
||||
fallthrough
|
||||
@@ -60,14 +59,14 @@ func (c Client) DownloadVoiceSampleWriter(ctx context.Context, w io.Writer, voic
|
||||
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:
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
io.Copy(w, res.Body)
|
||||
return nil
|
||||
@@ -97,14 +96,14 @@ func (c Client) DownloadVoiceSample(ctx context.Context, voiceID, sampleID strin
|
||||
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:
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
b := bytes.Buffer{}
|
||||
w := bufio.NewWriter(&b)
|
||||
|
||||
|
||||
98
client/sound_gen.go
Normal file
98
client/sound_gen.go
Normal file
@@ -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
|
||||
}
|
||||
127
client/stt.go
Normal file
127
client/stt.go
Normal file
@@ -0,0 +1,127 @@
|
||||
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)
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
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 := client.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
|
||||
}
|
||||
}
|
||||
158
client/tts.go
158
client/tts.go
@@ -12,121 +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,
|
||||
}
|
||||
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
|
||||
func WithPreviousText(previousText string) types.TTSParam {
|
||||
return func(tts *types.TTS) {
|
||||
tts.PreviousText = previousText
|
||||
}
|
||||
}
|
||||
|
||||
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{
|
||||
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,
|
||||
VoiceSettings: options,
|
||||
}
|
||||
b, _ := json.Marshal(opts)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(b))
|
||||
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
|
||||
}
|
||||
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:
|
||||
defer body.Close()
|
||||
b := bytes.Buffer{}
|
||||
|
||||
defer res.Body.Close()
|
||||
io.Copy(&b, res.Body)
|
||||
io.Copy(&b, 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{
|
||||
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,
|
||||
VoiceSettings: options,
|
||||
VoiceID: voiceID,
|
||||
Stream: true,
|
||||
}
|
||||
b, _ := json.Marshal(opts)
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(b))
|
||||
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:
|
||||
@@ -138,6 +118,6 @@ func (c Client) TTSStream(ctx context.Context, w io.Writer, text, voiceID string
|
||||
} else {
|
||||
err = errors.Join(err, ve)
|
||||
}
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
@@ -191,4 +232,85 @@ type VoiceResponseModel struct {
|
||||
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"`
|
||||
}
|
||||
|
||||
@@ -20,15 +20,13 @@ func (c Client) GetUserInfo(ctx context.Context) (types.UserResponseModel, error
|
||||
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.UserResponseModel{}, err
|
||||
}
|
||||
switch res.StatusCode {
|
||||
case 401:
|
||||
return types.UserResponseModel{}, ErrUnauthorized
|
||||
case 200:
|
||||
if err != nil {
|
||||
return types.UserResponseModel{}, err
|
||||
}
|
||||
|
||||
var user types.UserResponseModel
|
||||
defer res.Body.Close()
|
||||
jerr := json.NewDecoder(res.Body).Decode(&user)
|
||||
@@ -53,5 +51,8 @@ func (c Client) GetUserInfo(ctx context.Context) (types.UserResponseModel, error
|
||||
|
||||
func (c Client) GetSubscriptionInfo(ctx context.Context) (types.Subscription, error) {
|
||||
info, err := c.GetUserInfo(ctx)
|
||||
if err != nil {
|
||||
return types.Subscription{}, err
|
||||
}
|
||||
return info.Subscription, err
|
||||
}
|
||||
|
||||
@@ -44,13 +44,13 @@ func (c Client) CreateVoice(ctx context.Context, name, description string, label
|
||||
req.Header.Set("User-Agent", "github.com/taigrr/elevenlabs")
|
||||
req.Header.Set("accept", "application/json")
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch res.StatusCode {
|
||||
case 401:
|
||||
return ErrUnauthorized
|
||||
case 200:
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
case 422:
|
||||
fallthrough
|
||||
@@ -78,13 +78,13 @@ func (c Client) DeleteVoice(ctx context.Context, voiceID string) error {
|
||||
req.Header.Set("User-Agent", "github.com/taigrr/elevenlabs")
|
||||
req.Header.Set("accept", "application/json")
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch res.StatusCode {
|
||||
case 401:
|
||||
return ErrUnauthorized
|
||||
case 200:
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
case 422:
|
||||
fallthrough
|
||||
@@ -114,13 +114,13 @@ func (c Client) EditVoiceSettings(ctx context.Context, voiceID string, settings
|
||||
req.Header.Set("User-Agent", "github.com/taigrr/elevenlabs")
|
||||
req.Header.Set("accept", "application/json")
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch res.StatusCode {
|
||||
case 401:
|
||||
return ErrUnauthorized
|
||||
case 200:
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
so := types.SynthesisOptions{}
|
||||
defer res.Body.Close()
|
||||
jerr := json.NewDecoder(res.Body).Decode(&so)
|
||||
@@ -172,13 +172,13 @@ func (c Client) EditVoice(ctx context.Context, voiceID, name, description string
|
||||
req.Header.Set("User-Agent", "github.com/taigrr/elevenlabs")
|
||||
req.Header.Set("accept", "application/json")
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch res.StatusCode {
|
||||
case 401:
|
||||
return ErrUnauthorized
|
||||
case 200:
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
case 422:
|
||||
fallthrough
|
||||
@@ -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)
|
||||
switch res.StatusCode {
|
||||
case 401:
|
||||
return types.SynthesisOptions{}, ErrUnauthorized
|
||||
case 200:
|
||||
if err != nil {
|
||||
return types.SynthesisOptions{}, err
|
||||
}
|
||||
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{}
|
||||
@@ -246,13 +206,13 @@ func (c Client) GetVoiceSettings(ctx context.Context, voiceID string) (types.Syn
|
||||
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:
|
||||
if err != nil {
|
||||
return types.SynthesisOptions{}, err
|
||||
}
|
||||
so := types.SynthesisOptions{}
|
||||
defer res.Body.Close()
|
||||
jerr := json.NewDecoder(res.Body).Decode(&so)
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/faiface/beep"
|
||||
@@ -25,12 +26,23 @@ func main() {
|
||||
}
|
||||
pipeReader, pipeWriter := io.Pipe()
|
||||
|
||||
// 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)
|
||||
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
3
cmd/transcribe/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
*.mp3
|
||||
main
|
||||
transcribe
|
||||
34
cmd/transcribe/main.go
Normal file
34
cmd/transcribe/main.go
Normal 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))
|
||||
}
|
||||
17
go.mod
17
go.mod
@@ -1,15 +1,18 @@
|
||||
module github.com/taigrr/elevenlabs
|
||||
|
||||
go 1.20
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.24.0
|
||||
|
||||
require github.com/faiface/beep v1.1.0
|
||||
|
||||
require (
|
||||
github.com/hajimehoshi/go-mp3 v0.3.0 // indirect
|
||||
github.com/hajimehoshi/oto v0.7.1 // indirect
|
||||
github.com/hajimehoshi/go-mp3 v0.3.4 // indirect
|
||||
github.com/hajimehoshi/oto v1.0.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8 // indirect
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 // indirect
|
||||
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6 // indirect
|
||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 // indirect
|
||||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 // indirect
|
||||
golang.org/x/exp/shiny v0.0.0-20250228200357-dead58393ab7 // indirect
|
||||
golang.org/x/image v0.24.0 // indirect
|
||||
golang.org/x/mobile v0.0.0-20250218173827-cd096645fcd3 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
)
|
||||
|
||||
66
go.sum
66
go.sum
@@ -1,3 +1,4 @@
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/d4l3k/messagediff v1.2.2-0.20190829033028-7e0a312ae40b/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo=
|
||||
github.com/faiface/beep v1.1.0 h1:A2gWP6xf5Rh7RG/p9/VAW2jRSDEGQm5sbOb38sf5d4c=
|
||||
@@ -7,11 +8,14 @@ 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=
|
||||
github.com/hajimehoshi/oto/v2 v2.3.1/go.mod h1:seWLbgHH7AyUMYKfKYT9pg7PhUu9/SisyJvNTT+ASQo=
|
||||
github.com/icza/bitio v1.0.0/go.mod h1:0jGnlLAx8MKMr9VGnn/4YrvZiprkvBelsVIbA9Jjr9A=
|
||||
github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6/go.mod h1:xQig96I1VNBDIWGCdTt54nHt6EeI639SmHycLYL7FkA=
|
||||
github.com/jfreymuth/oggvorbis v1.0.1/go.mod h1:NqS+K+UXKje0FUYUPosyQ+XTVvjmVjps1aEZH1sumIk=
|
||||
@@ -23,16 +27,66 @@ github.com/mewkiz/pkg v0.0.0-20190919212034-518ade7978e2/go.mod h1:3E2FUC/qYUfM8
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8 h1:idBdZTd9UioThJp8KpM/rTSinK/ChZFBE43/WtIy8zg=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
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/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-20250228200357-dead58393ab7 h1:VxTRg3kpOpYQ+S2PlDH9x2j/ZOQMxVsPgdYYRvkErNY=
|
||||
golang.org/x/exp/shiny v0.0.0-20250228200357-dead58393ab7/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/mobile v0.0.0-20190415191353-3e0bab5405d6 h1:vyLBGJPIl9ZYbcQFM2USFmJBK6KI+t+z6jL0lbwjrnc=
|
||||
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/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
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/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=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
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=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.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/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=
|
||||
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.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=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
Reference in New Issue
Block a user