Skip to content

Commit 2ec7d13

Browse files
authored
Merge pull request #25 from mutablelogic/v1
Added openai and mistral API calls
2 parents 89fc399 + 20053e7 commit 2ec7d13

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+2118
-948
lines changed

cmd/api/anthropic.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,7 @@ func anthropicParse(flags *Flags, opts ...client.ClientOpt) error {
4040
apiKey := flags.GetString("anthropic-api-key")
4141
if apiKey == "" {
4242
return fmt.Errorf("missing -anthropic-api-key flag")
43-
}
44-
if client, err := anthropic.New(flags.GetString("anthropic-api-key"), opts...); err != nil {
43+
} else if client, err := anthropic.New(apiKey, opts...); err != nil {
4544
return err
4645
} else {
4746
anthropicClient = client

cmd/api/bitwarden.go

+2
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ func bwFolders(_ context.Context, w *tablewriter.Writer, _ []string) error {
139139
if bwPassword != "" {
140140
opts = append(opts, bitwarden.OptPassword(bwPassword))
141141
}
142+
143+
// Retrieve the folders
142144
folders, err := bwClient.Folders(opts...)
143145
if err != nil {
144146
return err

cmd/api/elevenlabs.go

+346
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"errors"
7+
"fmt"
8+
"io"
9+
"regexp"
10+
"strings"
11+
12+
// Packages
13+
tablewriter "github.com/djthorpe/go-tablewriter"
14+
audio "github.com/go-audio/audio"
15+
wav "github.com/go-audio/wav"
16+
client "github.com/mutablelogic/go-client"
17+
elevenlabs "github.com/mutablelogic/go-client/pkg/elevenlabs"
18+
19+
// Namespace imports
20+
. "github.com/djthorpe/go-errors"
21+
)
22+
23+
///////////////////////////////////////////////////////////////////////////////
24+
// GLOBALS
25+
26+
var (
27+
elName = "elevenlabs"
28+
elClient *elevenlabs.Client
29+
elExt = "mp3"
30+
elBitrate = uint64(32) // in kbps
31+
elSamplerate = uint64(44100) // in Hz
32+
elSimilarityBoost = float64(0.0)
33+
elStability = float64(0.0)
34+
elUseSpeakerBoost = false
35+
elWriteSettings = false
36+
reVoiceId = regexp.MustCompile("^[a-z0-9-]{20}$")
37+
)
38+
39+
///////////////////////////////////////////////////////////////////////////////
40+
// LIFECYCLE
41+
42+
func elRegister(flags *Flags) {
43+
// Register flags required
44+
flags.String(elName, "elevenlabs-api-key", "${ELEVENLABS_API_KEY}", "API Key")
45+
flags.Float(elName, "similarity-boost", 0, "Similarity boost")
46+
flags.Float(elName, "stability", 0, "Voice stability")
47+
flags.Bool(elName, "use-speaker-boost", false, "Use speaker boost")
48+
flags.Unsigned(elName, "bitrate", 0, "Bit rate (kbps)")
49+
flags.Unsigned(elName, "samplerate", 0, "Sample rate (kHz)")
50+
51+
// Register command set
52+
flags.Register(Cmd{
53+
Name: elName,
54+
Description: "Elevenlabs API",
55+
Parse: elParse,
56+
Fn: []Fn{
57+
{Name: "models", Call: elModels, Description: "Gets a list of available models"},
58+
{Name: "voices", Call: elVoices, Description: "Return registered voices"},
59+
{Name: "voice", Call: elVoice, Description: "Return one voice", MinArgs: 1, MaxArgs: 1, Syntax: "<voice-id>"},
60+
{Name: "settings", Call: elVoiceSettings, Description: "Return voice settings, or default settings. Set voice settings from -stability, -similarity-boost and -use-speaker-boost flags", MaxArgs: 1, Syntax: "(<voice-id>)"},
61+
{Name: "say", Call: elTextToSpeech, Description: "Text to speech", MinArgs: 2, Syntax: "<voice-id> <text>..."},
62+
},
63+
})
64+
}
65+
66+
func elParse(flags *Flags, opts ...client.ClientOpt) error {
67+
// Set defaults
68+
if typ := flags.GetOutExt(); typ != "" {
69+
elExt = strings.ToLower(flags.GetOutExt())
70+
}
71+
72+
// Create the client
73+
apiKey := flags.GetString("elevenlabs-api-key")
74+
if apiKey == "" {
75+
return fmt.Errorf("missing -elevenlabs-api-key flag")
76+
} else if client, err := elevenlabs.New(apiKey, opts...); err != nil {
77+
return err
78+
} else {
79+
elClient = client
80+
}
81+
82+
// Get the bit rate and sample rate
83+
if bitrate, err := flags.GetValue("bitrate"); err == nil {
84+
if bitrate_, ok := bitrate.(uint64); ok && bitrate_ > 0 {
85+
elBitrate = bitrate_
86+
}
87+
}
88+
if samplerate, err := flags.GetValue("samplerate"); err == nil {
89+
if samplerate_, ok := samplerate.(uint64); ok && samplerate_ > 0 {
90+
elSamplerate = samplerate_
91+
}
92+
}
93+
94+
// Similarity boost
95+
if value, err := flags.GetValue("similarity-boost"); err == nil {
96+
elSimilarityBoost = value.(float64)
97+
elWriteSettings = true
98+
} else if !errors.Is(err, ErrNotFound) {
99+
return err
100+
}
101+
102+
// Stability
103+
if value, err := flags.GetValue("stability"); err == nil {
104+
elStability = value.(float64)
105+
elWriteSettings = true
106+
} else if !errors.Is(err, ErrNotFound) {
107+
return err
108+
}
109+
110+
// Use speaker boost
111+
if value, err := flags.GetValue("use-speaker-boost"); err == nil {
112+
elUseSpeakerBoost = value.(bool)
113+
elWriteSettings = true
114+
} else if !errors.Is(err, ErrNotFound) {
115+
return err
116+
}
117+
118+
// Return success
119+
return nil
120+
}
121+
122+
/////////////////////////////////////////////////////////////////////
123+
// API CALL FUNCTIONS
124+
125+
func elModels(ctx context.Context, w *tablewriter.Writer, args []string) error {
126+
models, err := elClient.Models()
127+
if err != nil {
128+
return err
129+
}
130+
return w.Write(models)
131+
}
132+
133+
func elVoices(ctx context.Context, w *tablewriter.Writer, args []string) error {
134+
voices, err := elClient.Voices()
135+
if err != nil {
136+
return err
137+
}
138+
return w.Write(voices)
139+
}
140+
141+
func elVoice(ctx context.Context, w *tablewriter.Writer, args []string) error {
142+
if voice, err := elVoiceId(args[0]); err != nil {
143+
return err
144+
} else if voice, err := elClient.Voice(voice); err != nil {
145+
return err
146+
} else {
147+
return w.Write(voice)
148+
}
149+
}
150+
151+
func elVoiceSettings(ctx context.Context, w *tablewriter.Writer, args []string) error {
152+
var voice string
153+
if len(args) > 0 {
154+
if v, err := elVoiceId(args[0]); err != nil {
155+
return err
156+
} else {
157+
voice = v
158+
}
159+
}
160+
161+
// Get voice settings
162+
settings, err := elClient.VoiceSettings(voice)
163+
if err != nil {
164+
return err
165+
}
166+
167+
// Modify settings
168+
if elWriteSettings {
169+
// We need a voice in order to write the settings
170+
if voice == "" {
171+
return ErrBadParameter.With("Missing voice-id")
172+
}
173+
174+
// Change parameters
175+
if elStability != 0.0 {
176+
settings.Stability = float32(elStability)
177+
}
178+
if elSimilarityBoost != 0.0 {
179+
settings.SimilarityBoost = float32(elSimilarityBoost)
180+
}
181+
if elUseSpeakerBoost != settings.UseSpeakerBoost {
182+
settings.UseSpeakerBoost = elUseSpeakerBoost
183+
}
184+
185+
// Set voice settings
186+
if err := elClient.SetVoiceSettings(voice, settings); err != nil {
187+
return err
188+
}
189+
}
190+
191+
return w.Write(settings)
192+
}
193+
194+
func elTextToSpeech(ctx context.Context, w *tablewriter.Writer, args []string) error {
195+
// The voice to use
196+
voice, err := elVoiceId(args[0])
197+
if err != nil {
198+
return err
199+
}
200+
201+
// Output format
202+
opts := []elevenlabs.Opt{}
203+
if format := elOutputFormat(); format != nil {
204+
opts = append(opts, format)
205+
} else {
206+
return ErrBadParameter.Withf("invalid output format %q", elExt)
207+
}
208+
209+
// The text to speak
210+
text := strings.Join(args[1:], " ")
211+
212+
// If wav, then wrap in a header
213+
if elExt == "wav" {
214+
// Create the writer
215+
writer := NewAudioWriter(w.Output().(io.WriteSeeker), int(elSamplerate), 1)
216+
defer writer.Close()
217+
218+
// Read the data
219+
if n, err := elClient.TextToSpeech(writer, voice, text, opts...); err != nil {
220+
return err
221+
} else {
222+
elClient.Debugf("elTextToSpeech: generated %v bytes of PCM data", n)
223+
}
224+
} else if _, err := elClient.TextToSpeech(w.Output(), voice, text, opts...); err != nil {
225+
return err
226+
}
227+
228+
// Return success
229+
return nil
230+
}
231+
232+
/////////////////////////////////////////////////////////////////////
233+
// PRIVATE METHODS
234+
235+
func elVoiceId(q string) (string, error) {
236+
if reVoiceId.MatchString(q) {
237+
return q, nil
238+
} else if voices, err := elClient.Voices(); err != nil {
239+
return "", err
240+
} else {
241+
for _, v := range voices {
242+
if strings.EqualFold(v.Name, q) || v.Id == q {
243+
return v.Id, nil
244+
}
245+
}
246+
}
247+
return "", ErrNotFound.Withf("%q", q)
248+
}
249+
250+
func elOutputFormat() elevenlabs.Opt {
251+
switch elExt {
252+
case "mp3":
253+
return elevenlabs.OptFormatMP3(uint(elBitrate), uint(elSamplerate))
254+
case "wav":
255+
return elevenlabs.OptFormatPCM(uint(elSamplerate))
256+
case "ulaw":
257+
return elevenlabs.OptFormatULAW()
258+
}
259+
return nil
260+
}
261+
262+
/////////////////////////////////////////////////////////////////////
263+
// AUDIO WRITER
264+
265+
type wavWriter struct {
266+
enc *wav.Encoder
267+
buf *bytes.Buffer
268+
pcm *audio.IntBuffer
269+
}
270+
271+
func NewAudioWriter(w io.WriteSeeker, sampleRate, channels int) *wavWriter {
272+
this := new(wavWriter)
273+
274+
// Create a WAV encoder
275+
this.enc = wav.NewEncoder(w, sampleRate, 16, channels, 1)
276+
if this.enc == nil {
277+
return nil
278+
}
279+
280+
// Create a buffer for the incoming byte data
281+
this.buf = bytes.NewBuffer(nil)
282+
283+
// Make a PCM buffer with a capacity of 4096 samples
284+
this.pcm = &audio.IntBuffer{
285+
Format: &audio.Format{
286+
SampleRate: this.enc.SampleRate,
287+
NumChannels: this.enc.NumChans,
288+
},
289+
SourceBitDepth: this.enc.BitDepth,
290+
Data: make([]int, 0, 4096),
291+
}
292+
293+
// Return the writer
294+
return this
295+
}
296+
297+
func (a *wavWriter) Write(data []byte) (int, error) {
298+
// Write the data to the buffer
299+
if n, err := a.buf.Write(data); err != nil {
300+
return 0, err
301+
} else if err := a.Flush(); err != nil {
302+
return 0, err
303+
} else {
304+
return n, nil
305+
}
306+
}
307+
308+
func (a *wavWriter) Flush() error {
309+
var n int
310+
var sample [2]byte
311+
312+
// Read data until we have a full PCM buffer
313+
for {
314+
if a.buf.Len() < len(sample) {
315+
break
316+
} else if n, err := a.buf.Read(sample[:]); err != nil {
317+
return err
318+
} else if n != len(sample) {
319+
return ErrInternalAppError.With("short read")
320+
}
321+
322+
// Append the sample data - Little Endian
323+
a.pcm.Data = append(a.pcm.Data, int(int16(sample[0])|int16(sample[1])<<8))
324+
n += 2
325+
}
326+
327+
// Write the PCM data
328+
if n > 0 {
329+
if err := a.enc.Write(a.pcm); err != nil {
330+
return err
331+
}
332+
}
333+
334+
// Reset the PCM data
335+
a.pcm.Data = a.pcm.Data[:0]
336+
337+
// Return success
338+
return nil
339+
}
340+
341+
func (a *wavWriter) Close() error {
342+
if err := a.Flush(); err != nil {
343+
return err
344+
}
345+
return a.enc.Close()
346+
}

0 commit comments

Comments
 (0)