ispeech 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/Gemfile +3 -0
- data/LICENCE.txt +21 -0
- data/README.md +88 -0
- data/Rakefile +32 -0
- data/init.rb +1 -0
- data/ispeech.gemspec +25 -0
- data/lib/ispeech.rb +31 -0
- data/lib/ispeech/config.rb +27 -0
- data/lib/ispeech/constants.rb +19 -0
- data/lib/ispeech/error.rb +33 -0
- data/lib/ispeech/mock.rb +93 -0
- data/lib/ispeech/response.rb +45 -0
- data/lib/ispeech/voice.rb +121 -0
- data/lib/ispeech/voice_service.rb +65 -0
- data/lib/ispeech/voices/default.rb +42 -0
- data/script/create_voices.rb +143 -0
- data/script/test_voices.rb +87 -0
- data/spec/ispeech/config_spec.rb +29 -0
- data/spec/ispeech/mock_spec.rb +54 -0
- data/spec/ispeech/response_spec.rb +33 -0
- data/spec/ispeech/voice_service_spec.rb +68 -0
- data/spec/ispeech/voice_spec.rb +128 -0
- data/spec/ispeech_spec.rb +24 -0
- data/spec/spec_helper.rb +69 -0
- data/spec/test_data/test_config.yml +1 -0
- data/spec/test_data/test_file.mp3 +0 -0
- metadata +138 -0
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Ispeech
|
4
|
+
class Voice
|
5
|
+
attr_reader :languages, :speaker, :gender, :quality
|
6
|
+
|
7
|
+
def initialize(speaker, gender, *languages)
|
8
|
+
@speaker = speaker
|
9
|
+
@gender = gender
|
10
|
+
@quality = QUALITY_HIGH
|
11
|
+
|
12
|
+
@languages = Set.new
|
13
|
+
languages.each { |lang| @languages.add(lang) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def id
|
17
|
+
"#{speaker.downcase}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def language
|
21
|
+
languages.first
|
22
|
+
end
|
23
|
+
|
24
|
+
def low_quality!
|
25
|
+
@quality = QUALITY_LOW
|
26
|
+
end
|
27
|
+
|
28
|
+
def high_quality!
|
29
|
+
@quality = QUALITY_HIGH
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.override_map(override_voice_map = {})
|
33
|
+
current_map = self.map
|
34
|
+
override_voice_map.each do |language, voices|
|
35
|
+
language_sym = language.to_sym
|
36
|
+
if voices.nil?
|
37
|
+
# Remove voice directive.
|
38
|
+
current_map.delete(language_sym)
|
39
|
+
elsif voices.is_a?(String) || voices.is_a?(Symbol)
|
40
|
+
# Map voice directive.
|
41
|
+
current_map[language_sym] = self.map[voices]
|
42
|
+
elsif voices.is_a?(Hash)
|
43
|
+
# Replace/Override
|
44
|
+
if entry = self.map[language_sym]
|
45
|
+
new_entry = entry.dup
|
46
|
+
else
|
47
|
+
new_entry = Hash.new
|
48
|
+
end
|
49
|
+
|
50
|
+
voices.each do |gender, speakers|
|
51
|
+
if gender.is_a?(Symbol) && speakers.is_a?(Array)
|
52
|
+
new_entry[gender] = speakers
|
53
|
+
end
|
54
|
+
end
|
55
|
+
current_map[language_sym] = new_entry
|
56
|
+
end
|
57
|
+
end
|
58
|
+
@@map = current_map
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.map
|
62
|
+
reset_map unless defined?(@@map)
|
63
|
+
@@map
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.reset_map
|
67
|
+
@@map = Voices::PER_LANGUAGE.dup
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.extract_from_options(options = {})
|
71
|
+
if speaker = options[:speaker]
|
72
|
+
named_voice(speaker)
|
73
|
+
else
|
74
|
+
language = options[:language] || :en
|
75
|
+
gender = options[:gender]
|
76
|
+
speakers_for_language = self.map[language]
|
77
|
+
|
78
|
+
speakers = case gender
|
79
|
+
when GENDER_FEMALE
|
80
|
+
speakers_for_language[GENDER_FEMALE]
|
81
|
+
when GENDER_MALE
|
82
|
+
speakers_for_language[GENDER_MALE]
|
83
|
+
else
|
84
|
+
speakers_for_language[GENDER_FEMALE] +
|
85
|
+
speakers_for_language[GENDER_MALE]
|
86
|
+
end
|
87
|
+
|
88
|
+
if speakers.empty?
|
89
|
+
nil
|
90
|
+
else
|
91
|
+
named_voice(speakers.sample)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.named_voice(speaker)
|
97
|
+
unless defined?(@@voices)
|
98
|
+
@@voices = Hash.new
|
99
|
+
self.map.each do |language, voices|
|
100
|
+
[GENDER_MALE, GENDER_FEMALE].each do |gender|
|
101
|
+
voices[gender].each do |name|
|
102
|
+
downcase_name = name.downcase.to_sym
|
103
|
+
if @@voices[downcase_name].nil?
|
104
|
+
@@voices[downcase_name] = Voice.new(name, gender, language.to_s)
|
105
|
+
else
|
106
|
+
@@voices[downcase_name].languages.add(language.to_s)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
if info = @@voices[speaker.to_s.downcase.to_sym]
|
114
|
+
info
|
115
|
+
else
|
116
|
+
raise Error.new("Voice does not exist.")
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
module Ispeech
|
4
|
+
class VoiceService
|
5
|
+
|
6
|
+
CLIENT_ENVIRONMENT = "RUBY_#{RUBY_VERSION}"
|
7
|
+
|
8
|
+
ERROR_MISSING_CONFIG = Error.new("VoiceService requires configuration.")
|
9
|
+
|
10
|
+
attr_reader :config
|
11
|
+
|
12
|
+
def initialize(config = Ispeech.config)
|
13
|
+
if config.is_a?(Ispeech::Config)
|
14
|
+
@config = config
|
15
|
+
else
|
16
|
+
raise ERROR_MISSING_CONFIG
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def generate_sound(text, options = {})
|
21
|
+
voice = Voice.extract_from_options(options)
|
22
|
+
|
23
|
+
case options[:quality]
|
24
|
+
when :low
|
25
|
+
voice.low_quality!
|
26
|
+
when :high
|
27
|
+
voice.high_quality!
|
28
|
+
end
|
29
|
+
|
30
|
+
generate_with_voice(text, voice)
|
31
|
+
end
|
32
|
+
|
33
|
+
def generate_with_voice(text, voice)
|
34
|
+
params = {
|
35
|
+
# API Defaults:
|
36
|
+
# :bitrate => 48,
|
37
|
+
# :speed => 0,
|
38
|
+
# :startpadding => 0,
|
39
|
+
# :endpadding => 0,
|
40
|
+
# :pitch => 100,
|
41
|
+
# :format => 'mp3',
|
42
|
+
# :bitdepth => 16,
|
43
|
+
# :filename => 'rest'
|
44
|
+
:voice => voice.id,
|
45
|
+
:frequency => voice.quality,
|
46
|
+
:text => text,
|
47
|
+
}
|
48
|
+
|
49
|
+
with_action(:convert, params)
|
50
|
+
end
|
51
|
+
|
52
|
+
def with_action(action, params)
|
53
|
+
params[:action] = action
|
54
|
+
Response.new(post(params))
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def post(params)
|
60
|
+
params[:apikey] = @config.api_key
|
61
|
+
Net::HTTP.post_form(@config.target_url, params)
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Ispeech
|
2
|
+
module Voices
|
3
|
+
PER_LANGUAGE = {
|
4
|
+
:ar => {:female=>[], :male=>["arabicmale"]},
|
5
|
+
:ca => {:female=>["eurcatalanfemale"], :male=>[]},
|
6
|
+
:cs => {:female=>["eurczechfemale"], :male=>[]},
|
7
|
+
:da => {:female=>["eurdanishfemale"], :male=>[]},
|
8
|
+
:de => {:female=>["eurgermanfemale"], :male=>["eurgermanmale"]},
|
9
|
+
:el => {:female=>["eurgreekfemale"], :male=>[]},
|
10
|
+
:en => {:female=>["usenglishfemale", "caenglishfemale", "ukenglishfemale", "auenglishfemale"], :male=>["usenglishmale", "ukenglishmale"]},
|
11
|
+
:en_AU => {:female=>["auenglishfemale"], :male=>[]},
|
12
|
+
:en_CA => {:female=>["caenglishfemale"], :male=>[]},
|
13
|
+
:en_GB => {:female=>[], :male=>["ukenglishmale"]},
|
14
|
+
:en_UK => {:female=>["ukenglishfemale"], :male=>[]},
|
15
|
+
:en_US => {:female=>["usenglishfemale"], :male=>[]},
|
16
|
+
:es => {:female=>["eurspanishfemale", "usspanishfemale"], :male=>["usspanishmale", "eurspanishmale"]},
|
17
|
+
:es_ES => {:female=>["eurspanishfemale"], :male=>["eurspanishmale"]},
|
18
|
+
:es_MX => {:female=>[], :male=>["usspanishmale"]},
|
19
|
+
:fi => {:female=>["eurfinnishfemale"], :male=>[]},
|
20
|
+
:fr => {:female=>["cafrenchfemale", "eurfrenchfemale"], :male=>["cafrenchmale", "eurfrenchmale"]},
|
21
|
+
:fr_CA => {:female=>["cafrenchfemale"], :male=>["cafrenchmale"]},
|
22
|
+
:fr_FR => {:female=>["eurfrenchfemale"], :male=>["eurfrenchmale"]},
|
23
|
+
:hu => {:female=>["huhungarianfemale"], :male=>[]},
|
24
|
+
:it => {:female=>["euritalianfemale"], :male=>["euritalianmale"]},
|
25
|
+
:ja => {:female=>["jpjapanesefemale"], :male=>["jpjapanesemale"]},
|
26
|
+
:ko => {:female=>["krkoreanfemale"], :male=>["krkoreanmale"]},
|
27
|
+
:ko_KR => {:female=>[], :male=>["krkoreanmale"]},
|
28
|
+
:nl => {:female=>["eurdutchfemale"], :male=>[]},
|
29
|
+
:no => {:female=>["eurnorwegianfemale"], :male=>[]},
|
30
|
+
:pl => {:female=>["eurpolishfemale"], :male=>[]},
|
31
|
+
:pt => {:female=>["brportuguesefemale", "eurportuguesefemale"], :male=>["eurportuguesemale"]},
|
32
|
+
:pt_BR => {:female=>["brportuguesefemale"], :male=>[]},
|
33
|
+
:pt_PT => {:female=>["eurportuguesefemale"], :male=>["eurportuguesemale"]},
|
34
|
+
:ru => {:female=>["rurussianfemale"], :male=>["rurussianmale"]},
|
35
|
+
:sv => {:female=>["swswedishfemale"], :male=>[]},
|
36
|
+
:tr => {:female=>["eurturkishfemale"], :male=>["eurturkishmale"]},
|
37
|
+
:zh => {:female=>["hkchinesefemale", "twchinesefemale", "chchinesefemale"], :male=>["chchinesemale"]},
|
38
|
+
:zh_HK => {:female=>["hkchinesefemale", "chchinesefemale"], :male=>[]},
|
39
|
+
:zh_TW => {:female=>["twchinesefemale"], :male=>[]}
|
40
|
+
}
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'set'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'json'
|
5
|
+
require 'ispeech'
|
6
|
+
include Ispeech::Scripts
|
7
|
+
|
8
|
+
DEFAULT_VOICES_RUBY_FILE = File.join('lib', 'ispeech', 'voices', 'default.rb')
|
9
|
+
VOICE_KEY_FIELDS = /voice-([^-\d]*)-?(\d+)-?\d*$/
|
10
|
+
|
11
|
+
def save_uri_to_file_if_missing(filename)
|
12
|
+
if !File.exists?(filename)
|
13
|
+
FileUtils.mkdir_p(LOCAL_TEMP_DIR)
|
14
|
+
response = Ispeech.voice_service.with_action('information', :output => :json)
|
15
|
+
file = response.download_to_tempfile
|
16
|
+
FileUtils.mv(file.path, filename)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def convert_text_to_voices(text)
|
21
|
+
raw_voices = JSON.parse(text)
|
22
|
+
structured_voices = Hash.new { |h,k| h[k] = Hash.new}
|
23
|
+
|
24
|
+
raw_voices.each do |key,value|
|
25
|
+
if match = VOICE_KEY_FIELDS.match(key)
|
26
|
+
if match[1].empty?
|
27
|
+
structured_voices[match[2].to_i][:speaker] = value
|
28
|
+
else
|
29
|
+
structured_voices[match[2].to_i][match[1].to_sym] = value
|
30
|
+
end
|
31
|
+
else
|
32
|
+
if $VERBOSE
|
33
|
+
puts "Failed to parse: #{key}=#{value}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
voices = Array.new
|
39
|
+
structured_voices.each do |number, hash|
|
40
|
+
voices << {'language_locale' => hash[:locale], 'speaker' => hash[:speaker], 'gender' => hash[:gender]}
|
41
|
+
end
|
42
|
+
|
43
|
+
voices
|
44
|
+
end
|
45
|
+
|
46
|
+
def gender_code_to_symbol(gender)
|
47
|
+
case gender.downcase
|
48
|
+
when 'f','female'
|
49
|
+
Ispeech::Voice::GENDER_FEMALE
|
50
|
+
when 'm','male'
|
51
|
+
Ispeech::Voice::GENDER_MALE
|
52
|
+
else
|
53
|
+
raise "Unknown gender code: #{gender}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def qualified_locale(locale)
|
58
|
+
split = locale.split('-') || locale.split('_')
|
59
|
+
|
60
|
+
if split.size > 1
|
61
|
+
"#{split.first}_#{split.last.upcase}"
|
62
|
+
else
|
63
|
+
split.first
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def simple_language_code(locale)
|
68
|
+
locale.split('_').first
|
69
|
+
end
|
70
|
+
|
71
|
+
class Set
|
72
|
+
def inspect
|
73
|
+
self.to_a.inspect
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def voice_array_to_voices_per_language(voices)
|
78
|
+
language_voices = Hash.new { |h,v| h[v] = { :female => Set.new, :male => Set.new } }
|
79
|
+
|
80
|
+
voices.each do |voice_hash|
|
81
|
+
gender = gender_code_to_symbol(voice_hash['gender'])
|
82
|
+
locale = qualified_locale(voice_hash['language_locale'])
|
83
|
+
simple_locale = simple_language_code(locale)
|
84
|
+
|
85
|
+
language_voices[locale][gender].add(voice_hash['speaker'])
|
86
|
+
language_voices[simple_locale][gender].add(voice_hash['speaker'])
|
87
|
+
end
|
88
|
+
|
89
|
+
# Clean up instance where specific locale is same as generic one.
|
90
|
+
language_voices.delete_if do |key,value|
|
91
|
+
simple_locale = simple_language_code(key)
|
92
|
+
if key != simple_locale && language_voices[simple_locale] == value
|
93
|
+
true
|
94
|
+
else
|
95
|
+
false
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
language_voices
|
100
|
+
end
|
101
|
+
|
102
|
+
def construct_class(voices_per_language)
|
103
|
+
text_hashes = voices_per_language.keys.sort.map do |key|
|
104
|
+
"".rjust(6) + ":" + key.to_s.ljust(8) + "=>".ljust(4) + voices_per_language[key].inspect
|
105
|
+
end
|
106
|
+
|
107
|
+
klass_text = <<-CLASS
|
108
|
+
module Ispeech
|
109
|
+
module Voices
|
110
|
+
PER_LANGUAGE = {
|
111
|
+
#{text_hashes.join(",\n")}
|
112
|
+
}
|
113
|
+
end
|
114
|
+
end
|
115
|
+
CLASS
|
116
|
+
klass_text
|
117
|
+
end
|
118
|
+
|
119
|
+
def write_klass(klass)
|
120
|
+
klass_dir = File.dirname(DEFAULT_VOICES_RUBY_FILE)
|
121
|
+
|
122
|
+
if !File.exist?(klass_dir)
|
123
|
+
FileUtils.mkdir_p(klass_dir)
|
124
|
+
end
|
125
|
+
|
126
|
+
File.open(DEFAULT_VOICES_RUBY_FILE, 'w') do |f|
|
127
|
+
f.write(klass)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def main
|
132
|
+
save_uri_to_file_if_missing(VOICES_ENUMERATOR_FILE)
|
133
|
+
voices = convert_text_to_voices(File.read(VOICES_ENUMERATOR_FILE))
|
134
|
+
|
135
|
+
voices_per_language = voice_array_to_voices_per_language(voices)
|
136
|
+
klass = construct_class(voices_per_language)
|
137
|
+
write_klass(klass)
|
138
|
+
puts "Done."
|
139
|
+
end
|
140
|
+
|
141
|
+
if $0 == __FILE__
|
142
|
+
main
|
143
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# Encoding: UTF-8
|
2
|
+
require 'ispeech'
|
3
|
+
include Ispeech::Scripts
|
4
|
+
|
5
|
+
trap("SIGINT") do
|
6
|
+
puts "Stopping..."
|
7
|
+
@interrupted = true
|
8
|
+
end
|
9
|
+
|
10
|
+
TEST_VOICES_DIR = File.join(LOCAL_TEMP_DIR, 'voices')
|
11
|
+
|
12
|
+
SAMPLE_SENTENCES = {
|
13
|
+
:ar => "اسمي {name} وأنا أتكلم يمكن.", # Arabic (Saudi Arabia)
|
14
|
+
:ca => "El meu nom és {name} i que pot parlar.", # Catalan (Spain)
|
15
|
+
:cs => "Mé jméno je {name} a mohu mluvit.", # Czech
|
16
|
+
:da => "Mit navn er {name}, og jeg kan tale.", # Danish
|
17
|
+
:de => "Mein Name ist {name}, und ich kann sprechen.", # German
|
18
|
+
:el => "Το όνομά μου είναι {name}, και μπορώ να μιλήσω.", # Greek
|
19
|
+
:en => "My name is {name} and I can talk.", # English
|
20
|
+
:en_AU => "Hi, I live in Australia and I can speak English", # English (AU)
|
21
|
+
:en_CA => "Hi, I live in Canda and I speak English", # English (CA)
|
22
|
+
:en_GB => "Hi, I live in Great Britain and I speak English.", # English (Great Britain)
|
23
|
+
:en_IN => "Hi, I live in India and I can speak English", # English (India)
|
24
|
+
:en_UK => "Hi, I live in the United Kingdom and I can speak English", # English (UK)
|
25
|
+
:en_US => "Hi, I live in the United States and I speak English", # English (US)
|
26
|
+
:es => "Mi nombre es {name}, y puede hablar.", # Spanish
|
27
|
+
:es_ES => "Mi nombre es {name} y vivo en España.", # Spanish (Spain)
|
28
|
+
:es_MX => "Hola, vivo en Mexicon y puedo hablar español", # Spanish (Mexico)
|
29
|
+
:es_US => "Mi nombre es {name} y yo vivimos en Estados Unidos.", # Spanish (US?)
|
30
|
+
:fi => "Nimeni on {name}, ja voin puhua.", # Finnish
|
31
|
+
:fr => "Mon nom est {name}, et je peux parler.", # French
|
32
|
+
:fr_BE => "Mon nom est {name} et je vis en Belgique.", # French (Belgium)
|
33
|
+
:fr_CA => "Mon nom est {name} et je vis au Canada.", # French (Canada)
|
34
|
+
:fr_FR => "Mon nom est {name} et je vis en France.", # French (French)
|
35
|
+
:gb => "Mitt namn är {name} och jag bor i Gotenburg.", # Gotenburg (Swedish), should be sv_SV_gotenburg ?
|
36
|
+
:hu => "Szia, Magyarországon élek, és én is beszélek magyarul", # Hungarian
|
37
|
+
:it => "Il mio nome è {name}, e posso parlare.", # Italian
|
38
|
+
:ja => "こんにちは、私は日本に住んでいます。日本語話せます。", # Japanese
|
39
|
+
:ko => "안녕하세요, 저는 한국에 살고있는 나는 한국어를 쓸 수 있습니다", # Korean
|
40
|
+
:ko_KR => "안녕하세요, 저는 한국에 살고있는 나는 한국어를 쓸 수 있습니다", # Korean
|
41
|
+
:nl => "Mijn naam is {name}, en ik spreek kan.", # Dutch
|
42
|
+
:nl_BE => "Mijn naam is {name} en ik woon in België.", # Dutch (Belgium)
|
43
|
+
:nl_NL => "Mijn naam is {name} en ik woon in Nederland.", # Dutch (Netherlands)
|
44
|
+
:no => "Mitt navn er {name}, og jeg kan snakke.", # Norwegian
|
45
|
+
:pl => "Nazywam się {name}, i mogę mówić.", # Polish
|
46
|
+
:pt => "Meu nome é {name}, e eu posso falar.", # Portuguese
|
47
|
+
:pt_BR => "Meu nome é {name} e eu vivo no Brasil.", # Portuguese (Brazil)
|
48
|
+
:pt_PT => "Meu nome é {name} e eu vivo em Portugal.", # Portuguese (Portugal)
|
49
|
+
:ru => "Меня зовут {name}, и я могу сказать.", # Russia
|
50
|
+
:sc => "Mitt namn är {name} och jag bor i Scania.", # Scanian (Sweden), should be sv_SE_scania
|
51
|
+
:sv => "Mitt namn är {name}, och jag kan tala.", # Swedish
|
52
|
+
:sv_FI => "Mitt namn är {name} och jag bor i Finland.", # Swedish (Finland)
|
53
|
+
:sv_SE => "Mitt namn är {name} och jag bor i Sverige.", # Swedish (Sweden)
|
54
|
+
:tr => "Benim adım {name} ve konuşamıyorum.", # Turkish
|
55
|
+
:zh => "嗨,我住在中国,我能讲普通话", # Chinese (Mainland)
|
56
|
+
:zh_HK => "您好,我住在香港,我可以讲广东话", # Chinese (Hong Kong)
|
57
|
+
:zh_TW => "您好,我住在台灣,我可以講普通話", # Chinese (Taiwan)
|
58
|
+
}
|
59
|
+
|
60
|
+
service = Ispeech::VoiceService.new(Ispeech.config)
|
61
|
+
FileUtils.mkdir_p(TEST_VOICES_DIR)
|
62
|
+
|
63
|
+
Ispeech::Voices::PER_LANGUAGE.each do |language, voice_gender|
|
64
|
+
voice_gender.each do |gender, speakers|
|
65
|
+
speakers.each do |speaker|
|
66
|
+
begin
|
67
|
+
if text = SAMPLE_SENTENCES[language.to_sym]
|
68
|
+
text = text.gsub('{name}', 'Job')
|
69
|
+
destination_file = File.join(TEST_VOICES_DIR, "#{language}_#{speaker}.mp3")
|
70
|
+
if !File.exists?(destination_file)
|
71
|
+
response = service.generate_sound(text, :speaker => speaker)
|
72
|
+
mp3_file = response.download_to_tempfile
|
73
|
+
FileUtils.cp(mp3_file.path, destination_file)
|
74
|
+
mp3_file.close!
|
75
|
+
puts "Generated: #{text}"
|
76
|
+
end
|
77
|
+
else
|
78
|
+
puts "No text for: #{speaker} in #{language}"
|
79
|
+
end
|
80
|
+
rescue => err
|
81
|
+
puts "FAILED WITH: #{language}, #{gender}, #{speakers.inspect}, #{speaker}\nReason: #{err.message}"
|
82
|
+
end
|
83
|
+
|
84
|
+
exit 0 if @interrupted
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|