mac-say 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cba1ee99ff7b0b0b58234cb6875ca13596166495
4
- data.tar.gz: 0e8919d872e49f0a77cfe526274b7a38dda1a632
3
+ metadata.gz: 691366a15525f635d6c95a206f467a75a3692f3c
4
+ data.tar.gz: 19b525a7de0607f5a08759a5212c90622d066e7b
5
5
  SHA512:
6
- metadata.gz: 68f049d14c698bcb76487c953ab7232ee4f8470984bfc24c8e3c2bbac54adabb15827c42f3e89b8c7a82b91c51cb2e2490ad47bc85dabbb35feb402ff387bd5a
7
- data.tar.gz: 619652619a452f1decac984f957482642cd257ef0a29b84b6a2c85bb6e8bce30ee3697bb0fd31ffd5325ded22259ae266f6d02192fcad4d3394c25f7f24e3134
6
+ metadata.gz: 299481a3cf645e1c24759a9e254e3a902c90bce1e5e3a9fe4eb3c4303b178716942de5cf5906885e64d537bcb05ac068c6d489eeb4652dfbae9a16876c340c8c
7
+ data.tar.gz: 6582d02d8da76153bc33352b62fab27a3d23820a056decdafa18a29a572e2b8ac8793a97d758166fbd1cbd83180254b8ac750c1d252feb5d617dc216efaeaf0d
@@ -1,4 +1,21 @@
1
- ### 0.1.0 / 2017-01-22
1
+ v0.2.0 / 2017-02-16
2
+ ===================
2
3
 
3
- * Initial release:
4
+ * Added useful voices meta-information: gender, quality, "is it a joke?"-flag (WARNING: all the attributes values provided are highly subjective!)
5
+ * Implemented searching for voices by more than one attribute (i.e. @voices.find_all &block delegation)
6
+ * Improved/Fixed documentation
4
7
 
8
+ v.0.1.1 / 2017-01-30
9
+ ====================
10
+
11
+ * Fixed leaked `nil` output from the main lib file
12
+ * Test code with colourful language deleted from the main lib file 🤦🏻‍♂️
13
+
14
+ v0.1.0 / 2017-01-30
15
+ ===================
16
+
17
+ * First version
18
+ * Basic TTS (Strings/Multiline strings/Files)
19
+ * Dynamic voices list parsing (based on the real `say` output)
20
+ * Voices search (by one of the attributes: name / language / country)
21
+ * Full test/docs coverage + CI configuration + fake `say` command for CI
data/README.md CHANGED
@@ -19,7 +19,7 @@
19
19
  * [x] Multiline strings support
20
20
  * [x] Dynamic voices parsing (based on real `say` output)
21
21
  * [x] Voices list generation (including samples and ISO information)
22
- * [x] Voices search (by name / language / country)
22
+ * [x] Voices search (by name / language / country / etc.)
23
23
  * [x] Simple (class-level) and customisable (instance-level) usage
24
24
  * [ ] Observe reading progress line by line❓
25
25
  * [ ] Audio output support❓
@@ -41,11 +41,19 @@ require 'mac/say'
41
41
  # Get all the voices
42
42
  pp Mac::Say.voices
43
43
 
44
- # Collect the separate features lists
44
+ # Collect the separate attributes lists
45
45
  pp Mac::Say.voices.collect { |v| v[:name] }
46
- pp Mac::Say.voices.collect { |v| v[:iso_code] }
46
+ pp Mac::Say.voices.collect { |v| v[:language] }
47
47
  pp Mac::Say.voices.collect { |v| v[:sample] }
48
48
 
49
+ # Look for voices by an attribute
50
+ pp Mac::Say.voice(:joke, false)
51
+ pp Mac::Say.voice(:gender, :female)
52
+
53
+ # Look for voices by a buch of attributes
54
+ pp Mac::Say.voice { |v| v[:joke] == true && v[:gender] == :female }
55
+ pp Mac::Say.voice { |v| v[:language] == :en && v[:gender] == :male && v[:quality] == :high && v[:joke] == false }
56
+
49
57
  # Find a voice (returns a Hash)
50
58
  pp Mac::Say.voice(:name, :alex)
51
59
  pp Mac::Say.voice(:country, :scotland)
@@ -54,7 +62,7 @@ pp Mac::Say.voice(:country, :scotland)
54
62
  pp Mac::Say.voice(:language, :en)
55
63
 
56
64
  # Work with the voices collection
57
- indian_english = Mac::Say.voice(:country, :in).select { |v| v[:iso_code][:language] == :en }.first[:name]
65
+ indian_english = Mac::Say.voice(:country, :in).select { |v| v[:language] == :en }.first[:name]
58
66
 
59
67
  # Use multiline text
60
68
  puts Mac::Say.say <<-DATA, indian_english
@@ -74,7 +82,7 @@ talker = Mac::Say.new(voice: Mac::Say.voice(:country, :scotland)[:name])
74
82
  talker.say string: talker.voice(:country, :scotland)[:sample]
75
83
 
76
84
  # with the dynamic voice name selected from the multiple voices
