mac-say 0.1.1 → 0.2.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 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