Skip to content

Commit

Permalink
add elevenlabs tts
Browse files Browse the repository at this point in the history
  • Loading branch information
adrianliechti committed Aug 20, 2024
1 parent a9c9e91 commit 30a5422
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 5 deletions.
24 changes: 20 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ providers:
```yaml
providers:
- type: cohere
token: ${COHERE_API_TOKEN}
token: ${COHERE_API_KEY}

# https://docs.cohere.com/docs/models
models:
Expand All @@ -105,7 +105,7 @@ providers:
```yaml
providers:
- type: groq
token: ${GROQ_API_TOKEN}
token: ${GROQ_API_KEY}

# https://console.groq.com/docs/models
models:
Expand All @@ -122,7 +122,7 @@ providers:
```yaml
providers:
- type: mistral
token: ${MISTRAL_API_TOKEN}
token: ${MISTRAL_API_KEY}

# https://docs.mistral.ai/getting-started/models/
models:
Expand All @@ -138,7 +138,7 @@ https://replicate.com/
```yaml
providers:
- type: replicate
token: ${REPLICATE_API_TOKEN}
token: ${REPLICATE_API_KEY}
models:
replicate-flux-pro:
id: black-forest-labs/flux-pro
Expand Down Expand Up @@ -262,6 +262,22 @@ providers:
```
#### Eleven Labs
```yaml
providers:
- type: elevenlabs
token: ${ELEVENLABS_API_KEY}

models:
elevenlabs-sarah:
id: EXAVITQu4vr4xnSDxMaL

elevenlabs-charlie:
id: IKne3meq5aSn9XLyUdCD
```
#### Mimic 3
```shell
Expand Down
6 changes: 5 additions & 1 deletion cmd/client/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,11 @@ LOOP:
continue LOOP
}

name := uuid.New().String() + ".wav"
name := uuid.New().String()

if ext, _ := mime.ExtensionsByType(http.DetectContentType(data)); len(ext) > 0 {
name += ext[0]
}

os.WriteFile(name, data, 0600)
fmt.Println("Saved: " + name)
Expand Down
1 change: 1 addition & 0 deletions config/config_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ func DetectModelType(id string) ModelType {
}

synthesizers := []string{
"eleven",
"stable-audio",
"tts",
}
Expand Down
4 changes: 4 additions & 0 deletions config/config_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import (
func (cfg *Config) registerProviders(f *configFile) error {
for _, p := range f.Providers {
for id, m := range p.Models {
if m.Type == "" {
m.Type = DetectModelType(m.ID)
}

if m.Type == "" {
m.Type = DetectModelType(id)
}
Expand Down
22 changes: 22 additions & 0 deletions config/config_synthesizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/adrianliechti/llama/pkg/provider"
"github.com/adrianliechti/llama/pkg/provider/coqui"
"github.com/adrianliechti/llama/pkg/provider/elevenlabs"
"github.com/adrianliechti/llama/pkg/provider/mimic"
"github.com/adrianliechti/llama/pkg/provider/openai"

Expand Down Expand Up @@ -43,6 +44,9 @@ func createSynthesizer(cfg providerConfig, model modelContext) (provider.Synthes
case "coqui":
return coquiSynthesizer(cfg, model)

case "elevenlabs":
return elevenlabsSynthesizer(cfg, model)

case "mimic":
return mimicSynthesizer(cfg, model)

Expand All @@ -60,6 +64,24 @@ func coquiSynthesizer(cfg providerConfig, model modelContext) (provider.Synthesi
return coqui.NewSynthesizer(cfg.URL, options...)
}

func elevenlabsSynthesizer(cfg providerConfig, model modelContext) (provider.Synthesizer, error) {
var options []elevenlabs.Option

if cfg.URL != "" {
options = append(options, elevenlabs.WithURL(cfg.URL))
}

if cfg.Token != "" {
options = append(options, elevenlabs.WithToken(cfg.Token))
}

if model.ID != "" {
options = append(options, elevenlabs.WithModel(model.ID))
}

return elevenlabs.NewSynthesizer(options...)
}

func mimicSynthesizer(cfg providerConfig, model modelContext) (provider.Synthesizer, error) {
var options []mimic.Option

Expand Down
40 changes: 40 additions & 0 deletions pkg/provider/elevenlabs/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package elevenlabs

import (
"net/http"
)

type Config struct {
url string

token string
model string

client *http.Client
}

type Option func(*Config)

func WithClient(client *http.Client) Option {
return func(c *Config) {
c.client = client
}
}

func WithURL(url string) Option {
return func(c *Config) {
c.url = url
}
}

func WithToken(token string) Option {
return func(c *Config) {
c.token = token
}
}

func WithModel(model string) Option {
return func(c *Config) {
c.model = model
}
}
69 changes: 69 additions & 0 deletions pkg/provider/elevenlabs/synthesizer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package elevenlabs

import (
"context"
"net/http"
"net/url"
"strings"

"github.com/adrianliechti/llama/pkg/provider"

"github.com/google/uuid"
)

var _ provider.Synthesizer = (*Synthesizer)(nil)

type Synthesizer struct {
*Config
}

func NewSynthesizer(options ...Option) (*Synthesizer, error) {
cfg := &Config{
url: "https://api.elevenlabs.io",

client: http.DefaultClient,
}

for _, option := range options {
option(cfg)
}

return &Synthesizer{
Config: cfg,
}, nil
}

func (s *Synthesizer) Synthesize(ctx context.Context, content string, options *provider.SynthesizeOptions) (*provider.Synthesis, error) {
if options == nil {
options = new(provider.SynthesizeOptions)
}

u, _ := url.Parse(strings.TrimRight(s.url, "/") + "/v1/text-to-speech/" + s.model)

body := map[string]any{
"text": content,
"model_id": "eleven_multilingual_v2",
}

req, _ := http.NewRequestWithContext(ctx, "POST", u.String(), jsonReader(body))
req.Header.Set("xi-api-key", s.token)

resp, err := s.client.Do(req)

if err != nil {
return nil, err
}

if resp.StatusCode != http.StatusOK {
return nil, convertError(resp)
}

id := uuid.NewString()

return &provider.Synthesis{
ID: id,

Name: id + ".mp3",
Content: resp.Body,
}, nil
}
29 changes: 29 additions & 0 deletions pkg/provider/elevenlabs/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package elevenlabs

import (
"bytes"
"encoding/json"
"errors"
"io"
"net/http"
)

func convertError(resp *http.Response) error {
data, _ := io.ReadAll(resp.Body)

if len(data) == 0 {
return errors.New(http.StatusText(resp.StatusCode))
}

return errors.New(string(data))
}

func jsonReader(v any) io.Reader {
b := new(bytes.Buffer)

enc := json.NewEncoder(b)
enc.SetEscapeHTML(false)

enc.Encode(v)
return b
}

0 comments on commit 30a5422

Please sign in to comment.