77
- talker = Mac::Say.news
85
+ talker = Mac::Say.new
78
86
  voice = talker.voice(:language, :en)&.sample(1)&.first&.fetch :name
79
87
  talker.say string: 'Hello world!', voice: voice
80
88
 
@@ -182,14 +190,14 @@ end
182
190
  # wrong feature
183
191
  begin
184
192
  Mac::Say.voice(:tone, :enthusiastic)
185
- rescue Mac::Say::UnknownVoiceFeature => e
193
+ rescue Mac::Say::UnknownVoiceAttribute => e
186
194
  puts e.message
187
195
  end
188
196
 
189
197
  # wrong feature
190
198
  begin
191
199
  Mac::Say.new.voice(:articulation, :nostalgic)
192
- rescue Mac::Say::UnknownVoiceFeature => e
200
+ rescue Mac::Say::UnknownVoiceAttribute => e
193
201
  puts e.message
194
202
  end
195
203
  ```
@@ -7,11 +7,19 @@ require_relative '../lib/mac/say'
7
7
  # Get all the voices
8
8
  pp Mac::Say.voices
9
9
 
10
- # Collect the separate features lists
10
+ # Collect the separate attributes lists
11
11
  pp Mac::Say.voices.collect { |v| v[:name] }
12
- pp Mac::Say.voices.collect { |v| v[:iso_code] }
12
+ pp Mac::Say.voices.collect { |v| v[:language] }
13
13
  pp Mac::Say.voices.collect { |v| v[:sample] }
14
14
 
15
+ # Look for voices by an attribute
16
+ pp Mac::Say.voice(:joke, false)
17
+ pp Mac::Say.voice(:gender, :female)
18
+
19
+ # Look for voices by multiple attributes
20
+ pp Mac::Say.voice { |v| v[:joke] == true && v[:gender] == :female }
21
+ pp Mac::Say.voice { |v| v[:language] == :en && v[:gender] == :male && v[:quality] == :high && v[:joke] == false }
22
+
15
23
  # Find a voice (returns a Hash)
16
24
  pp Mac::Say.voice(:name, :alex)
17
25
  pp Mac::Say.voice(:country, :scotland)
@@ -20,7 +28,7 @@ pp Mac::Say.voice(:country, :scotland)
20
28
  pp Mac::Say.voice(:language, :en)
21
29
 
22
30
  # Work with the voices collection
23
- indian_english = Mac::Say.voice(:country, :in).select { |v| v[:iso_code][:language] == :en }.first[:name]
31
+ indian_english = Mac::Say.voice(:country, :in).select { |v| v[:language] == :en }.first[:name]
24
32
 
25
33
  # Use multiline text
26
34
  puts Mac::Say.say <<-DATA, indian_english
@@ -40,7 +48,7 @@ talker = Mac::Say.new(voice: Mac::Say.voice(:country, :scotland)[:name])
40
48
  talker.say string: talker.voice(:country, :scotland)[:sample]
41
49
 
42
50
  # with the dynamic voice name selected from the multiple voices
43
- talker = Mac::Say.news
51
+ talker = Mac::Say.new
44
52
  voice = talker.voice(:language, :en)&.sample(1)&.first&.fetch :name
45
53
  talker.say string: 'Hello world!', voice: voice
46
54
 
@@ -148,13 +156,13 @@ end
148
156
  # wrong feature
149
157
  begin
150
158
  Mac::Say.voice(:tone, :enthusiastic)
151
- rescue Mac::Say::UnknownVoiceFeature => e
159
+ rescue Mac::Say::UnknownVoiceAttribute => e
152
160
  puts e.message
153
161
  end
154
162
 
155
163
  # wrong feature
156
164
  begin
157
165
  Mac::Say.new.voice(:articulation, :nostalgic)
158
- rescue Mac::Say::UnknownVoiceFeature => e
166
+ rescue Mac::Say::UnknownVoiceAttribute => e
159
167
  puts e.message
160
168
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  require_relative 'say/version'
3
+ require_relative 'say/voices_attributes'
4
+
3
5
  require 'English'
4
6
 
5
7
  # Wrapper namespace module for a Say class
@@ -8,7 +10,7 @@ module Mac
8
10
  # Allows to use simple TTS on Mac right from Ruby scripts
9
11
  class Say
10
12
  # A regex pattern to parse say voices list output
11
- VOICES_PATTERN = /(^[\w-]+)\s+([\w-]+)\s+#\s([\p{Graph}\p{Zs}]+$)/i
13
+ VOICE_PATTERN = /(^[\w-]+)\s+([\w-]+)\s+#\s([\p{Graph}\p{Zs}]+$)/i
12
14
 
13
15
  # An error raised when `say` command couldn't be found
14
16
  class CommandNotFound < StandardError; end
@@ -19,8 +21,19 @@ module Mac
19
21
  # An error raised when the given voice isn't valid
20
22
  class VoiceNotFound < StandardError; end
21
23
 
22
- # An error raised when there is no a feature of voice to match
23
- class UnknownVoiceFeature < StandardError; end
24
+ # An error raised when there is no a attribute of voice to match
25
+ class UnknownVoiceAttribute < StandardError; end
26
+
27
+ # The list of the voice attributes available
28
+ VOICE_ATTRIBUTES = [
29
+ :name,
30
+ :language,
31
+ :country,
32
+ :sample,
33
+ :gender,
34
+ :joke,
35
+ :quality
36
+ ]
24
37
 
25
38
  # Current voices list
26
39
  #
@@ -29,20 +42,22 @@ module Mac
29
42
  # Mac::Say.voices #=>
30
43
  # [
31
44
  # {
32
- # :name => :agnes,
33
- # :iso_code => {
34
- # :language => :en,
35
- # :country => :us
36
- # },
37
- # :sample => "Isn't it nice to have a computer that will talk to you?"
45
+ # :name => :agnes,
46
+ # :language => :en,
47
+ # :country => :us,
48
+ # :sample => "Isn't it nice to have a computer that will talk to you?",
49
+ # :gender => :female,
50
+ # :joke => false,
51
+ # :quality => :low
38
52
  # },
39
53
  # {
40
- # :name => :albert,
41
- # :iso_code => {
42
- # :language => :en,
43
- # :country => :us
44
- # },
45
- # :sample => " I have a frog in my throat. No, I mean a real frog!"
54
+ # :name => :albert,
55
+ # :language => :en,
56
+ # :country => :us,
57
+ # :sample => "I have a frog in my throat. No, I mean a real frog!",
58
+ # :gender => :male,
59
+ # :joke => true,
60
+ # :quality => :medium
46
61
  # },
47
62
  # ...
48
63
  # ]
@@ -94,7 +109,7 @@ module Mac
94
109
  # Providing file, voice or rate arguments changes instance state and influence
95
110
  # all the subsequent #say calls unless they have their own custom arguments
96
111
  #
97
- # @param string [String] a text to read using say command
112
+ # @param string [String] a text to read using say command (default: nil)
98
113
  # @param file [String] path to the file to read (default: nil)
99
114
  # @param voice [Symbol] voice to be used by the say command (default: :alex)
100
115
  # @param rate [Integer] speech rate in words per minute (default: 175) accepts values in (175..720)
@@ -126,32 +141,67 @@ module Mac
126
141
  execute_command(string)
127
142
  end
128
143
 
129
- # Find a voice by one of its features (e.g. :name, :language, :country)
144
+ # Look for voices by their attributes (e.g. :name, :language, :country, :gender, etc.)
145
+ #
146
+ # @overload voice(attribute, value)
147
+ # @param attribute [Symbol] the attribute to search voices by
148
+ # @param value [Symbol, String] the value of the attribute to search voices by
149
+ # @overload voice(&block)
150
+ # @yield [voice] Passes the given block to @voices.find_all
151
+ #
152
+ # @return [Array<Hash>, Hash, nil] an array with all the voices matched by the attribute or
153
+ # a voice Hash if only one voice corresponds to the attribute, nil if no voices found
154
+ #
155
+ # @example Find voices by one or more attributes
130
156
  #
131
- # @return [Array<Hash>, Hash] an array with all the voices matched by the feature or
132
- # a voice Hash if only one voice corresponds to the feature
157
+ # Mac::Say.new.voice(:joke, false)
158
+ # Mac::Say.new.voice(:gender, :female)
133
159
  #
134
- # @raise [UnknownVoiceFeature] if the voice feature isn't supported
135
- def self.voice(feature, name)
160
+ # Mac::Say.new.voice { |v| v[:joke] == true && v[:gender] == :female }
161
+ # Mac::Say.new.voice { |v| v[:language] == :en && v[:gender] == :male && v[:quality] == :high && v[:joke] == false }
162
+ #
163
+ # @raise [UnknownVoiceAttribute] if the voice attribute isn't supported def self.voice(attribute = nil, value = nil, &block)
164
+ def self.voice(attribute = nil, value = nil, &block)
136
165
  mac = new
137
- mac.voice(feature, name)
166
+
167
+ if block_given?
168
+ mac.voice(&block)
169
+ else
170
+ mac.voice(attribute, value)
171
+ end
138
172
  end
139
173
 
140
- # Find a voice by one of its features (e.g. :name, :language, :country)
174
+ # Look for voices by their attributes (e.g. :name, :language, :country, :gender, etc.)
141
175
  #
142
- # @return [Array<Hash>, Hash] an array with all the voices matched by the feature or
143
- # a voice Hash if only one voice corresponds to the feature
176
+ # @overload voice(attribute, value)
177
+ # @param attribute [Symbol] the attribute to search voices by
178
+ # @param value [Symbol, String] the value of the attribute to search voices by
179
+ # @overload voice(&block)
180
+ # @yield [voice] Passes the given block to @voices.find_all
144
181
  #
145
- # @raise [UnknownVoiceFeature] if the voice feature isn't supported
146
- def voice(feature, value)
147
- raise UnknownVoiceFeature, "Voice has no '#{feature}' feature" unless [:name, :language, :country].include?(feature)
148
- value = value.to_sym
149
-
150
- condition = feature == :name ? ->(v) { v[feature] == value } : ->(v) { v[:iso_code][feature] == value }
151
- found_voices = @voices.find_all(&condition)
182
+ # @return [Array<Hash>, Hash, nil] an array with all the voices matched by the attribute or
183
+ # a voice Hash if only one voice corresponds to the attribute, nil if no voices found
184
+ #
185
+ # @example Find voices by one or more attributes
186
+ #
187
+ # Mac::Say.new.voice(:joke, false)
188
+ # Mac::Say.new.voice(:gender, :female)
189
+ #
190
+ # Mac::Say.new.voice { |v| v[:joke] == true && v[:gender] == :female }
191
+ # Mac::Say.new.voice { |v| v[:language] == :en && v[:gender] == :male && v[:quality] == :high && v[:joke] == false }
192
+ #
193
+ # @raise [UnknownVoiceAttribute] if the voice attribute isn't supported
194
+ def voice(attribute = nil, value = nil, &block)
195
+ return unless (attribute && !value.nil?) || block_given?
196
+ raise UnknownVoiceAttribute, "Voice has no '#{attribute}' attribute" if attribute && !VOICE_ATTRIBUTES.include?(attribute)
197
+
198
+ if block_given?
199
+ found_voices = @voices.find_all(&block)
200
+ else
201
+ found_voices = @voices.find_all {|voice| voice[attribute] === value }
202
+ end
152
203
 
153
204
  return if found_voices.empty?
154
-
155
205
  found_voices.count == 1 ? found_voices.first : found_voices
156
206
  end
157
207
 
@@ -162,20 +212,22 @@ module Mac
162
212
  # Mac::Say.voices #=>
163
213
  # [
164
214
  # {
165
- # :name => :agnes,
166
- # :iso_code => {
167
- # :language => :en,
168
- # :country => :us
169
- # },
170
- # :sample => "Isn't it nice to have a computer that will talk to you?"
215
+ # :name => :agnes,
216
+ # :language => :en,
217
+ # :country => :us,
218
+ # :sample => "Isn't it nice to have a computer that will talk to you?",
219
+ # :gender => :female,
220
+ # :joke => false,
221
+ # :quality => :low
171
222
  # },
172
223
  # {
173
- # :name => :albert,
174
- # :iso_code => {
175
- # :language => :en,
176
- # :country => :us
177
- # },
178
- # :sample => " I have a frog in my throat. No, I mean a real frog!"
224
+ # :name => :albert,
225
+ # :language => :en,
226
+ # :country => :us,
227
+ # :sample => "I have a frog in my throat. No, I mean a real frog!",
228
+ # :gender => :male,
229
+ # :joke => true,
230
+ # :quality => :medium
179
231
  # },
180
232
  # ...
181
233
  # ]
@@ -214,7 +266,7 @@ module Mac
214
266
  # @raise [FileNotFound] if the given file wasn't found or isn't readable by the current user
215
267
  def generate_command
216
268
  say_path = @config[:say_path]
217
- file = @config[:file]
269
+ file = @config[:file]
218
270
 
219
271
  raise CommandNotFound, "Command `say` couldn't be found by '#{@config[:say_path]}' path" unless valid_command_path? say_path
220
272
 
@@ -235,22 +287,30 @@ module Mac
235
287
  # @raise [CommandNotFound] if the say command wasn't found
236
288
  def load_voices
237
289
  return if @voices
290
+
238
291
  say_path = @config[:say_path]
239
292
  raise CommandNotFound, "Command `say` couldn't be found by '#{say_path}' path" unless valid_command_path? say_path
240
293
 
241
- @voices = `#{say_path} -v '?'`.scan(VOICES_PATTERN).map do |voice|
294
+ @voices = `#{say_path} -v '?'`.scan(VOICE_PATTERN).map do |voice|
242
295
  lang = voice[1].split(/[_-]/)
