Skip to content

Commit 043e7e4

Browse files
committed
Added files
1 parent ec0b7a0 commit 043e7e4

File tree

10 files changed

+245
-24
lines changed

10 files changed

+245
-24
lines changed

cmd/cli/elevenlabs.go

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,29 @@
11
package main
22

33
import (
4-
"encoding/json"
54
"fmt"
65
"os"
6+
"regexp"
7+
"strings"
78

89
// Packages
910
"github.com/mutablelogic/go-client/pkg/client"
1011
"github.com/mutablelogic/go-client/pkg/elevenlabs"
12+
13+
// Namespace imports
14+
. "github.com/djthorpe/go-errors"
15+
)
16+
17+
/////////////////////////////////////////////////////////////////////
18+
// GLOBALS
19+
20+
var (
21+
reVoiceId = regexp.MustCompile("^[a-zA-Z0-9]{20}$")
1122
)
1223

24+
/////////////////////////////////////////////////////////////////////
25+
// REGISTER FUNCTIONS
26+
1327
func ElevenlabsFlags(flags *Flags) {
1428
flags.String("elevenlabs-api-key", "${ELEVENLABS_API_KEY}", "ElevenLabs API key")
1529
flags.String("elevenlabs-voice", "", "Voice")
@@ -32,17 +46,21 @@ func ElevenlabsRegister(cmd []Client, opts []client.ClientOpt, flags *Flags) ([]
3246
cmd = append(cmd, Client{
3347
ns: "elevenlabs",
3448
cmd: []Command{
35-
{Name: "voices", Description: "Return registered voices", MinArgs: 2, MaxArgs: 2, Fn: ElevenlabsVoices(elevenlabs, flags)},
36-
{Name: "voice", Description: "Return a voice", Syntax: "<voice>", MinArgs: 3, MaxArgs: 3, Fn: ElevenlabsVoice(elevenlabs, flags)},
37-
{Name: "tts", Description: "Text-to-speech", Syntax: "<text>", MinArgs: 3, MaxArgs: 3, Fn: ElevenlabsTextToSpeech(elevenlabs, flags)},
49+
{Name: "voices", Description: "Return registered voices", MinArgs: 2, MaxArgs: 2, Fn: elevenlabsVoices(elevenlabs, flags)},
50+
{Name: "voice", Description: "Return a voice", Syntax: "<voice>", MinArgs: 3, MaxArgs: 3, Fn: elevenlabsVoice(elevenlabs, flags)},
51+
{Name: "preview", Description: "Preview a voice", Syntax: "<voice>", MinArgs: 3, MaxArgs: 3, Fn: elevenlabsVoicePreview(elevenlabs, flags)},
52+
{Name: "say", Description: "Text-to-speech", Syntax: "<voice> <text>", MinArgs: 3, MaxArgs: 3, Fn: elevenlabsTextToSpeech(elevenlabs, flags)},
3853
},
3954
})
4055

4156
// Return success
4257
return cmd, nil
4358
}
4459

45-
func ElevenlabsVoices(client *elevenlabs.Client, flags *Flags) CommandFn {
60+
/////////////////////////////////////////////////////////////////////
61+
// API CALL FUNCTIONS
62+
63+
func elevenlabsVoices(client *elevenlabs.Client, flags *Flags) CommandFn {
4664
return func() error {
4765
if voices, err := client.Voices(); err != nil {
4866
return err
@@ -52,20 +70,36 @@ func ElevenlabsVoices(client *elevenlabs.Client, flags *Flags) CommandFn {
5270
}
5371
}
5472

55-
func ElevenlabsVoice(client *elevenlabs.Client, flags *Flags) CommandFn {
73+
func elevenlabsVoice(client *elevenlabs.Client, flags *Flags) CommandFn {
5674
return func() error {
57-
if voice, err := client.Voice(flags.Arg(2)); err != nil {
75+
voice, err := elevenlabsGetVoiceId(client, flags.Arg(2))
76+
if err != nil {
5877
return err
59-
} else if data, err := json.MarshalIndent(voice, "", " "); err != nil {
78+
} else if voice, err := client.Voice(voice); err != nil {
6079
return err
6180
} else {
62-
fmt.Printf("%s\n", data)
81+
return flags.Write(voice)
6382
}
64-
return nil
6583
}
6684
}
6785

68-
func ElevenlabsTextToSpeech(client *elevenlabs.Client, flags *Flags) CommandFn {
86+
func elevenlabsVoicePreview(client *elevenlabs.Client, flags *Flags) CommandFn {
87+
return func() error {
88+
voice, err := elevenlabsGetVoiceId(client, flags.Arg(2))
89+
if err != nil {
90+
return err
91+
} else if voice, err := client.Voice(voice); err != nil {
92+
return err
93+
} else if voice.PreviewUrl == "" {
94+
return ErrNotFound.Withf("%q", flags.Arg(2))
95+
} else {
96+
fmt.Println(voice.PreviewUrl)
97+
return nil
98+
}
99+
}
100+
}
101+
102+
func elevenlabsTextToSpeech(client *elevenlabs.Client, flags *Flags) CommandFn {
69103
return func() error {
70104
// Determine the voice to use
71105
voice, err := flags.GetString("elevenlabs-voice")
@@ -74,6 +108,10 @@ func ElevenlabsTextToSpeech(client *elevenlabs.Client, flags *Flags) CommandFn {
74108
} else if voice == "" {
75109
return fmt.Errorf("missing argument: -elevenlabs-voice")
76110
}
111+
voice, err = elevenlabsGetVoiceId(client, voice)
112+
if err != nil {
113+
return err
114+
}
77115

78116
data, err := client.TextToSpeech(flags.Arg(2), voice)
79117
if err != nil {
@@ -84,3 +122,22 @@ func ElevenlabsTextToSpeech(client *elevenlabs.Client, flags *Flags) CommandFn {
84122
return nil
85123
}
86124
}
125+
126+
/////////////////////////////////////////////////////////////////////
127+
// PRIVATE METHODS
128+
129+
// return a voice-id given a parameter, which can be a voice-id or name
130+
func elevenlabsGetVoiceId(client *elevenlabs.Client, voice string) (string, error) {
131+
if reVoiceId.MatchString(voice) {
132+
return voice, nil
133+
} else if voices, err := client.Voices(); err != nil {
134+
return "", err
135+
} else {
136+
for _, v := range voices {
137+
if strings.EqualFold(v.Name, voice) || v.Id == voice {
138+
return v.Id, nil
139+
}
140+
}
141+
}
142+
return "", ErrNotFound.Withf("%q", voice)
143+
}

cmd/cli/openai.go

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
package main
22

33
import (
4-
54
// Packages
65
"github.com/mutablelogic/go-client/pkg/client"
76
"github.com/mutablelogic/go-client/pkg/openai"
7+
8+
// Namespace imports
9+
. "github.com/djthorpe/go-errors"
810
)
911

12+
/////////////////////////////////////////////////////////////////////
13+
// REGISTER FUNCTIONS
14+
1015
func OpenAIFlags(flags *Flags) {
1116
flags.String("openai-api-key", "${OPENAI_API_KEY}", "OpenAI API key")
17+
flags.String("openai-model", "", "OpenAI Model")
1218
}
1319

1420
func OpenAIRegister(cmd []Client, opts []client.ClientOpt, flags *Flags) ([]Client, error) {
@@ -28,16 +34,20 @@ func OpenAIRegister(cmd []Client, opts []client.ClientOpt, flags *Flags) ([]Clie
2834
cmd = append(cmd, Client{
2935
ns: "openai",
3036
cmd: []Command{
31-
{Name: "models", Description: "Return registered models", MinArgs: 2, MaxArgs: 2, Fn: OpenAIModels(openai, flags)},
32-
{Name: "model", Description: "Return model information", MinArgs: 3, MaxArgs: 3, Fn: OpenAIModel(openai, flags)},
37+
{Name: "models", Description: "Return registered models", MinArgs: 2, MaxArgs: 2, Fn: openaiModels(openai, flags)},
38+
{Name: "model", Description: "Return model information", Syntax: "<model>", MinArgs: 3, MaxArgs: 3, Fn: openaiModel(openai, flags)},
39+
{Name: "image", Description: "Generate an image", Syntax: "<prompt>", MinArgs: 3, MaxArgs: 3, Fn: openaiImage(openai, flags)},
3340
},
3441
})
3542

3643
// Return success
3744
return cmd, nil
3845
}
3946

40-
func OpenAIModels(client *openai.Client, flags *Flags) CommandFn {
47+
/////////////////////////////////////////////////////////////////////
48+
// API CALLS
49+
50+
func openaiModels(client *openai.Client, flags *Flags) CommandFn {
4151
return func() error {
4252
if models, err := client.Models(); err != nil {
4353
return err
@@ -48,7 +58,7 @@ func OpenAIModels(client *openai.Client, flags *Flags) CommandFn {
4858
}
4959
}
5060

51-
func OpenAIModel(client *openai.Client, flags *Flags) CommandFn {
61+
func openaiModel(client *openai.Client, flags *Flags) CommandFn {
5262
return func() error {
5363
if model, err := client.Model(flags.Arg(2)); err != nil {
5464
return err
@@ -58,3 +68,34 @@ func OpenAIModel(client *openai.Client, flags *Flags) CommandFn {
5868
return nil
5969
}
6070
}
71+
72+
// generate an image
73+
func openaiImage(client *openai.Client, flags *Flags) CommandFn {
74+
return func() error {
75+
// Set options
76+
opts := []openai.ImageOpt{
77+
openai.OptImageModel("dall-e-3"),
78+
}
79+
if model, err := flags.GetString("openai-model"); err != nil && model != "" {
80+
opts = append(opts, openai.OptImageModel(model))
81+
}
82+
83+
// Call API
84+
prompt := flags.Arg(2)
85+
images, err := client.ImageGenerate(prompt, opts...)
86+
if err != nil {
87+
return err
88+
} else if len(images) == 0 {
89+
return ErrInternalAppError.With("No images returned")
90+
}
91+
92+
// Write images out
93+
for _, image := range images {
94+
if _, err := image.Write(client, flags.Output()); err != nil {
95+
return err
96+
}
97+
}
98+
// Return success
99+
return nil
100+
}
101+
}

pkg/client/client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ type RequestOpt func(*http.Request) error
4949
// GLOBALS
5050

5151
const (
52-
DefaultTimeout = time.Second * 10
52+
DefaultTimeout = time.Second * 30
5353
DefaultUserAgent = "github.com/mutablelogic/go-client"
5454
PathSeparator = string(os.PathSeparator)
5555
ContentTypeJson = "application/json"

pkg/elevenlabs/voice.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,13 +153,13 @@ func (voiceAddRequest) Accept() string {
153153

154154
func (v VoiceSettings) Marshal() ([]byte, error) {
155155
data := new(bytes.Buffer)
156-
data.Write([]byte(fmt.Sprintf("similarity_boost=%v", v.SimilarityBoost)))
157-
data.Write([]byte(fmt.Sprintf("stability=%v", v.Stability)))
156+
data.Write([]byte(fmt.Sprintf("similarity_boost=%v\n", v.SimilarityBoost)))
157+
data.Write([]byte(fmt.Sprintf("stability=%v\n", v.Stability)))
158158
if v.Style != 0 {
159-
data.Write([]byte(fmt.Sprintf("style=%v", v.Style)))
159+
data.Write([]byte(fmt.Sprintf("style=%v\n", v.Style)))
160160
}
161161
if v.UseSpeakerBoost {
162-
data.Write([]byte(fmt.Sprintf("use_speaker_boost=%v", v.UseSpeakerBoost)))
162+
data.Write([]byte(fmt.Sprintf("use_speaker_boost=%v\n", v.UseSpeakerBoost)))
163163
}
164164
return data.Bytes(), nil
165165
}

pkg/soundwriter/opts.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package soundwriter
2+
3+
///////////////////////////////////////////////////////////////////////////////
4+
// TYPES
5+
6+
type Opt func(*Reader) error

pkg/soundwriter/soundwriter.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package soundwriter
2+
3+
import (
4+
"errors"
5+
"io"
6+
"mime"
7+
"net/http"
8+
"sync"
9+
10+
// Packages
11+
"github.com/veandco/go-sdl2/sdl"
12+
)
13+
14+
///////////////////////////////////////////////////////////////////////////////
15+
// TYPES
16+
17+
type Reader struct {
18+
r io.Reader
19+
mimetype string
20+
}
21+
22+
///////////////////////////////////////////////////////////////////////////////
23+
// GLOBALS
24+
25+
const (
26+
firstBytes = 512
27+
)
28+
29+
var (
30+
sdlinit sync.Once
31+
)
32+
33+
///////////////////////////////////////////////////////////////////////////////
34+
// CONSTRUCTOR
35+
36+
func NewReader(r io.Reader, opts ...Opt) (*Reader, error) {
37+
self := new(Reader)
38+
self.r = r
39+
40+
// Read first 512 bytes
41+
buf := make([]byte, firstBytes)
42+
lr := io.LimitReader(r, firstBytes)
43+
if _, err := lr.Read(buf); !errors.Is(err, io.EOF) && err != nil {
44+
return nil, err
45+
}
46+
47+
// Determine the mimetype
48+
mimetype := http.DetectContentType(buf)
49+
if mediatype, _, err := mime.ParseMediaType(mimetype); err != nil {
50+
return nil, err
51+
} else {
52+
self.mimetype = mediatype
53+
}
54+
55+
// Apply the options
56+
for _, opt := range opts {
57+
if err := opt(self); err != nil {
58+
return nil, err
59+
}
60+
}
61+
62+
// Initialise SDL
63+
var result error
64+
sdlinit.Do(func() {
65+
if err := sdl.Init(sdl.INIT_AUDIO); err != nil {
66+
result = err
67+
}
68+
})
69+
if result != nil {
70+
return nil, result
71+
}
72+
73+
// Return success
74+
return self, nil
75+
}
76+
77+
///////////////////////////////////////////////////////////////////////////////
78+
// PUBLIC METHODS
79+
80+
func (r *Reader) MimeType() string {
81+
return r.mimetype
82+
}

pkg/soundwriter/soundwriter_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package soundwriter_test
2+
3+
import (
4+
"os"
5+
"testing"
6+
7+
"github.com/mutablelogic/go-client/pkg/soundwriter"
8+
"github.com/stretchr/testify/assert"
9+
// Packages
10+
)
11+
12+
///////////////////////////////////////////////////////////////////////////////
13+
// TEST FILES
14+
15+
const (
16+
testFile1 = "../../test/david.mp3"
17+
)
18+
19+
///////////////////////////////////////////////////////////////////////////////
20+
// TEST CASES
21+
22+
func Test_soundwriter_000(t *testing.T) {
23+
assert := assert.New(t)
24+
f, err := os.Open(testFile1)
25+
assert.NoError(err)
26+
assert.NotNil(f)
27+
defer f.Close()
28+
r, err := soundwriter.NewReader(f)
29+
assert.NoError(err)
30+
assert.NotNil(r)
31+
t.Log(r.MimeType())
32+
}

pkg/writer/text.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ func (self *TextWriter) Formatln(delim rune) string {
5050
f.WriteRune('s')
5151
f.WriteRune(delim)
5252
}
53-
fmt.Println(f.String())
5453
return f.String()
5554
}
5655

@@ -59,9 +58,11 @@ func (self *TextWriter) Sizeln(elems []string) {
5958
for i, elem := range elems {
6059
w, _ := textSize(elem)
6160
if self.meta[i].Width == 0 {
62-
self.meta[i].Width = w
63-
} else if w > 0 && w > -self.meta[i].Width {
6461
self.meta[i].Width = -w
62+
} else if self.meta[i].Width < 0 {
63+
if w > -self.meta[i].Width {
64+
self.meta[i].Width = -w
65+
}
6566
}
6667
}
6768
}

0 commit comments

Comments
 (0)