17 Commits

Author SHA1 Message Date
fzqxzhang
261398509a feat: model_id tag add omitempty (#3) 2023-07-25 15:43:48 +00:00
Marcel Molina
b925ef1471 Fix compile error from variable typo (#2)
* Fix compile error from variable typo

* bytes.Buffer pointer
2023-07-09 16:00:45 +00:00
e095a7ec13 don't check resposne code before checcking err !=nil 2023-06-27 12:31:56 -07:00
32972d4ff2 Merge branch 'master' of github.com:taigrr/elevenlabs 2023-06-26 20:09:02 -07:00
84e59417d6 fix nil error pointed out by @Davincible , closes #1 2023-06-26 20:08:51 -07:00
3e3b7004b8 update to newer release of api 2023-05-12 23:42:59 -07:00
6fcc65115d add comments to the readme code 2023-04-19 14:02:09 -07:00
5451bcd0b1 fix readme and say 2023-04-19 14:00:03 -07:00
58581c3c46 add example binary to readme 2023-04-18 22:35:17 -07:00
544f24cab1 add example section 2023-04-18 22:32:17 -07:00
35f72a819b update copyright year 2023-04-18 22:31:02 -07:00
0d72be9d50 rename license 2023-04-18 22:23:48 -07:00
899a127e7a add badges and license 2023-04-18 22:23:03 -07:00
8a27f7df64 add example usage to readme 2023-04-18 22:19:04 -07:00
fb146435fd remove tts 2023-04-18 22:16:27 -07:00
e2bb856589 add example streamer 2023-04-18 22:16:04 -07:00
48f074cc9c add sample say program 2023-04-18 22:09:25 -07:00
9 changed files with 202 additions and 30 deletions

12
LICENSE Normal file
View File

@@ -0,0 +1,12 @@
Copyright (C) 2023 by Tai Groot <tai@taigrr.com>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@@ -1,5 +1,11 @@
# elevenlabs # elevenlabs
Unofficial [elevenlabs.io](https://beta.elevenlabs.io/) ([11.ai](11.ai)) voice synthesis client [![License 0BSD](https://img.shields.io/badge/License-0BSD-pink.svg)](https://opensource.org/licenses/0BSD)
[![GoDoc](https://godoc.org/github.com/taigrr/elevenlabs?status.svg)](https://godoc.org/github.com/taigrr/elevenlabs)
[![Go Mod](https://img.shields.io/badge/go.mod-v1.20-blue)](go.mod)
[![Go Report Card](https://goreportcard.com/badge/github.com/taigrr/elevenlabs?branch=master)](https://goreportcard.com/report/github.com/taigrr/elevenlabs)
Unofficial [elevenlabs.io](https://beta.elevenlabs.io/) ([11.ai](http://11.ai)) voice synthesis client
This library is not affiliated with, nor associated with ElevenLabs in any way. This library is not affiliated with, nor associated with ElevenLabs in any way.
@@ -12,7 +18,75 @@ make TTS (text-to-speech) requests to elevenlabs.io
As a prerequisite, you must already have an account with elevenlabs.io. As a prerequisite, you must already have an account with elevenlabs.io.
After creating your account, you can get you API key [from here](https://help.elevenlabs.io/hc/en-us/articles/14599447207697-How-to-authorize-yourself-using-your-xi-api-key-). After creating your account, you can get your API key [from here](https://help.elevenlabs.io/hc/en-us/articles/14599447207697-How-to-authorize-yourself-using-your-xi-api-key-).
## Test Program
To test out an example `say` program, run:
`go install github.com/taigrr/elevenlabs/cmd/say@latest`
Set the `XI_API_KEY` environment variable, and pipe it some text to give it a whirl!
## Example Code
To use this library, create a new client and send a TTS request to a voice. 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.
I've opted to go with faiface's beep package, but you can also save the file
to an mp3 on-disk.
```go
package main
import (
"bufio"
"context"
"io"
"log"
"os"
"time"
"github.com/faiface/beep"
"github.com/faiface/beep/mp3"
"github.com/faiface/beep/speaker"
"github.com/taigrr/elevenlabs/client"
"github.com/taigrr/elevenlabs/client/types"
)
func main() {
ctx := context.Background()
// load in an API key to create a client
client := client.New(os.Getenv("XI_API_KEY"))
// fetch a list of voice IDs from elevenlabs
ids, err := client.GetVoiceIDs(ctx)
if err != nil {
panic(err)
}
// prepare a pipe for streaming audio directly to beep
pipeReader, pipeWriter := io.Pipe()
reader := bufio.NewReader(os.Stdin)
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})
if err != nil {
panic(err)
}
pipeWriter.Close()
}()
// decode and prepare the streaming mp3 as it comes through
streamer, format, err := mp3.Decode(pipeReader)
if err != nil {
log.Fatal(err)
}
defer streamer.Close()
speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10))
done := make(chan bool)
// play the audio
speaker.Play(beep.Seq(streamer, beep.Callback(func() {
done <- true
})))
<-done
}
```

View File

@@ -1,7 +1,6 @@
package client package client
import ( import (
"bufio"
"bytes" "bytes"
"context" "context"
"encoding/json" "encoding/json"
@@ -13,11 +12,12 @@ import (
"github.com/taigrr/elevenlabs/client/types" "github.com/taigrr/elevenlabs/client/types"
) )
func (c Client) TTSWriter(ctx context.Context, w io.Writer, text, voiceID string, options types.SynthesisOptions) error { func (c Client) TTSWriter(ctx context.Context, w io.Writer, text, modelID, voiceID string, options types.SynthesisOptions) error {
options.Clamp() options.Clamp()
url := fmt.Sprintf(c.endpoint+"/v1/text-to-speech/%s", voiceID) url := fmt.Sprintf(c.endpoint+"/v1/text-to-speech/%s", voiceID)
opts := types.TTS{ opts := types.TTS{
Text: text, Text: text,
ModelID: modelID,
VoiceSettings: options, VoiceSettings: options,
} }
b, _ := json.Marshal(opts) b, _ := json.Marshal(opts)
@@ -30,14 +30,13 @@ func (c Client) TTSWriter(ctx context.Context, w io.Writer, text, voiceID string
req.Header.Set("User-Agent", "github.com/taigrr/elevenlabs") req.Header.Set("User-Agent", "github.com/taigrr/elevenlabs")
req.Header.Set("accept", "audio/mpeg") req.Header.Set("accept", "audio/mpeg")
res, err := client.Do(req) res, err := client.Do(req)
if err != nil {
return err
}
switch res.StatusCode { switch res.StatusCode {
case 401: case 401:
return ErrUnauthorized return ErrUnauthorized
case 200: case 200:
if err != nil {
return err
}
defer res.Body.Close() defer res.Body.Close()
io.Copy(w, res.Body) io.Copy(w, res.Body)
return nil return nil
@@ -56,12 +55,13 @@ func (c Client) TTSWriter(ctx context.Context, w io.Writer, text, voiceID string
} }
} }
func (c Client) TTS(ctx context.Context, text, voiceID string, options types.SynthesisOptions) ([]byte, error) { func (c Client) TTS(ctx context.Context, text, voiceID, modelID string, options types.SynthesisOptions) ([]byte, error) {
options.Clamp() options.Clamp()
url := fmt.Sprintf(c.endpoint+"/v1/text-to-speech/%s", voiceID) url := fmt.Sprintf(c.endpoint+"/v1/text-to-speech/%s", voiceID)
client := &http.Client{} client := &http.Client{}
opts := types.TTS{ opts := types.TTS{
Text: text, Text: text,
ModelID: modelID,
VoiceSettings: options, VoiceSettings: options,
} }
b, _ := json.Marshal(opts) b, _ := json.Marshal(opts)
@@ -73,19 +73,17 @@ func (c Client) TTS(ctx context.Context, text, voiceID string, options types.Syn
req.Header.Set("User-Agent", "github.com/taigrr/elevenlabs") req.Header.Set("User-Agent", "github.com/taigrr/elevenlabs")
req.Header.Set("accept", "audio/mpeg") req.Header.Set("accept", "audio/mpeg")
res, err := client.Do(req) res, err := client.Do(req)
if err != nil {
return []byte{}, err
}
switch res.StatusCode { switch res.StatusCode {
case 401: case 401:
return []byte{}, ErrUnauthorized return []byte{}, ErrUnauthorized
case 200: case 200:
if err != nil {
return []byte{}, err
}
b := bytes.Buffer{} b := bytes.Buffer{}
w := bufio.NewWriter(&b)
defer res.Body.Close() defer res.Body.Close()
io.Copy(w, res.Body) io.Copy(&b, res.Body)
return b.Bytes(), nil return b.Bytes(), nil
case 422: case 422:
fallthrough fallthrough
@@ -119,14 +117,13 @@ func (c Client) TTSStream(ctx context.Context, w io.Writer, text, voiceID string
req.Header.Set("User-Agent", "github.com/taigrr/elevenlabs") req.Header.Set("User-Agent", "github.com/taigrr/elevenlabs")
req.Header.Set("accept", "audio/mpeg") req.Header.Set("accept", "audio/mpeg")
res, err := client.Do(req) res, err := client.Do(req)
if err != nil {
return err
}
switch res.StatusCode { switch res.StatusCode {
case 401: case 401:
return ErrUnauthorized return ErrUnauthorized
case 200: case 200:
if err != nil {
return err
}
defer res.Body.Close() defer res.Body.Close()
io.Copy(w, res.Body) io.Copy(w, res.Body)
return nil return nil

View File

@@ -19,6 +19,7 @@ type Voice struct {
Labels string `json:"labels,omitempty"` // Serialized labels dictionary for the voice. Labels string `json:"labels,omitempty"` // Serialized labels dictionary for the voice.
} }
type TTS struct { type TTS struct {
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. Currently only English text is supported.
VoiceSettings SynthesisOptions `json:"voice_settings,omitempty"` // Voice settings are applied only on the given TTS request. VoiceSettings SynthesisOptions `json:"voice_settings,omitempty"` // Voice settings are applied only on the given TTS request.
} }
@@ -103,6 +104,22 @@ type LanguageResponseModel struct {
IsoCode string `json:"iso_code"` IsoCode string `json:"iso_code"`
DisplayName string `json:"display_name"` DisplayName string `json:"display_name"`
} }
type Language struct {
LanguageID string `json:"language_id"`
Name string `json:"name"`
}
type ModelResponseModel struct {
ModelID string `json:"model_id"`
Name string `json:"name"`
Description string `json:"description"`
CanBeFinetuned bool `json:"can_be_finetuned"`
CanDoTextToSpeech bool `json:"can_do_text_to_speech"`
CanDoVoiceConversion bool `json:"can_do_voice_conversion"`
TokenCostFactor float64 `json:"token_cost_factor"`
Languages []Language `json:"languages"`
}
type RecordingResponseModel struct { type RecordingResponseModel struct {
RecordingID string `json:"recording_id"` RecordingID string `json:"recording_id"`
MimeType string `json:"mime_type"` MimeType string `json:"mime_type"`

1
cmd/say/.gitignore vendored
View File

@@ -1,2 +1,3 @@
*.mp3 *.mp3
main main
say

View File

@@ -1,8 +1,16 @@
package main package main
import ( import (
"bufio"
"context" "context"
"io"
"log"
"os" "os"
"time"
"github.com/faiface/beep"
"github.com/faiface/beep/mp3"
"github.com/faiface/beep/speaker"
"github.com/taigrr/elevenlabs/client" "github.com/taigrr/elevenlabs/client"
"github.com/taigrr/elevenlabs/client/types" "github.com/taigrr/elevenlabs/client/types"
@@ -15,13 +23,30 @@ func main() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
saveFile, err := os.Create("sample.mp3") pipeReader, pipeWriter := io.Pipe()
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})
if err != nil {
panic(err)
}
pipeWriter.Close()
}()
streamer, format, err := mp3.Decode(pipeReader)
if err != nil { if err != nil {
panic(err) log.Fatal(err)
}
defer saveFile.Close()
err = client.TTSWriter(ctx, saveFile, "hello, golang", ids[0], types.SynthesisOptions{Stability: 0.75, SimilarityBoost: 0.75})
if err != nil {
panic(err)
} }
defer streamer.Close()
speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10))
done := make(chan bool)
speaker.Play(beep.Seq(streamer, beep.Callback(func() {
done <- true
})))
<-done
} }

View File

@@ -1,4 +0,0 @@
package main
func main() {
}

12
go.mod
View File

@@ -1,3 +1,15 @@
module github.com/taigrr/elevenlabs module github.com/taigrr/elevenlabs
go 1.20 go 1.20
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/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
)

38
go.sum
View File

@@ -0,0 +1,38 @@
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=
github.com/faiface/beep v1.1.0/go.mod h1:6I8p6kK2q4opL/eWb+kAkk38ehnTunWeToJB+s51sT4=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
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/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/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=
github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0=
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mewkiz/flac v1.0.7/go.mod h1:yU74UH277dBUpqxPouHSQIar3G1X/QIclVbFahSd1pU=
github.com/mewkiz/pkg v0.0.0-20190919212034-518ade7978e2/go.mod h1:3E2FUC/qYUfM8+r9zAwpeHJzqRVVMIYnpzD/clwWxyA=
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=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
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/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=