296
+ name = voice[0].downcase.to_sym
297
+
298
+ additional_attributes = ADDITIONAL_VOICE_ATTRIBUTES[name] || ADDITIONAL_VOICE_ATTRIBUTES[:_unknown_voice]
299
+
243
300
  {
244
- name: voice[0].downcase.to_sym,
245
- iso_code: { language: lang[0].downcase.to_sym, country: lang[1].downcase.to_sym },
246
- sample: voice[2]
247
- }
301
+ name: name,
302
+ language: lang[0].downcase.to_sym,
303
+ country: lang[1].downcase.to_sym,
304
+ sample: voice[2].strip
305
+ }.merge(additional_attributes)
248
306
  end
249
307
  end
250
308
 
251
309
  # Checks voice existence by the name
252
310
  # Loads voices if they weren't loaded before
253
311
  #
312
+ # @param name [String, Symbol] the name of the voice to validate
313
+ #
254
314
  # @return [Boolean] if the voices name in the list of voices
255
315
  #
256
316
  # @raise [CommandNotFound] if the say command wasn't found
@@ -261,6 +321,8 @@ module Mac
261
321
 
262
322
  # Checks say command existence by the path
263
323
  #
324
+ # @param path [String] the path of the `say` command to validate
325
+ #
264
326
  # @return [Boolean] if the command exists and if it is executable
