ispeech 1.0.1
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.
- 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
|