-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
208 additions
and
62 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,68 +1,41 @@ | ||
package main | ||
|
||
import ( | ||
"flag" | ||
"github.com/joho/godotenv" | ||
"github.com/sgerhardt/chatter/internal/client" | ||
"github.com/sgerhardt/chatter/internal/config" | ||
"github.com/sgerhardt/chatter/internal/setup" | ||
"github.com/spf13/cobra" | ||
"log" | ||
"net" | ||
"net/http" | ||
"os" | ||
"time" | ||
) | ||
|
||
func main() { | ||
run() | ||
} | ||
|
||
func readEnvFile() (string, string) { | ||
err := godotenv.Load() | ||
if err != nil { | ||
log.Fatalf("Error loading .env file: %v", err) | ||
func run() { | ||
if err := rootCmd.Execute(); err != nil { | ||
log.Fatal(err) | ||
} | ||
return os.Getenv("XI_API_KEY"), os.Getenv("OUTPUT") | ||
} | ||
|
||
func run() { | ||
app, httpClient := setup() | ||
|
||
client.New(app, httpClient).Run() | ||
var rootCmd = &cobra.Command{ | ||
Use: "app", | ||
Short: "an eleven labs client for text to voice", | ||
Run: func(_ *cobra.Command, _ []string) { | ||
cfg, c, err := setup.New(".env", voiceID, textInput, siteInput) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
client.New(cfg, c).Run() | ||
}, | ||
} | ||
var ( | ||
voiceID string | ||
textInput string | ||
siteInput string | ||
) | ||
|
||
func setup() (config.AppConfig, client.HTTP) { | ||
var app config.AppConfig | ||
key, dir := readEnvFile() | ||
if key == "" { | ||
log.Fatal("API Key not found") | ||
} | ||
app.APIKey = key | ||
app.OutputDir = dir | ||
app.CharacterRequestLimit = 10000 | ||
|
||
textInput := flag.String("t", "", "Text to convert to voice") | ||
siteInput := flag.String("s", "", "Website to read text from") | ||
voiceID := flag.String("v", "", "Voice ID to use") | ||
flag.Parse() | ||
if *voiceID == "" { | ||
log.Fatal("Voice ID is required") | ||
} | ||
app.VoiceID = *voiceID | ||
|
||
app.TextInput = *textInput | ||
app.WebsiteURL = *siteInput | ||
if app.TextInput != "" && app.WebsiteURL != "" { | ||
log.Fatal("Only one of text or site can be provided") | ||
} | ||
|
||
httpClient := &http.Client{ | ||
Timeout: time.Second * 310, | ||
Transport: &http.Transport{ | ||
DialContext: (&net.Dialer{Timeout: time.Second * 3}).DialContext, | ||
TLSHandshakeTimeout: time.Second * 3, | ||
ResponseHeaderTimeout: time.Second * 300, // eleven labs doesn't appear to respond with the header until the request completes | ||
}, | ||
} | ||
|
||
return app, httpClient | ||
func init() { | ||
rootCmd.PersistentFlags().StringVarP(&textInput, "text", "t", "", "Text to convert to voice") | ||
rootCmd.PersistentFlags().StringVarP(&siteInput, "site", "s", "", "Website to read text from") | ||
rootCmd.PersistentFlags().StringVarP(&voiceID, "voice", "v", "", "Voice ID to use") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package setup | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"github.com/joho/godotenv" | ||
"github.com/sgerhardt/chatter/internal/client" | ||
"github.com/sgerhardt/chatter/internal/config" | ||
"net" | ||
"net/http" | ||
"os" | ||
"strings" | ||
"time" | ||
) | ||
|
||
func readEnvFile(filename string) (string, string, error) { | ||
err := godotenv.Load(filename) | ||
if err != nil { | ||
return "", "", fmt.Errorf("error loading .env file: %v", err) | ||
} | ||
return os.Getenv("XI_API_KEY"), os.Getenv("OUTPUT"), nil | ||
} | ||
|
||
func New(filename string, voiceID string, textInput string, siteInput string) (*config.AppConfig, client.HTTP, error) { | ||
if filename == "" || !strings.HasSuffix(filename, ".env") { | ||
return nil, nil, errors.New(".env file not found") | ||
} | ||
key, dir, err := readEnvFile(filename) | ||
if err != nil { | ||
return nil, nil, fmt.Errorf("error reading env file: %w", err) | ||
} | ||
if key == "" { | ||
return nil, nil, fmt.Errorf("API Key not found") | ||
} | ||
|
||
app := &config.AppConfig{} | ||
app.APIKey = key | ||
app.OutputDir = dir | ||
app.CharacterRequestLimit = 10000 | ||
|
||
if voiceID == "" { | ||
return nil, nil, errors.New("voice ID is required") | ||
} | ||
app.VoiceID = voiceID | ||
|
||
if textInput == "" && siteInput == "" { | ||
return nil, nil, errors.New("text or site is required") | ||
} | ||
if textInput != "" && siteInput != "" { | ||
return nil, nil, errors.New("only one of text or site can be provided") | ||
} | ||
app.TextInput = textInput | ||
app.WebsiteURL = siteInput | ||
|
||
httpClient := &http.Client{ | ||
Timeout: time.Second * 310, | ||
Transport: &http.Transport{ | ||
DialContext: (&net.Dialer{Timeout: time.Second * 3}).DialContext, | ||
TLSHandshakeTimeout: time.Second * 3, | ||
ResponseHeaderTimeout: time.Second * 300, // eleven labs doesn't appear to respond with the header until the request completes | ||
}, | ||
} | ||
|
||
return app, httpClient, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package setup | ||
|
||
import ( | ||
"github.com/sgerhardt/chatter/internal/config" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
"os" | ||
"testing" | ||
) | ||
|
||
func TestNew(t *testing.T) { | ||
// nolint:paralleltest | ||
// This test deals with setting os-level env vars, which is not supported in parallel tests | ||
type expected struct { | ||
errString string | ||
cfg *config.AppConfig | ||
} | ||
|
||
tests := []struct { | ||
name string | ||
|
||
envFile string | ||
|
||
expected expected | ||
}{ | ||
{ | ||
name: "errors when .env file not present", | ||
expected: expected{ | ||
errString: ".env file not found", | ||
cfg: &config.AppConfig{}, | ||
}, | ||
envFile: "", | ||
}, | ||
{ | ||
name: ".env file exists and is empty", | ||
expected: expected{ | ||
errString: "API Key not found", | ||
cfg: &config.AppConfig{}, | ||
}, | ||
envFile: ".env", | ||
}, | ||
{ | ||
name: "API key is in .env file and voice ID is populated", | ||
expected: expected{ | ||
errString: "text or site is required", | ||
cfg: &config.AppConfig{APIKey: "123", VoiceID: "testVoiceID"}, | ||
}, | ||
envFile: ".env", | ||
}, | ||
{ | ||
name: "API key is in .env file and voice ID is populated and text input is populated", | ||
expected: expected{ | ||
errString: "", | ||
cfg: &config.AppConfig{APIKey: "123", VoiceID: "testVoiceID", TextInput: "hello world!", CharacterRequestLimit: 10000}, | ||
}, | ||
envFile: ".env", | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
// nolint:paralleltest | ||
// This test deals with setting os-level env vars, which is not supported in parallel tests | ||
|
||
dir := t.TempDir() | ||
envFile := "" | ||
if tt.envFile != "" { | ||
file, err := os.CreateTemp(dir, "*"+tt.envFile) | ||
require.NoError(t, err) | ||
_, err = file.WriteString("XI_API_KEY=" + tt.expected.cfg.APIKey) | ||
t.Setenv("XI_API_KEY", tt.expected.cfg.APIKey) | ||
require.NoError(t, file.Close()) | ||
require.NoError(t, err) | ||
t.Cleanup(func() { | ||
require.NoError(t, os.Remove(file.Name())) | ||
}) | ||
envFile = file.Name() | ||
} | ||
|
||
// Run test | ||
cfg, client, err := New(envFile, tt.expected.cfg.VoiceID, tt.expected.cfg.TextInput, tt.expected.cfg.WebsiteURL) | ||
|
||
// Assert expectations | ||
if tt.expected.errString != "" { | ||
require.Error(t, err) | ||
assert.ErrorContains(t, err, tt.expected.errString) | ||
assert.Nil(t, cfg) | ||
assert.Nil(t, client) | ||
return | ||
} | ||
require.NoError(t, err) | ||
assert.NotNil(t, cfg) | ||
assert.NotNil(t, client) | ||
assert.Equal(t, tt.expected.cfg, cfg) | ||
}) | ||
} | ||
} |