265
327
  def valid_command_path?(path)
266
328
  File.exist?(path) && File.executable?(path)
@@ -268,6 +330,8 @@ module Mac
268
330
 
269
331
  # Checks text file existence by the path
270
332
  #
333
+ # @param path [String] the path of the text file validate
334
+ #
271
335
  # @return [Boolean] if the file exists and if it is readable
272
336
  def valid_file_path?(path)
273
337
  path && File.exist?(path) && File.readable?(path)
@@ -2,10 +2,10 @@
2
2
 
3
3
  # Wrapper namespace module for a Say class
4
4
  module Mac
5
- # A class wrapper around the MacOS `say` commad
5
+ # A class wrapper around the MacOS `say` command
6
6
  # Allows to use simple TTS on Mac right from Ruby scripts
7
7
  class Say
8
8
  # mac-say version
9
- VERSION = '0.1.1'
9
+ VERSION = '0.2.0'
10
10
  end
11
11
  end
@@ -0,0 +1,510 @@
1
+ # Additional voice attributes mixed to the original Hash of
2
+ # parsed voices (to provide additional context when looking for a voice)
3
+ #
4
+ # @note Unfortunately information in system *.plist files
5
+ # e.g. /System/Library/Speech/Voices/<VoiceName>.SpeechVoice/Contents/Info.plist"
6
+ # is inconsistent, fragmentary and sometimes incorrect, but:
7
+ #
8
+ # WARNING: all the attributes values provided here are highly subjective!!!
9
+ ADDITIONAL_VOICE_ATTRIBUTES = {
10
+ _unknown_voice: {
11
+ gender: nil,
12
+ joke: nil,
13
+ quality: nil
14
+ },
15
+ agnes: {
16
+ gender: :female,
17
+ joke: false,
18
+ quality: :low
19
+ },
20
+ albert: {
21
+ gender: :male,
22
+ joke: true,
23
+ quality: :medium
24
+ },
25
+ alex: {
26
+ gender: :male,
27
+ joke: false,
28
+ quality: :medium
29
+ },
30
+ alice: {
31
+ gender: :female,
32
+ joke: false,
33
+ quality: :high
34
+ },
35
+ allison: {
36
+ gender: :female,
37
+ joke: false,
38
+ quality: :high
39
+ },
40
+ alva: {
41
+ gender: :female,
42
+ joke: false,
43
+ quality: :low
44
+ },
45
+ amelie: {
46
+ gender: :female,
47
+ joke: false,
48
+ quality: :medium
49
+ },
50
+ angelica: {
51
+ gender: :female,
52
+ joke: false,
53
+ quality: :low
54
+ },
55
+ anna: {
56
+ gender: :female,
57
+ joke: false,
58
+ quality: :medium
59
+ },
60
+ audrey: {
61
+ gender: :female,
62
+ joke: false,
63
+ quality: :medium
64
+ },
65
+ aurelie: {
66
+ gender: :female,
67
+ joke: false,
68
+ quality: :medium
69
+ },
70
+ ava: {
71
+ gender: :female,
72
+ joke: false,
73
+ quality: :high
74
+ },
75
+ bahh: {
76
+ gender: :male,
77
+ joke: true,
78
+ quality: :low
79
+ },
80
+ bells: {
81
+ gender: :neutral,
82
+ joke: true,
83
+ quality: :medium
84
+ },
85
+ boing: {
86
+ gender: :neutral,
87
+ joke: true,
88
+ quality: :medium
89
+ },
90
+ bruce: {
91
+ gender: :male,
92
+ joke: false,
93
+ quality: :low
94
+ },
95
+ bubbles: {
96
+ gender: :male,
97
+ joke: true,
98
+ quality: :medium
99
+ },
100
+ carlos: {
101
+ gender: :male,
102
+ joke: false,
103
+ quality: :high
104
+ },
105
+ carmit: {
106
+ gender: :female,
107
+ joke: false,
108
+ quality: :medium
109
+ },
110
+ catarina: {
111
+ gender: :female,
112
+ joke: false,
113
+ quality: :low
114
+ },
115
+ cellos: {
116
+ gender: :male,
117
+ joke: true,
118
+ quality: :medium
119
+ },
120
+ cem: {
121
+ gender: :male,
122
+ joke: false,
123
+ quality: :medium
124
+ },
125
+ chantal: {
126
+ gender: :female,
127
+ joke: false,
128
+ quality: :medium
129
+ },
130
+ claire: {
131
+ gender: :female,
132
+ joke: false,
133
+ quality: :medium
134
+ },
135
+ damayanti: {
136
+ gender: :female,
137
+ joke: false,
138
+ quality: :medium
139
+ },
140
+ daniel: {
141
+ gender: :male,
142
+ joke: false,
143
+ quality: :high
144
+ },
145
+ deranged: {
146
+ gender: :neutral,
147
+ joke: true,
148
+ quality: :medium
149
+ },
150
+ diego: {
151
+ gender: :male,
152
+ joke: false,
153
+ quality: :low
154
+ },
155
+ ellen: {
156
+ gender: :female,
157
+ joke: false,
158
+ quality: :low
159
+ },
160
+ ewa: {
161
+ gender: :female,
162
+ joke: false,
163
+ quality: :medium
164
+ },
165
+ federica: {
166
+ gender: :female,
167
+ joke: false,
168
+ quality: :medium
169
+ },
170
+ felipe: {
171
+ gender: :male,
172
+ joke: false,
173
+ quality: :medium
174
+ },
175
+ fiona: {
176
+ gender: :female,
177
+ joke: false,
178
+ quality: :high
179
+ },
180
+ fred: {
181
+ gender: :male,
182
+ joke: true,
183
+ quality: :low
184
+ },
185
+ henrik: {
186
+ gender: :male,
187
+ joke: false,
188
+ quality: :high
189
+ },
190
+ hysterical: {
191
+ gender: :male,
192
+ joke: true,
193
+ quality: :medium
194
+ },
195
+ ioana: {
196
+ gender: :female,
197
+ joke: false,
198
+ quality: :low
199
+ },
200
+ iveta: {
201
+ gender: :female,
202
+ joke: false,
203
+ quality: :low
204
+ },
205
+ joana: {
206
+ gender: :female,
207
+ joke: false,
208
+ quality: :low
209
+ },
210
+ jorge: {
211
+ gender: :male,
212
+ joke: false,
213
+ quality: :low
214
+ },
215
+ juan: {
216
+ gender: :male,
217
+ joke: false,
218
+ quality: :medium
219
+ },
220
+ junior: {
221
+ gender: :male,
222
+ joke: true,
223
+ quality: :low
224
+ },
225
+ kanya: {
226
+ gender: :female,
227
+ joke: false,
228
+ quality: :medium
229
+ },
230
+ karen: {
231
+ gender: :female,
232
+ joke: false,
233
+ quality: :high
234
+ },
235
+ kate: {
236
+ gender: :female,
237
+ joke: false,
238
+ quality: :high
239
+ },
240
+ kathy: {
241
+ gender: :female,
242
+ joke: true,
243
+ quality: :low
244
+ },
245
+ katya: {
246
+ gender: :female,
247
+ joke: false,
248
+ quality: :medium
249
+ },
250
+ klara: {
251
+ gender: :female,
252
+ joke: false,
253
+ quality: :low
254
+ },
255
+ kyoko: {
256
+ gender: :female,
257
+ joke: false,
258
+ quality: :low
259
+ },
260
+ laura: {
261
+ gender: :female,
262
+ joke: false,
263
+ quality: :low
264
+ },
265
+ lee: {
266
+ gender: :male,
267
+ joke: false,
268
+ quality: :high
269
+ },
270
+ lekha: {
271
+ gender: :female,
272
+ joke: false,
273
+ quality: :low
274
+ },
275
+ luca: {
276
+ gender: :male,
277
+ joke: false,
278
+ quality: :high
279
+ },
280
+ luciana: {
281
+ gender: :female,
282
+ joke: false,
283
+ quality: :low
284
+ },
285
+ maged: {
286
+ gender: :male,
287
+ joke: false,
288
+ quality: :low
289
+ },
290
+ magnus: {
291
+ gender: :male,
292
+ joke: false,
293
+ quality: :medium
294
+ },
295
+ mariska: {
296
+ gender: :female,
297
+ joke: false,
298
+ quality: :medium
299
+ },
300
+ markus: {
301
+ gender: :male,
302
+ joke: false,
303
+ quality: :low
304
+ },
305
+ :'mei-jia' => {
306
+ gender: :female,
307
+ joke: false,
308
+ quality: :medium
309
+ },
310
+ melina: {
311
+ gender: :female,
312
+ joke: false,
313
+ quality: :low
314
+ },
315
+ milena: {
316
+ gender: :female,
317
+ joke: false,
318
+ quality: :medium
319
+ },
320
+ moira: {
321
+ gender: :female,
322
+ joke: false,
323
+ quality: :medium
324
+ },
325
+ monica: {
326
+ gender: :female,
327
+ joke: false,
328
+ quality: :medium
329
+ },
330
+ nicolas: {
331
+ gender: :male,
332
+ joke: false,
333
+ quality: :high
334
+ },
335
+ nikos: {
336
+ gender: :male,
337
+ joke: false,
338
+ quality: :medium
339
+ },
340
+ nora: {
341
+ gender: :female,
342
+ joke: false,
343
+ quality: :low
344
+ },
345
+ oliver: {
346
+ gender: :male,
347
+ joke: false,
348
+ quality: :low
349
+ },
350
+ oskar: {
351
+ gender: :male,
352
+ joke: false,
353
+ quality: :medium
354
+ },
355
+ otoya: {
356
+ gender: :male,
357
+ joke: false,
358
+ quality: :medium
359
+ },
360
+ paola: {
361
+ gender: :female,
362
+ joke: false,
363
+ quality: :high
364
+ },
365
+ paulina: {
366
+ gender: :female,
367
+ joke: false,
368
+ quality: :low
369
+ },
370
+ petra: {
371
+ gender: :female,
372
+ joke: false,
373
+ quality: :medium
374
+ },
375
+ princess: {
376
+ gender: :female,
377
+ joke: true,
378
+ quality: :low
379
+ },
380
+ ralph: {
381
+ gender: :male,
382
+ joke: true,
383
+ quality: :low
384
+ },
385
+ samantha: {
386
+ gender: :female,
387
+ joke: false,
388
+ quality: :high
389
+ },
390
+ sara: {
391
+ gender: :female,
392
+ joke: false,
393
+ quality: :medium
394
+ },
395
+ satu: {
396
+ gender: :female,
397
+ joke: false,
398
+ quality: :medium
399
+ },
400
+ serena: {
401
+ gender: :female,
402
+ joke: false,
403
+ quality: :medium
404
+ },
405
+ :'sin-ji' => {
406
+ gender: :female,
407
+ joke: false,
408
+ quality: :medium
409
+ },
410
+ soledad: {
411
+ gender: :female,
412
+ joke: false,
413
+ quality: :medium
414
+ },
415
+ susan: {
416
+ gender: :female,
417
+ joke: false,
418
+ quality: :medium
419
+ },
420
+ tarik: {
421
+ gender: :male,
422
+ joke: false,
423
+ quality: :low
424
+ },
425
+ tessa: {
426
+ gender: :female,
427
+ joke: false,
428
+ quality: :high
429
+ },
430
+ thomas: {
431
+ gender: :male,
432
+ joke: false,
433
+ quality: :high
434
+ },
435
+ :'ting-ting' => {
436
+ gender: :female,
437
+ joke: false,
438
+ quality: :high
439
+ },
440
+ tom: {
441
+ gender: :male,
442
+ joke: false,
443
+ quality: :high
444
+ },
445
+ trinoids: {
446
+ gender: :neutral,
447
+ joke: true,
448
+ quality: :medium
449
+ },
450
+ veena: {
451
+ gender: :female,
452
+ joke: false,
453
+ quality: :high
454
+ },
455
+ vicki: {
456
+ gender: :female,
457
+ joke: false,
458
+ quality: :low
459
+ },
460
+ victoria: {
461
+ gender: :female,
462
+ joke: false,
463
+ quality: :low
464
+ },
465
+ whisper: {
466
+ gender: :male,
467
+ joke: true,
468
+ quality: :medium
469
+ },
470
+ xander: {
471
+ gender: :male,
472
+ joke: false,
473
+ quality: :high
474
+ },
475
+ yannick: {
476
+ gender: :male,
477
+ joke: false,
478
+ quality: :medium
479
+ },
480
+ yelda: {
481
+ gender: :female,
482
+ joke: false,
483
+ quality: :low
484
+ },
485
+ yuna: {
486
+ gender: :female,
487
+ joke: false,
488
+ quality: :medium
489
+ },
490
+ yuri: {
491
+ gender: :male,
492
+ joke: false,
493
+ quality: :low
494
+ },
495
+ zarvox: {
496
+ gender: :neutral,
497
+ joke: true,
498
+ quality: :medium
499
+ },
500
+ zosia: {
501
+ gender: :female,
502
+ joke: false,
503
+ quality: :low
504
+ },
505
+ zuzana: {
506
+ gender: :female,
507
+ joke: false,
508
+ quality: :high
509
+ }
510
+ }.freeze
@@ -9,6 +9,7 @@ Fiona en-scotland # Hello, my name is Fiona. I am a Scottish-Engli
9
9
  Serena en_GB # Hello, my name is Serena. I am a British-English voice.
