yandex_speech_api 1.1.2 → 1.4.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 +4 -4
- data/Gemfile +5 -0
- data/README.md +42 -88
- data/lib/yandex_speech.rb +61 -146
- data/lib/yandex_speech/connection.rb +21 -39
- data/lib/yandex_speech/project_structure.rb +3 -15
- data/lib/yandex_speech/setters.rb +78 -0
- data/lib/yandex_speech/sounds.rb +38 -0
- data/lib/yandex_speech/speaker.rb +26 -67
- data/spec/yandex_speech/mp3_player/base_spec.rb +1 -2
- data/spec/yandex_speech_spec.rb +21 -38
- metadata +4 -23
- data/lib/yandex_speech/emotion.rb +0 -39
- data/lib/yandex_speech/format.rb +0 -38
- data/lib/yandex_speech/helpers.rb +0 -52
- data/lib/yandex_speech/key.rb +0 -106
- data/lib/yandex_speech/language.rb +0 -59
- data/lib/yandex_speech/mp3_player.rb +0 -33
- data/lib/yandex_speech/speed.rb +0 -60
- data/lib/yandex_speech/voice.rb +0 -45
- data/spec/yandex_speech/emotion_spec.rb +0 -34
- data/spec/yandex_speech/format_spec.rb +0 -30
- data/spec/yandex_speech/key_spec.rb +0 -58
- data/spec/yandex_speech/language_spec.rb +0 -36
- data/spec/yandex_speech/mp3_player_spec.rb +0 -23
- data/spec/yandex_speech/speed_spec.rb +0 -33
- data/spec/yandex_speech/voice_spec.rb +0 -28
|
@@ -2,51 +2,33 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
module YandexSpeechApi
|
|
4
4
|
module Connection # no-doc
|
|
5
|
-
class << self
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
# @return [String]
|
|
23
|
-
# Binary data.
|
|
24
|
-
|
|
25
|
-
def send(**params)
|
|
26
|
-
uri = URI.parse URL
|
|
27
|
-
uri.query = URI.encode_www_form params
|
|
28
|
-
response = Net::HTTP.get_response uri
|
|
29
|
-
|
|
30
|
-
case response
|
|
31
|
-
when Net::HTTPSuccess
|
|
32
|
-
return response.body
|
|
33
|
-
else
|
|
34
|
-
raise ConnectionError.new response.code, response.message
|
|
35
|
-
end
|
|
6
|
+
# @param [Hash] params
|
|
7
|
+
#
|
|
8
|
+
# @exception ConnectionError
|
|
9
|
+
# Raised when responce is not successful.
|
|
10
|
+
#
|
|
11
|
+
# @return [String]
|
|
12
|
+
# Response as binary data.
|
|
13
|
+
|
|
14
|
+
def self.send(**params)
|
|
15
|
+
uri = URI.parse(ENDPOINT_OFFICIAL)
|
|
16
|
+
uri.query = URI.encode_www_form params
|
|
17
|
+
response = Net::HTTP.get_response uri
|
|
18
|
+
|
|
19
|
+
unless response.is_a? Net::HTTPSuccess
|
|
20
|
+
raise ConnectionError.new(response.code, response.message)
|
|
36
21
|
end
|
|
37
22
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
##
|
|
41
|
-
# YandexAPI endpoint.
|
|
23
|
+
return response.body
|
|
24
|
+
end
|
|
42
25
|
|
|
43
|
-
|
|
44
|
-
|
|
26
|
+
ENDPOINT_OFFICIAL = "https://tts.voicetech.yandex.net/generate"
|
|
27
|
+
#ENDPOINT_NON_OFFICIAL = "https://tts.voicetech.yandex.net/tts?&platform=web&application=translate"
|
|
45
28
|
|
|
46
|
-
|
|
47
|
-
# Raised when connection failed.
|
|
29
|
+
private_constant :ENDPOINT_OFFICIAL
|
|
48
30
|
|
|
49
|
-
class ConnectionError <
|
|
31
|
+
class ConnectionError < StandardError
|
|
50
32
|
def initialize(code, message); super "Connection refused by remote server. Error code: '#{code}', Exception message: '#{message}'." end; end
|
|
51
33
|
end # module Connection
|
|
52
34
|
end # module YandexSpeechApi
|
|
@@ -2,29 +2,17 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
module YandexSpeechApi
|
|
4
4
|
# dependencies from core lib
|
|
5
|
-
require 'rbconfig'
|
|
6
5
|
require 'uri'
|
|
7
6
|
require 'net/http'
|
|
8
7
|
|
|
9
|
-
##
|
|
10
|
-
# Core class for all exceptions that can be raised in YandexSpeechApi lib..
|
|
11
|
-
|
|
12
|
-
class YandexSpeechError < StandardError; end
|
|
13
|
-
|
|
14
|
-
# project structure
|
|
15
|
-
|
|
16
8
|
##
|
|
17
9
|
# loads *.rb files in requested order
|
|
18
10
|
|
|
19
|
-
def self.
|
|
11
|
+
def self.req(**params)
|
|
20
12
|
params[:files].each do |f|
|
|
21
13
|
require File.join(__dir__, params[:folder].to_s, f)
|
|
22
14
|
end
|
|
23
|
-
end
|
|
24
|
-
private_class_method :load
|
|
25
|
-
|
|
26
|
-
load folder: 'mp3_player',
|
|
27
|
-
files: %w(base mac_mp3_player linux_mp3_player)
|
|
15
|
+
end; private_class_method :req
|
|
28
16
|
|
|
29
|
-
|
|
17
|
+
req files: %w(setters sounds connection speaker)
|
|
30
18
|
end # module YandexSpeechApi
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
module YandexSpeechApi
|
|
4
|
+
module Setters
|
|
5
|
+
|
|
6
|
+
def voice=(new_voice)
|
|
7
|
+
voice = new_voice.to_s.downcase
|
|
8
|
+
unless allowed_voices.include? voice
|
|
9
|
+
raise ArgumentError, "Unexpected voice: #{new_voice}"
|
|
10
|
+
end
|
|
11
|
+
@voice = voice
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def speed=(new_speed)
|
|
15
|
+
unless allowed_range.cover? new_speed
|
|
16
|
+
raise ArgumentError, "Incorrect speech speed value: #{new_speed}"
|
|
17
|
+
end
|
|
18
|
+
@speed = new_speed.round(2)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def emotion=(new_emotion)
|
|
22
|
+
emotion = new_emotion.to_s.downcase
|
|
23
|
+
unless allowed_emotions.include? emotion
|
|
24
|
+
raise ArgumentError, "Unexpected emotion: #{new_emotion}"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
@emotion = emotion
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def language=(new_language)
|
|
31
|
+
@language = allowed_languages[new_language.to_s.downcase]
|
|
32
|
+
|
|
33
|
+
unless language
|
|
34
|
+
raise ArgumentError, "Unexpected language: #{new_language}"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def format=(new_format)
|
|
39
|
+
format = new_format.to_s.downcase
|
|
40
|
+
unless allowed_formats.include? format
|
|
41
|
+
raise ArgumentError, "Unknown parameter: #{new_format}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
@format = format
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def key=(new_key)
|
|
48
|
+
@key = new_key.to_s if new_key
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def allowed_emotions
|
|
54
|
+
%w(evil good neutral)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def allowed_voices
|
|
58
|
+
%w(jane oksana alyss omazh zahar ermil)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def allowed_languages
|
|
62
|
+
{
|
|
63
|
+
"english" => 'en-US',
|
|
64
|
+
"russian" => 'ru‑RU',
|
|
65
|
+
"turkey" => 'tr-TR',
|
|
66
|
+
"ukrain" => 'uk-UK'
|
|
67
|
+
}
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def allowed_formats
|
|
71
|
+
%w(mp3 wav opus)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def allowed_range
|
|
75
|
+
0.1..3
|
|
76
|
+
end
|
|
77
|
+
end # module Validations
|
|
78
|
+
end # module YandexSpeechApi
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
module YandexSpeechApi
|
|
4
|
+
module Sounds
|
|
5
|
+
|
|
6
|
+
# @param [String] srs
|
|
7
|
+
|
|
8
|
+
def self.play(srs)
|
|
9
|
+
file = Tempfile.new 'yandex_speech_temp_file.mp3'
|
|
10
|
+
file << srs
|
|
11
|
+
file.close
|
|
12
|
+
|
|
13
|
+
case operation_system
|
|
14
|
+
when :linux
|
|
15
|
+
exec "mpg123 -q '#{file.path}'"
|
|
16
|
+
when :mac_os
|
|
17
|
+
exec "afplay '#{file.path}'"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
file.unlink
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @return [Symbol]
|
|
24
|
+
|
|
25
|
+
def self.operation_system
|
|
26
|
+
case RbConfig::CONFIG['host_os']
|
|
27
|
+
when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
|
|
28
|
+
:windows
|
|
29
|
+
when /darwin|mac os/
|
|
30
|
+
:mac_os
|
|
31
|
+
when /linux/
|
|
32
|
+
:linux
|
|
33
|
+
else
|
|
34
|
+
:unknown
|
|
35
|
+
end
|
|
36
|
+
end; private_class_method :operation_system
|
|
37
|
+
end # module Sounds
|
|
38
|
+
end # module YandexSpeechApi
|
|
@@ -4,49 +4,21 @@ module YandexSpeechApi
|
|
|
4
4
|
class Speaker
|
|
5
5
|
private
|
|
6
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
7
|
# @param [String] text
|
|
41
8
|
# Something that should been said.
|
|
42
|
-
# @param [Hash] params
|
|
43
|
-
# Overrides object settings (only for this request)
|
|
44
9
|
#
|
|
45
10
|
# @return [String]
|
|
46
11
|
|
|
47
|
-
def request(text
|
|
48
|
-
|
|
49
|
-
|
|
12
|
+
def request(text)
|
|
13
|
+
params = generate_params text
|
|
14
|
+
|
|
15
|
+
response = if key
|
|
16
|
+
Connection.send params
|
|
17
|
+
else
|
|
18
|
+
raise StandardError, "Request cannot been completed without key."
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
return response
|
|
50
22
|
end
|
|
51
23
|
|
|
52
24
|
##
|
|
@@ -54,50 +26,37 @@ module YandexSpeechApi
|
|
|
54
26
|
#
|
|
55
27
|
# @param [String] text
|
|
56
28
|
#
|
|
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
29
|
# @exception TextTooBig
|
|
66
30
|
# Raised when param +text+ too big (>2000 symbols)
|
|
67
31
|
#
|
|
68
32
|
# @return [Hash]
|
|
69
33
|
|
|
70
|
-
def
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
34
|
+
def generate_params(text)
|
|
35
|
+
txt = text.dup.encode(Encoding::UTF_8, invalid: :replace,
|
|
36
|
+
undef: :replace, replace: '')
|
|
37
|
+
|
|
38
|
+
if txt.length > 2000
|
|
39
|
+
raise TextTooBig, 'Text too big. Only 2000 symbols per request are allowed.'
|
|
40
|
+
end
|
|
74
41
|
|
|
75
42
|
tmp = {
|
|
76
|
-
text:
|
|
77
|
-
format:
|
|
78
|
-
lang:
|
|
79
|
-
speaker:
|
|
80
|
-
key:
|
|
81
|
-
emotion:
|
|
82
|
-
speed:
|
|
43
|
+
text: txt,
|
|
44
|
+
format: format,
|
|
45
|
+
lang: language,
|
|
46
|
+
speaker: voice,
|
|
47
|
+
key: key,
|
|
48
|
+
emotion: emotion,
|
|
49
|
+
speed: speed
|
|
83
50
|
}
|
|
84
51
|
|
|
85
52
|
return tmp
|
|
86
53
|
end
|
|
87
54
|
|
|
88
|
-
def generate_path
|
|
89
|
-
dir_path =
|
|
90
|
-
|
|
91
|
-
Dir.mkdir(dir_path) unless Dir.exist?(dir_path)
|
|
55
|
+
def generate_path
|
|
56
|
+
dir_path = Dir.pwd
|
|
92
57
|
filename = "yandex_speech_audio_#{Time.now.strftime "%Y-%m-%d_%H-%M-%S"}"
|
|
93
58
|
|
|
94
59
|
return File.join(dir_path, filename)
|
|
95
60
|
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
61
|
end # class Speaker
|
|
103
62
|
end # module YandexSpeechApi
|
data/spec/yandex_speech_spec.rb
CHANGED
|
@@ -19,9 +19,10 @@ module YandexSpeechApi
|
|
|
19
19
|
.each(&:unlink)
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
|
|
22
23
|
context "Speaker#save_to_file" do
|
|
23
24
|
it 'saves audio-file' do
|
|
24
|
-
speaker = Speaker.
|
|
25
|
+
speaker = Speaker.new key: "xxxxx-xxxxx-xxxxx-xxxxx"
|
|
25
26
|
|
|
26
27
|
path = speaker.save_to_file("Не будите спящего кота.",
|
|
27
28
|
"spec/tmp/not_today")
|
|
@@ -30,9 +31,9 @@ module YandexSpeechApi
|
|
|
30
31
|
end
|
|
31
32
|
|
|
32
33
|
it 'works fine when global key is set' do
|
|
33
|
-
|
|
34
|
+
described_class.key = "xxxxx-xxxxx-xxxxx-xxxxx"
|
|
34
35
|
|
|
35
|
-
bobby = Speaker.
|
|
36
|
+
bobby = Speaker.new
|
|
36
37
|
path_to_bob_file =
|
|
37
38
|
bobby.save_to_file "Не будите спящего кота.", "spec/tmp/bobby"
|
|
38
39
|
|
|
@@ -43,57 +44,38 @@ module YandexSpeechApi
|
|
|
43
44
|
expect_any_instance_of(Speaker).to receive(:generate_path)
|
|
44
45
|
.and_return(File.join(TEMP_PATH, "bobby.mp3"))
|
|
45
46
|
|
|
46
|
-
bobby = Speaker.
|
|
47
|
+
bobby = Speaker.new(key: "xxxx")
|
|
47
48
|
path_to_bob_file = bobby.save_to_file "Не будите спящего кота."
|
|
48
49
|
|
|
49
50
|
expect(File.exist?(path_to_bob_file)).to be_truthy
|
|
50
51
|
end
|
|
51
52
|
|
|
52
53
|
it 'sets correct params for object when block syntax is used' do
|
|
53
|
-
jane = Speaker.
|
|
54
|
+
jane = Speaker.new do |j|
|
|
54
55
|
j.voice = :jane
|
|
55
56
|
j.language = :english
|
|
56
|
-
j.speed =
|
|
57
|
+
j.speed = 1.2
|
|
57
58
|
j.emotion = :good
|
|
58
59
|
j.format = :opus
|
|
59
60
|
end
|
|
60
61
|
|
|
61
|
-
expect(jane.voice
|
|
62
|
-
expect(jane.language
|
|
63
|
-
expect(jane.speed
|
|
64
|
-
expect(jane.emotion
|
|
65
|
-
expect(jane.format
|
|
62
|
+
expect(jane.voice).to be_eql "jane"
|
|
63
|
+
expect(jane.language).to be_eql 'en-US'
|
|
64
|
+
expect(jane.speed).to be_eql 1.2
|
|
65
|
+
expect(jane.emotion).to be_eql "good"
|
|
66
|
+
expect(jane.format).to be_eql "opus"
|
|
66
67
|
end
|
|
67
68
|
|
|
68
69
|
it 'sets correct params for object when hash syntax is used' do
|
|
69
|
-
jane = Speaker.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
expect(jane.voice.name).to be_eql :jane
|
|
73
|
-
expect(jane.language.code).to be_eql 'en-US'
|
|
74
|
-
expect(jane.speed.value).to be_eql 0.5
|
|
75
|
-
expect(jane.emotion.type).to be_eql :good
|
|
76
|
-
end
|
|
77
|
-
end # context "Speaker#save_to_file"
|
|
78
|
-
|
|
79
|
-
# ----------------------------------------------------
|
|
80
|
-
|
|
81
|
-
context "Speaker#say" do
|
|
82
|
-
it 'calls mp3 player' do
|
|
83
|
-
expect_any_instance_of(MP3_Player::Base).to receive(:play)
|
|
84
|
-
|
|
85
|
-
speaker = Speaker.init key: "xxxxx-xxxxx-xxxxx-xxxxx"
|
|
86
|
-
speaker.say("Не будите спящего кота.")
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
it 'raises an exception when text too long' do
|
|
90
|
-
speaker = Speaker.init key: "xxxxx-xxxxx-xxxxx-xxxxx"
|
|
91
|
-
phrase = 'some phrase' * 5000
|
|
70
|
+
jane = Speaker.new(voice: :jane, language: :english,
|
|
71
|
+
speed: 1.2, emotion: :good)
|
|
92
72
|
|
|
93
|
-
expect
|
|
73
|
+
expect(jane.voice).to be_eql "jane"
|
|
74
|
+
expect(jane.language).to be_eql 'en-US'
|
|
75
|
+
expect(jane.speed).to be_eql 1.2
|
|
76
|
+
expect(jane.emotion).to be_eql "good"
|
|
94
77
|
end
|
|
95
78
|
end # context "Speaker#save_to_file"
|
|
96
|
-
end # context "Going to be tested with successful Net:HTTP requests"
|
|
97
79
|
|
|
98
80
|
# ----------------------------------------------------
|
|
99
81
|
|
|
@@ -102,11 +84,12 @@ module YandexSpeechApi
|
|
|
102
84
|
stub_request(:get, /https:\/\/tts.voicetech.yandex.net\/.*/)
|
|
103
85
|
.to_return status: 400, body: "Unreachable body"
|
|
104
86
|
|
|
105
|
-
bobby = Speaker.
|
|
87
|
+
bobby = Speaker.new key: "xxxxx-xxxxx-xxxxx-xxxxx"
|
|
106
88
|
|
|
107
89
|
expect{bobby.say "313"}
|
|
108
90
|
.to raise_exception Connection::ConnectionError
|
|
109
91
|
end
|
|
110
|
-
|
|
92
|
+
end # context "Going to be tested with failed Net:HTTP requests"
|
|
93
|
+
end # context
|
|
111
94
|
end # describe YandexSpeechApi
|
|
112
95
|
end # module YandexSpeechApi
|