voicemaker 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 84b3811f8d8ef6084933654f41b1764c2e1c3eb1d59ac8a6e599d1c18a38bd7a
4
- data.tar.gz: 91491d408b0884424c20771d4a4b9d230a7cd29dc05eb7d4f55caeb2ca8e5440
3
+ metadata.gz: ef73c119c97b71f1114e095357f7bfc04d20e73081752fa5bae226c390911622
4
+ data.tar.gz: 116b47aaa08211a962702f2f2cf1db1134992468a7cb4d296a454f8e23b0f264
5
5
  SHA512:
6
- metadata.gz: 98939ba36fb37cc71987060376422da98e064fab61074f08d8f853d5ce35a1f648db32d10ea7da5c25b9af67c908b4b901fff3b35244dfb96ee9b5d3b795e717
7
- data.tar.gz: 1d4a1894a2f1eddbd30b93bb5c1226664079daaaba8c5f72510a6a5912efb65ad49e1b270a1ee628ce6b4e197285af59f5bcb65da3e70b7b2f2c3c0a4b7b5b84
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
- api_key = '...'
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
- result = api.voices
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 = api.voices ["en_US", "female"]
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
- params = {
62
- "Engine" => "neural",
63
- "VoiceId" => "ai3-Jony",
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
- For convenience, you can call `#download` instead, to mak ethe API call and
78
- download the file:
67
+ or with the full list of available parameters:
79
68
 
80
69
  ```ruby
81
- result = api.download "out.mp3', params
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
- $ voicemaker --help
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
- Voicemaker API
106
+ ```
91
107
 
92
- API Documentation:
93
- https://developer.voicemaker.in/apidocs
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 new CONFIG
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
- Commands:
103
- voices
104
- Get list of voices, optionally in a given language
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
- new
107
- Generate a sample config file
125
+ -c --count
126
+ Add number of voices to the result
108
127
 
109
- generate
110
- Generate audio file. The output filename will be the same as the config
111
- filename, with the proper mp3 or wav extension
128
+ -h --help
129
+ Show this help
112
130
 
113
- batch
114
- Generate multiple audio files from multiple config files
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
- -l --language LANG
118
- Limit results to a specific language
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 output to output YAML file
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
- --version
127
- Show version number
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
- SEARCH
131
- Provide one or more text strings to search for (case insensitive)
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
- CONFIG
134
- Path to config file
263
+ Usage:
264
+ voicemaker project PATH [--debug]
265
+ voicemaker project (-h|--help)
135
266
 
136
- OUTPUT
137
- Path to output mp3/wav file. If not provided, the filename will be the same
138
- as the config file, with wav/mp3 extension
267
+ Options:
268
+ --debug
269
+ Show API parameters
139
270
 
140
- INDIR
141
- Path to input directory, containing config YAML files
271
+ -h --help
272
+ Show this help
142
273
 
143
- OURDIR
144
- Path to output directory, where mp3/wav files will be saved
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 voices en-us
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
- Engine: neural
2
- VoiceId: ai3-Jony
3
- LanguageCode: en-US
4
- OutputFormat: mp3
5
- SampleRate: 48000
6
- Effect: default
7
- MasterSpeed: "0"
8
- MasterVolume: "0"
9
- MasterPitch: "0"
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
@@ -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
- attr_reader :api_key
6
+ ROOT = 'https://developer.voicemaker.in/voice'
7
7
 
8
8
  class << self
9
- attr_writer :base_uri
9
+ attr_writer :root, :key, :cache_life, :cache_dir
10
10
 
11
- def base_uri
12
- @base_uri ||= ENV['VOICEMAKER_API_HOST'] || 'https://developer.voicemaker.in/voice'
11
+ def root
12
+ @root ||= ENV['VOICEMAKER_API_ROOT'] || ROOT
13
13
  end
14
- end
15
14
 
16
- def initialize(api_key)
17
- @api_key = api_key
18
- end
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
- # Returns a list of voices, with optional search criteria (array)
21
- def voices(search = nil)
22
- search = nil if search&.empty?
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
- if search
29
- voices.select do |voice|
30
- search_string = voice.values.join(' ').downcase
31
- search.any? { |query| search_string.include? query.downcase }
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
- # Returns the URL for the generated file
39
- def generate(params = {})
40
- response = HTTP.auth(auth_header).post "#{base_uri}/api", json: params
41
- if response.status.success?
42
- response.parse['path']
43
- else
44
- raise BadResponse, "#{response.status}\n#{response.body}"
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
- # Calls the API and saves the file
49
- def download(outpath, params = {})
50
- path = generate params
51
- yield path if block_given?
52
- Down.download path, destination: outpath
53
- end
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
- def base_uri
58
- self.class.base_uri
59
- end
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
- def auth_header
62
- "Bearer #{api_key}"
63
- end
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
@@ -1,10 +1,25 @@
1
+ require 'lp'
1
2
  require 'mister_bin'
2
- require 'voicemaker/command'
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 handler: Command
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,11 @@
1
+ require "fileutils"
2
+
3
+ class File
4
+ class << self
5
+ def deep_write(file, content)
6
+ dir = File.dirname file
7
+ FileUtils.mkdir_p dir unless Dir.exist? dir
8
+ File.write file, content
9
+ end
10
+ end
11
+ 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
@@ -1,3 +1,3 @@
1
1
  module Voicemaker
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -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
@@ -1,5 +1,9 @@
1
1
  require 'voicemaker/version'
2
2
  require 'voicemaker/exceptions'
3
+ require 'voicemaker/extensions/file'
3
4
  require 'voicemaker/api'
5
+ require 'voicemaker/voices'
6
+ require 'voicemaker/tts_params'
7
+ require 'voicemaker/tts'
4
8
 
5
9
  require 'byebug' if ENV['BYEBUG']
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.1.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-16 00:00:00.000000000 Z
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/command.rb
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
@@ -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