10
10
  Ting-Ting zh_CN # 您好,我叫Ting-Ting。我讲中文普通话。
11
11
  Veena en_IN # Hello, my name is Veena. I am an Indian-English voice.
12
+ Test en_GB # Well I'm the most mysterious voice round here, believe me.
12
13
  VOICES
13
14
 
14
15
  VOICES_NAMES = [
@@ -16,7 +17,8 @@ VOICES_NAMES = [
16
17
  :daniel,
17
18
  :fiona,
18
19
  :'ting-ting',
19
- :veena
20
+ :veena,
21
+ :test
20
22
  ]
21
23
 
22
24
  options = {
@@ -4,15 +4,19 @@ require 'rubygems'
4
4
  require 'coveralls'
5
5
  Coveralls.wear!
6
6
 
7
+ require 'simplecov'
8
+ SimpleCov.formatters = SimpleCov::Formatter::MultiFormatter.new [
9
+ SimpleCov::Formatter::HTMLFormatter,
10
+ Coveralls::SimpleCov::Formatter
11
+ ]
12
+ SimpleCov.start
13
+
7
14
  begin
8
15
  require 'bundler/setup'
9
16
  rescue LoadError => error
10
17
  abort error.message
11
18
  end
12
19
 
13
- require 'simplecov'
14
- SimpleCov.start
15
-
16
20
  require 'minitest/autorun'
17
21
  require 'minitest/reporters'
18
22
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  require 'helper'
3
3
  require 'mac/say'
4
+ require 'mac/say/voices_attributes'
4
5
 
5
6
  describe 'Mac::Say as a macOS `say` wrapper' do
6
7
  describe 'On a class level' do
@@ -21,34 +22,27 @@ describe 'Mac::Say as a macOS `say` wrapper' do
21
22
  voice = Mac::Say.voice(:name, :alex)
22
23
  voice.wont_be_empty
23
24
  voice.must_be_kind_of Hash
24
- voice.keys.must_equal [:name, :iso_code, :sample]
25
+ voice.keys.must_equal Mac::Say::VOICE_ATTRIBUTES
25
26
  end
26
27
 
27
- it 'must return specific Hash structure for an iso_code' do
28
- voice = Mac::Say.voice(:name, :alex)
29
- voice.wont_be_empty
30
- voice[:iso_code].must_be_kind_of Hash
31
- voice[:iso_code].keys.must_equal [:language, :country]
32
- end
28
+ it 'must return additional attributes for known voices' do
29
+ voice_name = :alex
30
+ voice = Mac::Say.voice(:name, voice_name)
31
+ additional_voice_attributes = ADDITIONAL_VOICE_ATTRIBUTES[voice_name]
33
32
 
34
- it '.voice must search for a voice by name' do
35
- voice = Mac::Say.voice(:name, :alex)
36
- voice[:name].must_equal :alex
33
+ voice[:gender].must_equal additional_voice_attributes[:gender]
34
+ voice[:joke].must_equal additional_voice_attributes[:joke]
35
+ voice[:quality].must_equal additional_voice_attributes[:quality]
37
36
  end
38
37
 
39
- it '.voice must accept String as a value' do
40
- voice = Mac::Say.voice(:name, 'alex')
38
+ it '.voice must search for a voice using single attribute' do
39
+ voice = Mac::Say.voice(:name, :alex)
41
40
  voice[:name].must_equal :alex
42
41
  end
43
42
 
44
- it '.voice must search for a voice by country' do
45
- voice = Mac::Say.voice(:country, :scotland)
46
- voice[:name].must_equal :fiona
47
- end
48
-
49
- it '.voice must search for a voice by language' do
50
- voices = Mac::Say.voice(:language, :en)
51
- voices.count.must_be :>, 2
43
+ it '.voice must search for a voice using block given' do
44
+ voices = Mac::Say.voice {|voice| voice[:language] == :en && voice[:joke] == false }
45
+ voices.must_be_kind_of Array
52
46
  end
53
47
 
54
48
  it '.voice must return one voice as a Hash' do
@@ -66,6 +60,7 @@ describe 'Mac::Say as a macOS `say` wrapper' do
66
60
  voices.must_be_nil
67
61
  end
68
62
 
63
+
69
64
  it '.say must return 0 in successive speech' do
70
65
  expectation = ["#{@say_path} -v 'alex' -r 175", 0]
71
66
  Mac::Say.say('42').must_equal expectation
@@ -91,10 +86,10 @@ describe 'Mac::Say as a macOS `say` wrapper' do
91
86
  }.must_raise Mac::Say::VoiceNotFound
92
87
  end
93
88
 
94
- it '.voice must fail on wrong voice feature' do
89
+ it '.voice must fail on wrong voice attribute' do
95
90
  -> {
96
91
  Mac::Say.voice(:tone, :enthusiastic)
97
- }.must_raise Mac::Say::UnknownVoiceFeature
92
+ }.must_raise Mac::Say::UnknownVoiceAttribute
98
93
  end
