yandex_speech_api 1.1.2
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 +7 -0
- data/Gemfile +16 -0
- data/LICENSE +9 -0
- data/README.md +101 -0
- data/lib/yandex_speech.rb +253 -0
- data/lib/yandex_speech/connection.rb +52 -0
- data/lib/yandex_speech/emotion.rb +39 -0
- data/lib/yandex_speech/format.rb +38 -0
- data/lib/yandex_speech/helpers.rb +52 -0
- data/lib/yandex_speech/key.rb +106 -0
- data/lib/yandex_speech/language.rb +59 -0
- data/lib/yandex_speech/mp3_player.rb +33 -0
- data/lib/yandex_speech/mp3_player/base.rb +72 -0
- data/lib/yandex_speech/mp3_player/linux_mp3_player.rb +41 -0
- data/lib/yandex_speech/mp3_player/mac_mp3_player.rb +22 -0
- data/lib/yandex_speech/project_structure.rb +30 -0
- data/lib/yandex_speech/speaker.rb +103 -0
- data/lib/yandex_speech/speed.rb +60 -0
- data/lib/yandex_speech/voice.rb +45 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/yandex_speech/connection_spec.rb +24 -0
- data/spec/yandex_speech/emotion_spec.rb +34 -0
- data/spec/yandex_speech/format_spec.rb +30 -0
- data/spec/yandex_speech/helpers_spec.rb +20 -0
- data/spec/yandex_speech/key_spec.rb +58 -0
- data/spec/yandex_speech/language_spec.rb +36 -0
- data/spec/yandex_speech/mp3_player/base_spec.rb +25 -0
- data/spec/yandex_speech/mp3_player/linux_mp3_player_spec.rb +39 -0
- data/spec/yandex_speech/mp3_player_spec.rb +23 -0
- data/spec/yandex_speech/speed_spec.rb +33 -0
- data/spec/yandex_speech/voice_spec.rb +28 -0
- data/spec/yandex_speech_spec.rb +112 -0
- metadata +90 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
module YandexSpeechApi
|
|
4
|
+
class Speaker
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
#
|
|
8
|
+
# @param [Proc] callback
|
|
9
|
+
# Used to set object attributes throw {do...end} block.
|
|
10
|
+
#
|
|
11
|
+
# @example Block syntax
|
|
12
|
+
# key = 'Your secret key'
|
|
13
|
+
# message = "one two three. one two three. one two three four."
|
|
14
|
+
#
|
|
15
|
+
# speaker = YandexSpeechApi::Speaker.init do |s|
|
|
16
|
+
# s.key = key
|
|
17
|
+
# s.voice = :jane
|
|
18
|
+
# s.language = :english
|
|
19
|
+
# s.speed = :slow
|
|
20
|
+
# s.emotion = :good
|
|
21
|
+
# end
|
|
22
|
+
# speaker.say message
|
|
23
|
+
#
|
|
24
|
+
# @return [Speaker]
|
|
25
|
+
|
|
26
|
+
def initialize(settings, &callback)
|
|
27
|
+
yield self if block_given?
|
|
28
|
+
|
|
29
|
+
@key ||= Key.new settings[:key] || :unknown
|
|
30
|
+
@voice ||= Voice.new settings[:voice] || :jane
|
|
31
|
+
@speed ||= Speed.new settings[:speed] || :standard
|
|
32
|
+
@emotion ||= Emotion.new settings[:emotion] || :neutral
|
|
33
|
+
@language ||= Language.new settings[:language] || :english
|
|
34
|
+
@format ||= Format.new settings[:format] || :mp3
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
##
|
|
38
|
+
# Prepares and sends request on Yandex Servers.
|
|
39
|
+
#
|
|
40
|
+
# @param [String] text
|
|
41
|
+
# Something that should been said.
|
|
42
|
+
# @param [Hash] params
|
|
43
|
+
# Overrides object settings (only for this request)
|
|
44
|
+
#
|
|
45
|
+
# @return [String]
|
|
46
|
+
|
|
47
|
+
def request(text, params = {})
|
|
48
|
+
tmp_params = generate_params_for_request text, params
|
|
49
|
+
Connection.send tmp_params
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
##
|
|
53
|
+
# Generates params for request.
|
|
54
|
+
#
|
|
55
|
+
# @param [String] text
|
|
56
|
+
#
|
|
57
|
+
# @param [Hash] params ({})
|
|
58
|
+
# @option params [Format] :format (nil).
|
|
59
|
+
# @option params [Language] :language (nil).
|
|
60
|
+
# @option params [Voice] :voice (nil).
|
|
61
|
+
# @option params [Key] :key (nil).
|
|
62
|
+
# @option params [Emotion] :emotion (nil).
|
|
63
|
+
# @option params [Speed] :speed (nil).
|
|
64
|
+
#
|
|
65
|
+
# @exception TextTooBig
|
|
66
|
+
# Raised when param +text+ too big (>2000 symbols)
|
|
67
|
+
#
|
|
68
|
+
# @return [Hash]
|
|
69
|
+
|
|
70
|
+
def generate_params_for_request(text, params = {})
|
|
71
|
+
tmp_text = text.dup.encode(Encoding::UTF_8, invalid: :replace,
|
|
72
|
+
undef: :replace, replace: '')
|
|
73
|
+
raise TextTooBig if tmp_text.length > 2000
|
|
74
|
+
|
|
75
|
+
tmp = {
|
|
76
|
+
text: tmp_text,
|
|
77
|
+
format: params[:format] ? params[:format].type : format.type,
|
|
78
|
+
lang: params[:language] ? params[:language].code : language.code,
|
|
79
|
+
speaker: params[:voice] ? params[:voice].name : voice.name,
|
|
80
|
+
key: params[:key] ? params[:key].get : key.get,
|
|
81
|
+
emotion: params[:emotion] ? params[:emotion].type : emotion.type,
|
|
82
|
+
speed: params[:speed] ? params[:speed].value : speed.value
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return tmp
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def generate_path # no-doc
|
|
89
|
+
dir_path = File.join ENV['HOME'], 'downloads'
|
|
90
|
+
|
|
91
|
+
Dir.mkdir(dir_path) unless Dir.exist?(dir_path)
|
|
92
|
+
filename = "yandex_speech_audio_#{Time.now.strftime "%Y-%m-%d_%H-%M-%S"}"
|
|
93
|
+
|
|
94
|
+
return File.join(dir_path, filename)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
##
|
|
98
|
+
# Raised when user tries to #say too big text.
|
|
99
|
+
|
|
100
|
+
class TextTooBig < YandexSpeechError
|
|
101
|
+
def initialize; super 'Text message length limited by 2000 symbols per request' end; end
|
|
102
|
+
end # class Speaker
|
|
103
|
+
end # module YandexSpeechApi
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
module YandexSpeechApi
|
|
4
|
+
class Speed
|
|
5
|
+
class << self
|
|
6
|
+
|
|
7
|
+
# @example #1
|
|
8
|
+
# list[:slow] # ==> 0.5
|
|
9
|
+
#
|
|
10
|
+
# @example #2
|
|
11
|
+
# list[:wrong_key] # ==> nil
|
|
12
|
+
#
|
|
13
|
+
# @return [Hash]
|
|
14
|
+
|
|
15
|
+
def modes
|
|
16
|
+
{
|
|
17
|
+
:slowest => 0.1, # minimal allowed speed
|
|
18
|
+
:slow => 0.5,
|
|
19
|
+
:standard => 1.0, # default
|
|
20
|
+
:fast => 1.5,
|
|
21
|
+
:fastest => 3.0 # maximal allowed speed
|
|
22
|
+
}
|
|
23
|
+
end
|
|
24
|
+
end # class << self
|
|
25
|
+
|
|
26
|
+
# @return [Float]
|
|
27
|
+
# In range [(0.1)..3.0]
|
|
28
|
+
|
|
29
|
+
attr_reader :value
|
|
30
|
+
|
|
31
|
+
def initialize(speed)
|
|
32
|
+
@value = if speed.is_a? Numeric
|
|
33
|
+
speed.round 2
|
|
34
|
+
else
|
|
35
|
+
Speed.modes[speed.downcase.to_sym]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
raise SpeedModeNotAllowed, speed if @value.nil?
|
|
39
|
+
raise SpeedValueNotInRange, @value unless speed_in_valid_range? @value
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def speed_in_valid_range?(number)
|
|
45
|
+
number.between? 0.1, 3
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
##
|
|
49
|
+
# Raised when speed mode is unknown.
|
|
50
|
+
|
|
51
|
+
class SpeedModeNotAllowed < YandexSpeechError
|
|
52
|
+
def initialize(speed); super "Speed '#{speed}' not allowed for usage. To see list of allowed formats use Emotion#list." end; end
|
|
53
|
+
|
|
54
|
+
##
|
|
55
|
+
# Raised when +speed+ param is not in [(0.1)..3] range.
|
|
56
|
+
|
|
57
|
+
class SpeedValueNotInRange < YandexSpeechError
|
|
58
|
+
def initialize(value); super "Speed value '#{value}' should be from [(0.1)..3] range" end; end
|
|
59
|
+
end # class Speed
|
|
60
|
+
end # module YandexSpeechApi
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
module YandexSpeechApi
|
|
4
|
+
|
|
5
|
+
# @example Valid Usage
|
|
6
|
+
# Voice.new('Oksana') # ==> instance of voice class
|
|
7
|
+
#
|
|
8
|
+
# @example Bad Usage
|
|
9
|
+
# Voice.new(:bill) # ==> VoiceNotAllowed exception
|
|
10
|
+
|
|
11
|
+
class Voice
|
|
12
|
+
|
|
13
|
+
##
|
|
14
|
+
# List of allowed voices
|
|
15
|
+
#
|
|
16
|
+
# @return [Array<String>]
|
|
17
|
+
|
|
18
|
+
def self.list
|
|
19
|
+
%i(jane oksana alyss omazh zahar ermil)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @return [Symbol]
|
|
23
|
+
# possible values: :jane, :oksana, :alyss, :omazh, :zahar, :ermil
|
|
24
|
+
|
|
25
|
+
attr_reader :name
|
|
26
|
+
|
|
27
|
+
def initialize(voice)
|
|
28
|
+
@name = voice.downcase.to_sym
|
|
29
|
+
raise VoiceNotAllowed, voice unless voice_known? @name
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def voice_known?(name)
|
|
35
|
+
Voice.list.include? name
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
##
|
|
39
|
+
# Raised when unknown voice was selected.
|
|
40
|
+
|
|
41
|
+
class VoiceNotAllowed < YandexSpeechError
|
|
42
|
+
def initialize(voice)
|
|
43
|
+
super "Voice '#{voice}' not allowed for usage. To see list of allowed voices use Voice#list" end; end
|
|
44
|
+
end # class Voice
|
|
45
|
+
end # module YandexSpeechApi
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require 'coveralls'
|
|
4
|
+
Coveralls.wear!
|
|
5
|
+
|
|
6
|
+
require 'webmock/rspec'
|
|
7
|
+
|
|
8
|
+
require_relative '../lib/yandex_speech'
|
|
9
|
+
|
|
10
|
+
RSpec.configure do |config|
|
|
11
|
+
WebMock.disable_net_connect! allow_localhost: true
|
|
12
|
+
|
|
13
|
+
config.filter_run_excluding :disabled => true
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
SimpleCov.coverage_dir("spec/coverage")
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
module YandexSpeechApi
|
|
6
|
+
describe Connection do
|
|
7
|
+
context '#send' do
|
|
8
|
+
it 'raises an exception for fallen request' do
|
|
9
|
+
stub_request(:any, /https:\/\/tts.voicetech.yandex.net\/.*/)
|
|
10
|
+
.to_return status: 500, body: 'Body'
|
|
11
|
+
|
|
12
|
+
expect {described_class.send {}}
|
|
13
|
+
.to raise_exception described_class::ConnectionError
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'returns response body for successful request' do
|
|
17
|
+
stub_request(:any, /https:\/\/tts.voicetech.yandex.net\/.*/)
|
|
18
|
+
.to_return status: 200, body: 'Body'
|
|
19
|
+
|
|
20
|
+
expect(described_class.send {}).to be_eql "Body"
|
|
21
|
+
end
|
|
22
|
+
end # context '#send'
|
|
23
|
+
end # describe Connection
|
|
24
|
+
end # module YandexSpeechApi
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
module YandexSpeechApi
|
|
6
|
+
describe Emotion do
|
|
7
|
+
context '#new' do
|
|
8
|
+
it 'raises an exception for unknown emotion' do
|
|
9
|
+
expect { described_class.new :some_random_emotion }
|
|
10
|
+
.to raise_exception described_class::EmotionNotAllowed
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it 'creates object instance when emotion param is symbol' do
|
|
14
|
+
expect(described_class.new(:good))
|
|
15
|
+
.to be_instance_of Emotion
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it 'creates object instance when emotion param is string' do
|
|
19
|
+
expect(described_class.new('GOOD'))
|
|
20
|
+
.to be_instance_of Emotion
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'creates :good emotion without any exception' do
|
|
24
|
+
expect { described_class.new :good }.to_not raise_exception
|
|
25
|
+
end
|
|
26
|
+
end # context #new
|
|
27
|
+
|
|
28
|
+
context '#list' do
|
|
29
|
+
it 'shows list of allowed emotions' do
|
|
30
|
+
expect(described_class.list).to_not be_nil
|
|
31
|
+
end
|
|
32
|
+
end # context #list
|
|
33
|
+
end # describe Emotion
|
|
34
|
+
end # module YandexSpeechApi
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
module YandexSpeechApi
|
|
6
|
+
describe Format do
|
|
7
|
+
context '#new' do
|
|
8
|
+
it 'raises an exception for unknown format type' do
|
|
9
|
+
expect { described_class.new :some_random_format }
|
|
10
|
+
.to raise_exception described_class::FormatNotAllowed
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it 'creates object instance when format param is symbol' do
|
|
14
|
+
expect(described_class.new(:wav))
|
|
15
|
+
.to be_instance_of Format
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it 'creates object instance when format param is string' do
|
|
19
|
+
expect(described_class.new('Wav'))
|
|
20
|
+
.to be_instance_of Format
|
|
21
|
+
end
|
|
22
|
+
end # context #new
|
|
23
|
+
|
|
24
|
+
context '#list' do
|
|
25
|
+
it 'shows list of all possible formats' do
|
|
26
|
+
expect(described_class.list).to_not be_nil
|
|
27
|
+
end
|
|
28
|
+
end # context #list
|
|
29
|
+
end # describe Format
|
|
30
|
+
end # module YandexSpeechApi
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
module YandexSpeechApi
|
|
6
|
+
describe Helpers do
|
|
7
|
+
context '#recognize_operation_system' do
|
|
8
|
+
|
|
9
|
+
it "returns :windows for CONFIG['host_os'] == msys" do
|
|
10
|
+
stub_const 'RbConfig::CONFIG', {'host_os' => 'msys'}
|
|
11
|
+
expect(described_class.recognize_operation_system).to eql(:windows)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "returns :mac_os for CONFIG['host_os'] == darwin" do
|
|
15
|
+
stub_const 'RbConfig::CONFIG', {'host_os' => 'darwin'}
|
|
16
|
+
expect(described_class.recognize_operation_system).to eql(:mac_os)
|
|
17
|
+
end
|
|
18
|
+
end # context '#recognize_operation_system'
|
|
19
|
+
end # describe Helpers
|
|
20
|
+
end # module YandexSpeechApi
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
module YandexSpeechApi
|
|
6
|
+
describe Key do
|
|
7
|
+
after :each do
|
|
8
|
+
described_class.instance_variable_set :@global_key, nil
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
context '#global_key=(other)' do
|
|
12
|
+
it 'raises an exception +other+ param is not present' do
|
|
13
|
+
key = described_class.new :unknown
|
|
14
|
+
expect { described_class.global_key = key }
|
|
15
|
+
.to raise_error Key::InvalidGlobalKey
|
|
16
|
+
end
|
|
17
|
+
end # context '#global_key=(other)'
|
|
18
|
+
|
|
19
|
+
context '#global_key' do
|
|
20
|
+
it 'returns +false+ when global key not set' do
|
|
21
|
+
expect(Key.global_key?).to be_falsey
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'returns +true+ when global key set' do
|
|
25
|
+
described_class.global_key = 'Secret Key'
|
|
26
|
+
expect(Key.global_key?).to be_truthy
|
|
27
|
+
end
|
|
28
|
+
end # context '#global_key'
|
|
29
|
+
|
|
30
|
+
context '#get' do
|
|
31
|
+
it 'raises an exception for not defined (instance or global) key' do
|
|
32
|
+
key = described_class.new :unknown
|
|
33
|
+
expect { key.get }.to raise_error Key::KeyNotDefined
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it 'not raises an exception when instance key defined' do
|
|
37
|
+
key = described_class.new 'xxxxx-xxxxx-xxxxx-xxxxx-xxxxx'
|
|
38
|
+
expect { key.get }.to_not raise_error
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it 'not raises an exception when global key defined' do
|
|
42
|
+
described_class.global_key = 'xxxxx-xxxxx-xxxxx-xxxxx-xxxxx'
|
|
43
|
+
key = described_class.new :unknown
|
|
44
|
+
expect { key.get }.to_not raise_error
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it 'not raises an exception for any key instance when global key defined' do
|
|
48
|
+
described_class.global_key = 'xxxxx-xxxxx-xxxxx-xxxxx-xxxxx'
|
|
49
|
+
|
|
50
|
+
key = described_class.new :unknown
|
|
51
|
+
key2 = described_class.new 'test key'
|
|
52
|
+
|
|
53
|
+
expect { key.get }.to_not raise_error
|
|
54
|
+
expect { key2.get }.to_not raise_error
|
|
55
|
+
end
|
|
56
|
+
end # context #get
|
|
57
|
+
end # describe Key
|
|
58
|
+
end # module YandexSpeechApi
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
module YandexSpeechApi
|
|
6
|
+
describe Language do
|
|
7
|
+
context '#new' do
|
|
8
|
+
it 'raises an exception for unknown language' do
|
|
9
|
+
expect { described_class.new :wommi_lang }
|
|
10
|
+
.to raise_exception described_class::UnknownLanguageError
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it 'creates object instance when +language+ param is symbol' do
|
|
14
|
+
expect{described_class.new(:ukrain)}.to_not raise_exception
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'creates object instance when +language+ param is string' do
|
|
18
|
+
expect{described_class.new('Ukrain')}.to_not raise_exception
|
|
19
|
+
end
|
|
20
|
+
end # context #new
|
|
21
|
+
|
|
22
|
+
context '#allowed_languages' do
|
|
23
|
+
it 'shows list of all possible languages' do
|
|
24
|
+
expect(described_class.allowed_languages).to_not be_nil
|
|
25
|
+
end
|
|
26
|
+
end # context #allowed_languages
|
|
27
|
+
|
|
28
|
+
context '#code' do
|
|
29
|
+
it 'returns valid code for any class instance from #list' do
|
|
30
|
+
sample = described_class.allowed_languages.sample
|
|
31
|
+
|
|
32
|
+
expect{described_class.new(sample)}.to_not raise_exception
|
|
33
|
+
end
|
|
34
|
+
end # context #code
|
|
35
|
+
end # describe Language
|
|
36
|
+
end # module YandexSpeechApi
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
module YandexSpeechApi
|
|
7
|
+
module MP3_Player
|
|
8
|
+
describe Base do
|
|
9
|
+
context '#play' do
|
|
10
|
+
it 'raises an exception when file not found' do
|
|
11
|
+
expect{described_class.new.play('lalalala.')}
|
|
12
|
+
.to raise_exception described_class::FileNotFound
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'raises an exception when file has wrong extension' do
|
|
16
|
+
expect_any_instance_of(described_class)
|
|
17
|
+
.to receive(:validate_file_presence).and_return nil
|
|
18
|
+
|
|
19
|
+
expect{described_class.new.play('lalalala.')}
|
|
20
|
+
.to raise_exception described_class::UnknownExtension
|
|
21
|
+
end
|
|
22
|
+
end # context '#play'
|
|
23
|
+
end # describe Linux_MP3_Player
|
|
24
|
+
end # module MP3_Player
|
|
25
|
+
end # module YandexSpeechApi
|