voicemaker 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +201 -62
- data/lib/sample.yml +9 -11
- data/lib/voicemaker/api.rb +60 -45
- data/lib/voicemaker/cli.rb +17 -2
- data/lib/voicemaker/commands/base.rb +23 -0
- data/lib/voicemaker/commands/new_command.rb +48 -0
- data/lib/voicemaker/commands/project_command.rb +72 -0
- data/lib/voicemaker/commands/tts_command.rb +90 -0
- data/lib/voicemaker/commands/voices_command.rb +63 -0
- data/lib/voicemaker/extensions/file.rb +11 -0
- data/lib/voicemaker/tts.rb +24 -0
- data/lib/voicemaker/tts_params.rb +90 -0
- data/lib/voicemaker/version.rb +1 -1
- data/lib/voicemaker/voices.rb +41 -0
- data/lib/voicemaker.rb +4 -0
- metadata +25 -3
- data/lib/voicemaker/command.rb +0 -111
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ef73c119c97b71f1114e095357f7bfc04d20e73081752fa5bae226c390911622
|
4
|
+
data.tar.gz: 116b47aaa08211a962702f2f2cf1db1134992468a7cb4d296a454f8e23b0f264
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ba494699349745471825e4e9af8c92282f5c075b533ae1a9392f6058a9885f617f61e7d3faab48c1a3b3de4f7ea51b29760bf4985d510a3c50d82017d24be93b
|
7
|
+
data.tar.gz: 912165db465f663a2c062206c712bfd35fdc27b5b558fa5a34ee3084618d7565f35bab41cd7e2e02d4c97d9f92808ea2e6def4a923bd3721fc98e6d38944d310
|
data/README.md
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/voicemaker.svg)](https://badge.fury.io/rb/voicemaker)
|
4
4
|
[![Build Status](https://github.com/DannyBen/voicemaker/workflows/Test/badge.svg)](https://github.com/DannyBen/voicemaker/actions?query=workflow%3ATest)
|
5
|
+
[![Maintainability](https://api.codeclimate.com/v1/badges/b1977ee0d60affeba3d4/maintainability)](https://codeclimate.com/github/DannyBen/voicemaker/maintainability)
|
5
6
|
|
6
7
|
---
|
7
8
|
|
@@ -35,8 +36,7 @@ First, require and initialize with your Voicemaker API key:
|
|
35
36
|
|
36
37
|
```ruby
|
37
38
|
require 'voicemaker'
|
38
|
-
|
39
|
-
api = Voicemaker::API.new api_key
|
39
|
+
Voicemaker::API.key = 'your api key'
|
40
40
|
```
|
41
41
|
|
42
42
|
### Voices list
|
@@ -44,13 +44,14 @@ api = Voicemaker::API.new api_key
|
|
44
44
|
Get the full voices list:
|
45
45
|
|
46
46
|
```ruby
|
47
|
-
|
47
|
+
voices = Voicemaker::Voices.new
|
48
|
+
result = voices.all
|
48
49
|
```
|
49
50
|
|
50
51
|
Search the voices list for one or more strings (AND search):
|
51
52
|
|
52
53
|
```ruby
|
53
|
-
result =
|
54
|
+
result = voices.search "en-us", "female"
|
54
55
|
```
|
55
56
|
|
56
57
|
### Audio generation and download
|
@@ -58,90 +59,221 @@ result = api.voices ["en_US", "female"]
|
|
58
59
|
Make an API call and get the URL to the audio file in return:
|
59
60
|
|
60
61
|
```ruby
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
"LanguageCode" => "en-US",
|
65
|
-
"OutputFormat" => "mp3",
|
66
|
-
"SampleRate" => "48000",
|
67
|
-
"Effect" => "default",
|
68
|
-
"MasterSpeed" => "0",
|
69
|
-
"MasterVolume" => "0",
|
70
|
-
"MasterPitch" => "0",
|
71
|
-
"Text" => "Hello world",
|
72
|
-
}
|
73
|
-
|
74
|
-
result = api.generate params
|
62
|
+
tts = Voicemaker::TTS.new voice: 'Jony', text: 'Hello world'
|
63
|
+
url = tts.url
|
64
|
+
tts.save "output.mp3"
|
75
65
|
```
|
76
66
|
|
77
|
-
|
78
|
-
download the file:
|
67
|
+
or with the full list of available parameters:
|
79
68
|
|
80
69
|
```ruby
|
81
|
-
|
70
|
+
params = {
|
71
|
+
voice: "Jony",
|
72
|
+
output_format: "mp3",
|
73
|
+
sample_rate: 48000,
|
74
|
+
effect: "default",
|
75
|
+
master_speed: 0,
|
76
|
+
master_volume: 0,
|
77
|
+
master_pitch: 0,
|
78
|
+
text: "Hello world"
|
79
|
+
}
|
80
|
+
|
81
|
+
tts = Voicemaker::TTS.new params
|
82
|
+
url = tts.url
|
82
83
|
```
|
83
84
|
|
85
|
+
As the `voice` parameter, you may use either the voice ID (`ai3-Jony`) or the
|
86
|
+
voice web name (`Jony`). Just note that some voices have the same webname (for
|
87
|
+
example, Emily), so in these cases it is best to use the full voice ID.
|
88
|
+
|
84
89
|
## Command line interface
|
85
90
|
|
86
91
|
<!-- USAGE -->
|
92
|
+
### `$ voicemaker `
|
93
|
+
|
94
|
+
|
87
95
|
```
|
88
|
-
|
96
|
+
Voicemaker Text-to-Speech
|
97
|
+
|
98
|
+
Commands:
|
99
|
+
voices Get a list of available voices
|
100
|
+
tts Generate audio files from text
|
101
|
+
new Create a new config file or a project directory
|
102
|
+
project Create multiple audio files
|
103
|
+
|
104
|
+
API Documentation: https://developer.voicemaker.in/apidocs
|
89
105
|
|
90
|
-
|
106
|
+
```
|
91
107
|
|
92
|
-
|
93
|
-
|
108
|
+
### `$ voicemaker voices --help`
|
109
|
+
|
110
|
+
|
111
|
+
```
|
112
|
+
Get a list of available voices
|
94
113
|
|
95
114
|
Usage:
|
96
|
-
voicemaker voices [--save PATH] [SEARCH...]
|
97
|
-
voicemaker
|
98
|
-
voicemaker generate CONFIG [OUTPUT]
|
99
|
-
voicemaker batch INDIR OUTDIR
|
100
|
-
voicemaker (-h|--help|--version)
|
115
|
+
voicemaker voices [--save PATH --count --compact] [SEARCH...]
|
116
|
+
voicemaker voices (-h|--help)
|
101
117
|
|
102
|
-
|
103
|
-
|
104
|
-
|
118
|
+
Options:
|
119
|
+
-s --save PATH
|
120
|
+
Save output to output YAML file
|
121
|
+
|
122
|
+
-t --compact
|
123
|
+
Show each voice in a single line
|
105
124
|
|
106
|
-
|
107
|
-
|
125
|
+
-c --count
|
126
|
+
Add number of voices to the result
|
108
127
|
|
109
|
-
|
110
|
-
|
111
|
-
filename, with the proper mp3 or wav extension
|
128
|
+
-h --help
|
129
|
+
Show this help
|
112
130
|
|
113
|
-
|
114
|
-
|
131
|
+
Parameters:
|
132
|
+
SEARCH
|
133
|
+
Provide one or more text strings to search for (case insensitive AND search)
|
134
|
+
|
135
|
+
Environment Variables:
|
136
|
+
VOICEMAKER_API_KEY
|
137
|
+
Your Voicemaker API key [required]
|
138
|
+
|
139
|
+
VOICEMAKER_API_HOST
|
140
|
+
Override the API host URL
|
141
|
+
|
142
|
+
VOICEMAKER_CACHE_DIR
|
143
|
+
API cache diredctory [default: cache]
|
144
|
+
|
145
|
+
VOICEMAKER_CACHE_LIFE
|
146
|
+
API cache life. These formats are supported:
|
147
|
+
off - No cache
|
148
|
+
20s - 20 seconds
|
149
|
+
10m - 10 minutes
|
150
|
+
10h - 10 hours
|
151
|
+
10d - 10 days
|
152
|
+
|
153
|
+
Examples:
|
154
|
+
voicemaker voices en-us
|
155
|
+
voicemaker voices --save out.yml en-us
|
156
|
+
voicemaker voices en-us female
|
157
|
+
voicemaker voices en-us --compact
|
158
|
+
|
159
|
+
```
|
160
|
+
|
161
|
+
### `$ voicemaker tts --help`
|
162
|
+
|
163
|
+
|
164
|
+
```
|
165
|
+
Generate audio files from text
|
166
|
+
|
167
|
+
Usage:
|
168
|
+
voicemaker tts [options]
|
169
|
+
voicemaker tts (-h|--help)
|
115
170
|
|
116
171
|
Options:
|
117
|
-
-
|
118
|
-
|
172
|
+
-v --voice NAME
|
173
|
+
Voice ID or Webname
|
174
|
+
|
175
|
+
-t --text TEXT
|
176
|
+
Text to say
|
177
|
+
|
178
|
+
-f --file PATH
|
179
|
+
Load text from file
|
180
|
+
|
181
|
+
-c --config PATH
|
182
|
+
Use a YAML configuration file
|
119
183
|
|
120
184
|
-s --save PATH
|
121
|
-
Save
|
185
|
+
Save audio file.
|
186
|
+
If not provided, a URL to the audio file will be printed instead.
|
187
|
+
When used with the --config option, omit the file extension, as it will be
|
188
|
+
determined based on the config file.
|
189
|
+
|
190
|
+
-d --debug
|
191
|
+
Show API parameters
|
122
192
|
|
123
193
|
-h --help
|
124
194
|
Show this help
|
125
195
|
|
126
|
-
|
127
|
-
|
196
|
+
Environment Variables:
|
197
|
+
VOICEMAKER_API_KEY
|
198
|
+
Your Voicemaker API key [required]
|
199
|
+
|
200
|
+
VOICEMAKER_API_HOST
|
201
|
+
Override the API host URL
|
202
|
+
|
203
|
+
VOICEMAKER_CACHE_DIR
|
204
|
+
API cache diredctory [default: cache]
|
205
|
+
|
206
|
+
VOICEMAKER_CACHE_LIFE
|
207
|
+
API cache life. These formats are supported:
|
208
|
+
off - No cache
|
209
|
+
20s - 20 seconds
|
210
|
+
10m - 10 minutes
|
211
|
+
10h - 10 hours
|
212
|
+
10d - 10 days
|
213
|
+
|
214
|
+
Examples:
|
215
|
+
voicemaker tts --voice ai3-Jony --text "Hello world" --save out.mp3
|
216
|
+
voicemaker tts -v ai3-Jony --file hello.txt --save out.mp3
|
217
|
+
voicemaker tts --config english-female.yml -f text.txt -s outfile
|
218
|
+
|
219
|
+
```
|
220
|
+
|
221
|
+
### `$ voicemaker new --help`
|
222
|
+
|
223
|
+
|
224
|
+
```
|
225
|
+
Create a new config file or a project directory
|
226
|
+
|
227
|
+
Usage:
|
228
|
+
voicemaker new config PATH
|
229
|
+
voicemaker new project DIR
|
230
|
+
voicemaker new (-h|--help)
|
231
|
+
|
232
|
+
Commands:
|
233
|
+
config
|
234
|
+
Create a config file to be used with the 'voicemaker tts' command
|
235
|
+
|
236
|
+
project
|
237
|
+
Generate a project directory to be used with the 'voicemaker project'
|
238
|
+
command
|
239
|
+
|
240
|
+
Options:
|
241
|
+
-h --help
|
242
|
+
Show this help
|
128
243
|
|
129
244
|
Parameters:
|
130
|
-
|
131
|
-
|
245
|
+
PATH
|
246
|
+
Path to use for creating the config file
|
247
|
+
|
248
|
+
DIR
|
249
|
+
Directory name for creating the project structure
|
250
|
+
|
251
|
+
Examples:
|
252
|
+
voicemaker new config test.yml
|
253
|
+
voicemaker new project sample-project
|
254
|
+
|
255
|
+
```
|
256
|
+
|
257
|
+
### `$ voicemaker project --help`
|
258
|
+
|
259
|
+
|
260
|
+
```
|
261
|
+
Create multiple audio files
|
132
262
|
|
133
|
-
|
134
|
-
|
263
|
+
Usage:
|
264
|
+
voicemaker project PATH [--debug]
|
265
|
+
voicemaker project (-h|--help)
|
135
266
|
|
136
|
-
|
137
|
-
|
138
|
-
|
267
|
+
Options:
|
268
|
+
--debug
|
269
|
+
Show API parameters
|
139
270
|
|
140
|
-
|
141
|
-
|
271
|
+
-h --help
|
272
|
+
Show this help
|
142
273
|
|
143
|
-
|
144
|
-
|
274
|
+
Parameters:
|
275
|
+
PATH
|
276
|
+
Path to the project directory
|
145
277
|
|
146
278
|
Environment Variables:
|
147
279
|
VOICEMAKER_API_KEY
|
@@ -150,15 +282,22 @@ Environment Variables:
|
|
150
282
|
VOICEMAKER_API_HOST
|
151
283
|
Override the API host URL
|
152
284
|
|
285
|
+
VOICEMAKER_CACHE_DIR
|
286
|
+
API cache diredctory [default: cache]
|
287
|
+
|
288
|
+
VOICEMAKER_CACHE_LIFE
|
289
|
+
API cache life. These formats are supported:
|
290
|
+
off - No cache
|
291
|
+
20s - 20 seconds
|
292
|
+
10m - 10 minutes
|
293
|
+
10h - 10 hours
|
294
|
+
10d - 10 days
|
295
|
+
|
153
296
|
Examples:
|
154
|
-
voicemaker
|
155
|
-
voicemaker voices --save out.yml en-us
|
156
|
-
voicemaker voices en-us female
|
157
|
-
voicemaker new test.yml
|
158
|
-
voicemaker generate test.yml out.mp3
|
159
|
-
voicemaker batch configs out
|
297
|
+
voicemaker project sample-project
|
160
298
|
|
161
299
|
```
|
300
|
+
|
162
301
|
<!-- USAGE -->
|
163
302
|
|
164
303
|
|
data/lib/sample.yml
CHANGED
@@ -1,11 +1,9 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
Text: |
|
11
|
-
Hello world
|
1
|
+
voice: Jony
|
2
|
+
|
3
|
+
# All the below are optional
|
4
|
+
output_format: mp3
|
5
|
+
sample_rate: 48000
|
6
|
+
effect: default
|
7
|
+
master_speed: 0
|
8
|
+
master_volume: 0
|
9
|
+
master_pitch: 0
|
data/lib/voicemaker/api.rb
CHANGED
@@ -1,66 +1,81 @@
|
|
1
|
+
require 'lightly'
|
1
2
|
require 'http'
|
2
|
-
require 'down'
|
3
3
|
|
4
4
|
module Voicemaker
|
5
5
|
class API
|
6
|
-
|
6
|
+
ROOT = 'https://developer.voicemaker.in/voice'
|
7
7
|
|
8
8
|
class << self
|
9
|
-
attr_writer :
|
9
|
+
attr_writer :root, :key, :cache_life, :cache_dir
|
10
10
|
|
11
|
-
def
|
12
|
-
@
|
11
|
+
def root
|
12
|
+
@root ||= ENV['VOICEMAKER_API_ROOT'] || ROOT
|
13
13
|
end
|
14
|
-
end
|
15
14
|
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
def key
|
16
|
+
@key ||= ENV['VOICEMAKER_API_KEY'] || raise(MissingAuth, "Please set the 'VOICEMAKER_API_KEY' environment variable")
|
17
|
+
end
|
18
|
+
|
19
|
+
def cache_life
|
20
|
+
@cache_life ||= ENV['VOICEMAKER_CACHE_LIFE'] || '4h'
|
21
|
+
end
|
19
22
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
response = HTTP.auth(auth_header).get "#{base_uri}/list"
|
24
|
-
raise BadResponse, "#{response.status}\n#{response.body}" unless response.status.success?
|
25
|
-
voices = response.parse.dig 'data', 'voices_list'
|
26
|
-
raise BadResponse, "Unexpected response: #{response}" unless voices
|
23
|
+
def cache_dir
|
24
|
+
@cache_dir ||= ENV['VOICEMAKER_CACHE_DIR'] || 'cache'
|
25
|
+
end
|
27
26
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
27
|
+
# Performs HTTP GET and cache it
|
28
|
+
# Returns a parsed body on success
|
29
|
+
# Raises BadResponse on error
|
30
|
+
def get(endpoint, params = {})
|
31
|
+
cache.get "#{root}/#{endpoint}+#{params}" do
|
32
|
+
response = get! endpoint, params
|
33
|
+
response.parse
|
32
34
|
end
|
33
|
-
else
|
34
|
-
voices
|
35
35
|
end
|
36
|
-
end
|
37
36
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
37
|
+
# Performs HTTP POST and cache it
|
38
|
+
# Returns a parsed body on success
|
39
|
+
# Raises BadResponse on error
|
40
|
+
def post(endpoint, params = {})
|
41
|
+
cache.get "#{root}/#{endpoint}+#{params}" do
|
42
|
+
response = post! endpoint, params
|
43
|
+
response.parse
|
44
|
+
end
|
45
45
|
end
|
46
|
-
end
|
47
46
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
47
|
+
def cache
|
48
|
+
@cache ||= begin
|
49
|
+
lightly = Lightly.new life: cache_life, dir: cache_dir
|
50
|
+
lightly.disable if cache_life == 'off'
|
51
|
+
lightly
|
52
|
+
end
|
53
|
+
end
|
54
54
|
|
55
|
-
protected
|
55
|
+
protected
|
56
56
|
|
57
|
-
|
58
|
-
|
59
|
-
|
57
|
+
# Performs HTTP GET
|
58
|
+
# Returns a parsed body on success
|
59
|
+
# Raises BadResponse on error
|
60
|
+
def get!(endpoint, params = {})
|
61
|
+
response = HTTP.auth(auth_header).get "#{root}/#{endpoint}", json: params
|
62
|
+
raise BadResponse, "#{response.status}\n#{response.body}" unless response.status.success?
|
63
|
+
response
|
64
|
+
end
|
60
65
|
|
61
|
-
|
62
|
-
|
63
|
-
|
66
|
+
# Performs HTTP POST
|
67
|
+
# Returns a parsed body on success
|
68
|
+
# Raises BadResponse on error
|
69
|
+
def post!(endpoint, params = {})
|
70
|
+
response = HTTP.auth(auth_header).post "#{root}/#{endpoint}", json: params
|
71
|
+
raise BadResponse, "#{response.status}\n#{response.body}" unless response.status.success?
|
72
|
+
response
|
73
|
+
end
|
74
|
+
|
75
|
+
def auth_header
|
76
|
+
"Bearer #{key}"
|
77
|
+
end
|
64
78
|
|
79
|
+
end
|
65
80
|
end
|
66
|
-
end
|
81
|
+
end
|
data/lib/voicemaker/cli.rb
CHANGED
@@ -1,10 +1,25 @@
|
|
1
|
+
require 'lp'
|
1
2
|
require 'mister_bin'
|
2
|
-
require 'voicemaker/
|
3
|
+
require 'voicemaker/commands/base'
|
4
|
+
require 'voicemaker/commands/voices_command'
|
5
|
+
require 'voicemaker/commands/tts_command'
|
6
|
+
require 'voicemaker/commands/new_command'
|
7
|
+
require 'voicemaker/commands/project_command'
|
3
8
|
|
4
9
|
module Voicemaker
|
5
10
|
class CLI
|
6
11
|
def self.runner
|
7
|
-
MisterBin::Runner.new
|
12
|
+
router = MisterBin::Runner.new version: VERSION,
|
13
|
+
header: "Voicemaker Text-to-Speech",
|
14
|
+
footer: "API Documentation: https://developer.voicemaker.in/apidocs"
|
15
|
+
|
16
|
+
router.route "voices", to: Commands::VoicesCommand
|
17
|
+
router.route "tts", to: Commands::TTSCommand
|
18
|
+
router.route "new", to: Commands::NewCommand
|
19
|
+
router.route "project", to: Commands::ProjectCommand
|
20
|
+
|
21
|
+
router
|
22
|
+
|
8
23
|
end
|
9
24
|
end
|
10
25
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Voicemaker
|
2
|
+
module Commands
|
3
|
+
class Base < MisterBin::Command
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def api_environment
|
7
|
+
environment "VOICEMAKER_API_KEY", "Your Voicemaker API key [required]"
|
8
|
+
environment "VOICEMAKER_API_HOST", "Override the API host URL"
|
9
|
+
environment "VOICEMAKER_CACHE_DIR", "API cache diredctory [default: cache]"
|
10
|
+
environment "VOICEMAKER_CACHE_LIFE", <<~EOF
|
11
|
+
API cache life. These formats are supported:
|
12
|
+
off - No cache
|
13
|
+
20s - 20 seconds
|
14
|
+
10m - 10 minutes
|
15
|
+
10h - 10 hours
|
16
|
+
10d - 10 days
|
17
|
+
EOF
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Voicemaker
|
2
|
+
module Commands
|
3
|
+
class NewCommand < Base
|
4
|
+
help "Create a new config file or a project directory"
|
5
|
+
|
6
|
+
usage "voicemaker new config PATH"
|
7
|
+
usage "voicemaker new project DIR"
|
8
|
+
usage "voicemaker new (-h|--help)"
|
9
|
+
|
10
|
+
command "config", "Create a config file to be used with the 'voicemaker tts' command"
|
11
|
+
command "project", "Generate a project directory to be used with the 'voicemaker project' command"
|
12
|
+
|
13
|
+
param "PATH", "Path to use for creating the config file"
|
14
|
+
param "DIR", "Directory name for creating the project structure"
|
15
|
+
|
16
|
+
example "voicemaker new config test.yml"
|
17
|
+
example "voicemaker new project sample-project"
|
18
|
+
|
19
|
+
def config_command
|
20
|
+
copy_config_template args['PATH']
|
21
|
+
end
|
22
|
+
|
23
|
+
def project_command
|
24
|
+
base = args['DIR']
|
25
|
+
copy_config_template "#{base}/config.yml"
|
26
|
+
Dir.mkdir "#{base}/in" unless Dir.exist? "#{base}/in"
|
27
|
+
Dir.mkdir "#{base}/out" unless Dir.exist? "#{base}/out"
|
28
|
+
File.write "#{base}/in/sample1.txt", "hello"
|
29
|
+
File.write "#{base}/in/sample2.txt", "hello"
|
30
|
+
say "created in and out folders in #{base}"
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
def copy_config_template(path)
|
36
|
+
raise InputError, "File already exists: #{path}" if File.exist? path
|
37
|
+
content = File.read template
|
38
|
+
File.deep_write path, content
|
39
|
+
say "saved #{path}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def template
|
43
|
+
@template ||= File.expand_path "../../sample.yml", __dir__
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Voicemaker
|
2
|
+
module Commands
|
3
|
+
class ProjectCommand < Base
|
4
|
+
help "Create multiple audio files"
|
5
|
+
|
6
|
+
usage "voicemaker project PATH [--debug]"
|
7
|
+
usage "voicemaker project (-h|--help)"
|
8
|
+
|
9
|
+
param "PATH", "Path to the project directory"
|
10
|
+
|
11
|
+
option "--debug", "Show API parameters"
|
12
|
+
|
13
|
+
api_environment
|
14
|
+
|
15
|
+
example "voicemaker project sample-project"
|
16
|
+
|
17
|
+
def run
|
18
|
+
text_files.each do |file|
|
19
|
+
text = File.read file
|
20
|
+
tts.params.text = text
|
21
|
+
audio_file = File.basename(file, '.txt') + ".#{tts.params.output_format}"
|
22
|
+
output_path = "#{outdir}/#{audio_file}"
|
23
|
+
|
24
|
+
say "in: !txtblu!#{file}"
|
25
|
+
say "url: !txtblu!#{tts.url}"
|
26
|
+
say "out: !txtblu!#{output_path}"
|
27
|
+
show_details tts if args['--debug']
|
28
|
+
|
29
|
+
tts.save output_path
|
30
|
+
say "---"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def tts
|
37
|
+
@tts ||= Voicemaker::TTS.new **config
|
38
|
+
end
|
39
|
+
|
40
|
+
def show_details(tts, output: nil)
|
41
|
+
lp tts.params.api_params
|
42
|
+
end
|
43
|
+
|
44
|
+
def text_files
|
45
|
+
@text_files ||= Dir["#{indir}/*.txt"]
|
46
|
+
end
|
47
|
+
|
48
|
+
def config
|
49
|
+
@config ||= begin
|
50
|
+
raise InputError, "Cannot find config file: #{config_path}" unless File.exist? config_path
|
51
|
+
YAML.load_file(config_path).transform_keys &:to_sym
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def config_path
|
56
|
+
"#{project_dir}/config.yml"
|
57
|
+
end
|
58
|
+
|
59
|
+
def project_dir
|
60
|
+
args['PATH']
|
61
|
+
end
|
62
|
+
|
63
|
+
def indir
|
64
|
+
"#{project_dir}/in"
|
65
|
+
end
|
66
|
+
|
67
|
+
def outdir
|
68
|
+
"#{project_dir}/out"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Voicemaker
|
2
|
+
module Commands
|
3
|
+
class TTSCommand < Base
|
4
|
+
help "Generate audio files from text"
|
5
|
+
|
6
|
+
usage "voicemaker tts [options]"
|
7
|
+
usage "voicemaker tts (-h|--help)"
|
8
|
+
|
9
|
+
option "-v --voice NAME", "Voice ID or Webname"
|
10
|
+
option "-t --text TEXT", "Text to say"
|
11
|
+
option "-f --file PATH", "Load text from file"
|
12
|
+
option "-c --config PATH", "Use a YAML configuration file"
|
13
|
+
option "-s --save PATH", <<~EOF
|
14
|
+
Save audio file.
|
15
|
+
If not provided, a URL to the audio file will be printed instead.
|
16
|
+
When used with the --config option, omit the file extension, as it will be determined based on the config file.
|
17
|
+
EOF
|
18
|
+
option "-d --debug", "Show API parameters"
|
19
|
+
|
20
|
+
api_environment
|
21
|
+
|
22
|
+
example %q[voicemaker tts --voice ai3-Jony --text "Hello world" --save out.mp3]
|
23
|
+
example %q[voicemaker tts -v ai3-Jony --file hello.txt --save out.mp3]
|
24
|
+
example %q[voicemaker tts --config english-female.yml -f text.txt -s outfile]
|
25
|
+
|
26
|
+
def run
|
27
|
+
verify_args
|
28
|
+
tts = Voicemaker::TTS.new **tts_params
|
29
|
+
say "url: !txtblu!#{tts.url}"
|
30
|
+
|
31
|
+
if output_file
|
32
|
+
tts.save output_file
|
33
|
+
say "out: !txtblu!#{output_file}"
|
34
|
+
end
|
35
|
+
|
36
|
+
show_details tts if args['--debug']
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def show_details(tts, output: nil)
|
42
|
+
lp tts.params.api_params
|
43
|
+
end
|
44
|
+
|
45
|
+
def verify_args
|
46
|
+
raise InputError, "Please provide either --config or --voice" unless args['--config'] || args['--voice']
|
47
|
+
raise InputError, "Please provide either --text or --file" unless args['--text'] || args['--file']
|
48
|
+
end
|
49
|
+
|
50
|
+
def voice
|
51
|
+
args['--voice']
|
52
|
+
end
|
53
|
+
|
54
|
+
def text
|
55
|
+
args['--text'] || text_from_file
|
56
|
+
end
|
57
|
+
|
58
|
+
def output_file
|
59
|
+
args['--save']
|
60
|
+
end
|
61
|
+
|
62
|
+
def tts_params
|
63
|
+
result = if config
|
64
|
+
YAML.load_file(config).transform_keys &:to_sym
|
65
|
+
else
|
66
|
+
{}
|
67
|
+
end
|
68
|
+
|
69
|
+
result[:voice] = voice if voice
|
70
|
+
result[:text] = text
|
71
|
+
result[:output_format] = File.extname(output_file)[1..] if output_file
|
72
|
+
result
|
73
|
+
end
|
74
|
+
|
75
|
+
def config
|
76
|
+
if args['--config']
|
77
|
+
raise InputError, "Cannot find config file #{args['--config']}" unless File.exist? args['--config']
|
78
|
+
args['--config']
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def text_from_file
|
83
|
+
path = args['--file']
|
84
|
+
raise InputError, "Cannot find text file: #{path}" unless File.exist? path
|
85
|
+
File.read path
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Voicemaker
|
2
|
+
module Commands
|
3
|
+
class VoicesCommand < Base
|
4
|
+
help "Get a list of available voices"
|
5
|
+
|
6
|
+
usage "voicemaker voices [--save PATH --count --verbose] [SEARCH...]"
|
7
|
+
usage "voicemaker voices (-h|--help)"
|
8
|
+
|
9
|
+
option "-s --save PATH", "Save output to output YAML file"
|
10
|
+
option "-v --verbose", "Show the full voices data structure"
|
11
|
+
option "-c --count", "Add number of voices to the result"
|
12
|
+
|
13
|
+
param "SEARCH", "Provide one or more text strings to search for (case insensitive AND search)"
|
14
|
+
|
15
|
+
api_environment
|
16
|
+
|
17
|
+
example "voicemaker voices en-us"
|
18
|
+
example "voicemaker voices --save out.yml en-us"
|
19
|
+
example "voicemaker voices en-us female"
|
20
|
+
example "voicemaker voices en-us --verbose"
|
21
|
+
|
22
|
+
def run
|
23
|
+
result = voices.search *args['SEARCH']
|
24
|
+
result['count'] = result.count if args['--count'] and args['--verbose']
|
25
|
+
send_output result
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def voices
|
31
|
+
@voices ||= Voicemaker::Voices.new
|
32
|
+
end
|
33
|
+
|
34
|
+
def send_output(data)
|
35
|
+
save = args['--save']
|
36
|
+
if save
|
37
|
+
say "saved #{save}"
|
38
|
+
File.write save, args['--verbose'] ? data.to_yaml : compact(data)
|
39
|
+
elsif args['--verbose']
|
40
|
+
lp data
|
41
|
+
else
|
42
|
+
puts compact(data)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def compact(data)
|
47
|
+
result = data.values.map do |v|
|
48
|
+
[
|
49
|
+
v['VoiceId'].ljust(16),
|
50
|
+
# v['VoiceWebname'].ljust(10),
|
51
|
+
v['Language'],
|
52
|
+
v['VoiceGender'].ljust(10),
|
53
|
+
v['Engine']
|
54
|
+
].join "\t"
|
55
|
+
end
|
56
|
+
|
57
|
+
result.push "count: #{data.count}" if args['--count']
|
58
|
+
result.join "\n"
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'down'
|
2
|
+
|
3
|
+
module Voicemaker
|
4
|
+
class TTS
|
5
|
+
attr_reader :params
|
6
|
+
|
7
|
+
def initialize(params = {})
|
8
|
+
@params = TTSParams.new params
|
9
|
+
end
|
10
|
+
|
11
|
+
# Returns the URL for the generated file
|
12
|
+
def url
|
13
|
+
response = API.post "api", params.api_params
|
14
|
+
url = response['path']
|
15
|
+
raise BadResponse, "Received empty URL: #{response}" unless url
|
16
|
+
url
|
17
|
+
end
|
18
|
+
|
19
|
+
# Saves the audio file
|
20
|
+
def save(outpath)
|
21
|
+
Down.download url, destination: outpath
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'down'
|
2
|
+
|
3
|
+
module Voicemaker
|
4
|
+
# Provides normalized access to all parameters needed for the TTS endpoint,
|
5
|
+
# including the auto identification of the Engine and Language based on
|
6
|
+
# voice ID, and including sensible defaults.
|
7
|
+
class TTSParams
|
8
|
+
attr_reader :input_params
|
9
|
+
attr_writer :text, :voice, :output_format, :effect, :sample_rate,
|
10
|
+
:master_speed, :master_volume, :master_pitch
|
11
|
+
|
12
|
+
def initialize(input_params = {})
|
13
|
+
@input_params = input_params
|
14
|
+
end
|
15
|
+
|
16
|
+
def inspect
|
17
|
+
%Q[#<Voicemaker::TTSParams api_params="#{api_params}"]
|
18
|
+
end
|
19
|
+
|
20
|
+
def voice
|
21
|
+
@voice ||= find_voice || raise(InputError, "Missing or invalid parameter: voice")
|
22
|
+
end
|
23
|
+
|
24
|
+
def text
|
25
|
+
@text ||= input_params[:text] || raise(InputError, "Missing parameter: text")
|
26
|
+
end
|
27
|
+
|
28
|
+
def output_format
|
29
|
+
@output_format ||= input_params[:output_format] || 'mp3'
|
30
|
+
end
|
31
|
+
|
32
|
+
def effect
|
33
|
+
@effect ||= input_params[:effect] || 'default'
|
34
|
+
end
|
35
|
+
|
36
|
+
def sample_rate
|
37
|
+
@sample_rate ||= (input_params[:sample_rate] || 48000).to_s
|
38
|
+
end
|
39
|
+
|
40
|
+
def master_speed
|
41
|
+
@master_speed ||= (input_params[:master_speed] || 0).to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
def master_volume
|
45
|
+
@master_volume ||= (input_params[:master_volume] || 0).to_s
|
46
|
+
end
|
47
|
+
|
48
|
+
def master_pitch
|
49
|
+
@master_pitch ||= (input_params[:master_pitch] || 0).to_s
|
50
|
+
end
|
51
|
+
|
52
|
+
def api_params
|
53
|
+
{
|
54
|
+
"Engine" => engine,
|
55
|
+
"VoiceId" => voice,
|
56
|
+
"LanguageCode" => language,
|
57
|
+
"OutputFormat" => output_format,
|
58
|
+
"SampleRate" => sample_rate,
|
59
|
+
"Effect" => effect,
|
60
|
+
"MasterSpeed" => master_speed,
|
61
|
+
"MasterVolume" => master_volume,
|
62
|
+
"MasterPitch" => master_pitch,
|
63
|
+
"Text" => text
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
protected
|
68
|
+
|
69
|
+
def engine
|
70
|
+
voice_params['Engine']
|
71
|
+
end
|
72
|
+
|
73
|
+
def language
|
74
|
+
voice_params['Language']
|
75
|
+
end
|
76
|
+
|
77
|
+
def find_voice
|
78
|
+
return nil unless input_params[:voice]
|
79
|
+
voices.find(input_params[:voice])&.dig 'VoiceId'
|
80
|
+
end
|
81
|
+
|
82
|
+
def voice_params
|
83
|
+
voices[voice] || raise(InputError, "Cannot find definition for voice: #{voice}")
|
84
|
+
end
|
85
|
+
|
86
|
+
def voices
|
87
|
+
@voices ||= Voices.new
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/voicemaker/version.rb
CHANGED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Voicemaker
|
4
|
+
# Provides easy and cached access to the list of available voices.
|
5
|
+
class Voices
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
# Enable some of the hash methods idrectly on the Voices object
|
9
|
+
def_delegators :all, :[], :count, :size, :first, :last, :select, :reject,
|
10
|
+
:map, :keys, :values, :each
|
11
|
+
|
12
|
+
# Returns all voices
|
13
|
+
def all
|
14
|
+
@all ||= begin
|
15
|
+
response = API.get "/list"
|
16
|
+
result = response.dig 'data', 'voices_list'
|
17
|
+
raise BadResponse, "Unexpected response: #{response}" unless result
|
18
|
+
result.map { |voice| [voice['VoiceId'], voice] }
|
19
|
+
.sort_by { |_, v| v['VoiceId'] }.to_h
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns a filtered list of voices, with all queries partially included
|
24
|
+
# in the voice parameter values
|
25
|
+
def search(*queries)
|
26
|
+
queries = nil if queries&.empty?
|
27
|
+
return all unless queries
|
28
|
+
|
29
|
+
all.select do |key, data|
|
30
|
+
haystack = data.values.join(' ').downcase
|
31
|
+
queries.all? { |query| haystack.include? query.downcase }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns a single voice, by Voice ID or Voice Webname
|
36
|
+
def find(id_or_webname)
|
37
|
+
all[id_or_webname] || all.find { |_, a| a['VoiceWebname'] == id_or_webname }&.last
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
data/lib/voicemaker.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: voicemaker
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Danny Ben Shitrit
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-04-
|
11
|
+
date: 2022-04-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mister_bin
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '5.3'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: lightly
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.3'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.3'
|
69
83
|
description: Easy to use API for Voicemaker.in TTS service, with a command line interface
|
70
84
|
email: db@dannyben.com
|
71
85
|
executables:
|
@@ -79,9 +93,17 @@ files:
|
|
79
93
|
- lib/voicemaker.rb
|
80
94
|
- lib/voicemaker/api.rb
|
81
95
|
- lib/voicemaker/cli.rb
|
82
|
-
- lib/voicemaker/
|
96
|
+
- lib/voicemaker/commands/base.rb
|
97
|
+
- lib/voicemaker/commands/new_command.rb
|
98
|
+
- lib/voicemaker/commands/project_command.rb
|
99
|
+
- lib/voicemaker/commands/tts_command.rb
|
100
|
+
- lib/voicemaker/commands/voices_command.rb
|
83
101
|
- lib/voicemaker/exceptions.rb
|
102
|
+
- lib/voicemaker/extensions/file.rb
|
103
|
+
- lib/voicemaker/tts.rb
|
104
|
+
- lib/voicemaker/tts_params.rb
|
84
105
|
- lib/voicemaker/version.rb
|
106
|
+
- lib/voicemaker/voices.rb
|
85
107
|
homepage: https://github.com/DannyBen/voicemaker
|
86
108
|
licenses:
|
87
109
|
- MIT
|
data/lib/voicemaker/command.rb
DELETED
@@ -1,111 +0,0 @@
|
|
1
|
-
require 'lp'
|
2
|
-
require 'mister_bin'
|
3
|
-
|
4
|
-
module Voicemaker
|
5
|
-
class Command < MisterBin::Command
|
6
|
-
help "Voicemaker API\n\n API Documentation:\n https://developer.voicemaker.in/apidocs"
|
7
|
-
version Voicemaker::VERSION
|
8
|
-
|
9
|
-
usage "voicemaker voices [--save PATH] [SEARCH...]"
|
10
|
-
usage "voicemaker new CONFIG"
|
11
|
-
usage "voicemaker generate CONFIG [OUTPUT]"
|
12
|
-
usage "voicemaker batch INDIR OUTDIR"
|
13
|
-
usage "voicemaker (-h|--help|--version)"
|
14
|
-
|
15
|
-
command "voices", "Get list of voices, optionally in a given language"
|
16
|
-
command "new", "Generate a sample config file"
|
17
|
-
command "generate", "Generate audio file. The output filename will be the same as the config filename, with the proper mp3 or wav extension"
|
18
|
-
command "batch", "Generate multiple audio files from multiple config files"
|
19
|
-
|
20
|
-
option "-l --language LANG", "Limit results to a specific language"
|
21
|
-
option "-s --save PATH", "Save output to output YAML file"
|
22
|
-
|
23
|
-
param "SEARCH", "Provide one or more text strings to search for (case insensitive)"
|
24
|
-
param "CONFIG", "Path to config file"
|
25
|
-
param "OUTPUT", "Path to output mp3/wav file. If not provided, the filename will be the same as the config file, with wav/mp3 extension"
|
26
|
-
param "INDIR", "Path to input directory, containing config YAML files"
|
27
|
-
param "OURDIR", "Path to output directory, where mp3/wav files will be saved"
|
28
|
-
|
29
|
-
environment "VOICEMAKER_API_KEY", "Your Voicemaker API key [required]"
|
30
|
-
environment "VOICEMAKER_API_HOST", "Override the API host URL"
|
31
|
-
|
32
|
-
example "voicemaker voices en-us"
|
33
|
-
example "voicemaker voices --save out.yml en-us"
|
34
|
-
example "voicemaker voices en-us female"
|
35
|
-
example "voicemaker new test.yml"
|
36
|
-
example "voicemaker generate test.yml out.mp3"
|
37
|
-
example "voicemaker batch configs out"
|
38
|
-
|
39
|
-
def voices_command
|
40
|
-
send_output api.voices(args['SEARCH'])
|
41
|
-
end
|
42
|
-
|
43
|
-
def new_command
|
44
|
-
template = File.expand_path "../sample.yml", __dir__
|
45
|
-
content = File.read template
|
46
|
-
File.write args['CONFIG'], content
|
47
|
-
say "Saved #{args['CONFIG']}"
|
48
|
-
end
|
49
|
-
|
50
|
-
def generate_command
|
51
|
-
config_path = args['CONFIG']
|
52
|
-
raise InputError, "Cannot find config file #{config_path}" unless File.exist? config_path
|
53
|
-
outpath = args['OUTPUT'] || outpath_from_config(config_path)
|
54
|
-
generate config_path, outpath
|
55
|
-
end
|
56
|
-
|
57
|
-
def batch_command
|
58
|
-
files = Dir["#{args['INDIR']}/*.yml"].sort
|
59
|
-
raise InputError, "No config files in #{args['INDIR']}" if files.empty?
|
60
|
-
|
61
|
-
files.each do |config_path|
|
62
|
-
extension = extension_from_config(config_path)
|
63
|
-
basename = File.basename config_path, '.yml'
|
64
|
-
outpath = "#{args['OUTDIR']}/#{basename}.#{extension}"
|
65
|
-
generate config_path, outpath
|
66
|
-
puts ""
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
private
|
71
|
-
|
72
|
-
def generate(config_path, outpath)
|
73
|
-
params = YAML.load_file config_path
|
74
|
-
|
75
|
-
say "Config : !txtgrn!#{config_path}"
|
76
|
-
api.download outpath, params do |url|
|
77
|
-
say "URL : !txtblu!#{url}"
|
78
|
-
end
|
79
|
-
|
80
|
-
say "Path : !txtblu!#{outpath}"
|
81
|
-
end
|
82
|
-
|
83
|
-
def send_output(data)
|
84
|
-
save = args['--save']
|
85
|
-
if save
|
86
|
-
say "Saved #{save}"
|
87
|
-
File.write save, data.to_yaml
|
88
|
-
else
|
89
|
-
lp data
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
def api
|
94
|
-
@api ||= Voicemaker::API.new(api_key)
|
95
|
-
end
|
96
|
-
|
97
|
-
def api_key
|
98
|
-
ENV['VOICEMAKER_API_KEY'] or raise MissingAuth, "Please set the 'VOICEMAKER_API_KEY' environment variable"
|
99
|
-
end
|
100
|
-
|
101
|
-
def outpath_from_config(config_path)
|
102
|
-
ext = extension_from_config config_path
|
103
|
-
config_path.gsub(/yml$/, ext)
|
104
|
-
end
|
105
|
-
|
106
|
-
def extension_from_config(config_path)
|
107
|
-
YAML.load_file(config_path)['OutputFormat']
|
108
|
-
end
|
109
|
-
|
110
|
-
end
|
111
|
-
end
|