99
94
  end
100
95
 
@@ -133,6 +128,17 @@ describe 'Mac::Say as a macOS `say` wrapper' do
133
128
  @reader.say.must_equal expectation
134
129
  end
135
130
 
131
+ it 'must return nil additional attrs for unknown voices' do
132
+ if ENV['USE_FAKE_SAY']
133
+ voice = @reader.voice(:name, :test)
134
+ additional_voice_attributes = ADDITIONAL_VOICE_ATTRIBUTES[:test]
135
+
136
+ voice[:gender].must_be_nil
137
+ voice[:joke].must_be_nil
138
+ voice[:quality].must_be_nil
139
+ end
140
+ end
141
+
136
142
  it '#say must change :file from initial config' do
137
143
  gb_absolute_path = File.absolute_path './fixtures/text/en_gb_test.txt', File.dirname(__FILE__)
138
144
  us_absolute_path = File.absolute_path './fixtures/text/en_us_test.txt', File.dirname(__FILE__)
@@ -217,10 +223,10 @@ describe 'Mac::Say as a macOS `say` wrapper' do
217
223
  }.must_raise Mac::Say::FileNotFound
218
224
  end
219
225
 
220
- it '#voice must fail on wrong feature' do
226
+ it '#voice must fail on wrong attribute' do
221
227
  -> {
222
228
  Mac::Say.new.voice(:articulation, :nostalgic)
223
- }.must_raise Mac::Say::UnknownVoiceFeature
229
+ }.must_raise Mac::Say::UnknownVoiceAttribute
224
230
  end
225
231
 
226
232
  it '#say must fail on initial wrong file path' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mac-say
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Serge Bedzhyk
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-01-30 00:00:00.000000000 Z
11
+ date: 2017-02-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -201,6 +201,7 @@ files:
201
201
  - img/voices_manual.png
202
202
  - lib/mac/say.rb
203
203
  - lib/mac/say/version.rb
204
+ - lib/mac/say/voices_attributes.rb
204
205
  - mac-say.gemspec
205
206
  - test/fake/say
206
207
  - test/fixtures/text/en_gb.txt