mac-say 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: befe01288b8556115593d963428671229af0a2e0
4
+ data.tar.gz: 715ed2147a05a67de7c05012c1b4009d909ed0af
5
+ SHA512:
6
+ metadata.gz: 9d48e9271ac17bc24a068c2ce63121a3f9bd2be092c5d3002d63400e2cfc5f24f26c6f6817c24b5b37a823665043fd3e9edeb33cd91712b63bba85b67d02ac59
7
+ data.tar.gz: 916ea15be74ff4d22c461c25117ae8240556bc4d9a09a58c667c0d6469b136621cbc889c123e11b50d1c8eaa9eb88e0f8916fc8ff36ac54f6142543e0d1c0e1d
data/.document ADDED
@@ -0,0 +1,4 @@
1
+ -
2
+ ChangeLog.md
3
+ README.md
4
+ LICENSE.txt
data/.gitignore ADDED
@@ -0,0 +1,40 @@
1
+ /.bundle
2
+ /.yardoc/
3
+ /Gemfile.lock
4
+ /doc/
5
+ /pkg/
6
+ /coverage/
7
+ /vendor/cache/*.gem
8
+
9
+ #********** osx template**********
10
+
11
+ .DS_Store
12
+
13
+ # Thumbnails
14
+ ._*
15
+
16
+ # Files that might appear on external disk
17
+ .Spotlight-V100
18
+ .Trashes
19
+
20
+
21
+ #********** ruby template**********
22
+
23
+ *.gem
24
+ *.rbc
25
+ .bundle
26
+ .config
27
+ coverage
28
+ InstalledFiles
29
+ lib/bundler/man
30
+ pkg
31
+ rdoc
32
+ spec/reports
33
+ test/tmp
34
+ test/version_tmp
35
+ tmp
36
+
37
+ # YARD artifacts
38
+ .yardoc
39
+ _yardoc
40
+ doc/
data/.rubocop.yml ADDED
@@ -0,0 +1,15 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.3
3
+ Exclude:
4
+ - 'test/test_mac-say.rb'
5
+ - 'mac-say.gemspec'
6
+ Metrics/AbcSize:
7
+ Max: 18
8
+ Style/AsciiComments:
9
+ Enabled: false
10
+ Style/MutableConstant:
11
+ Enabled: false
12
+ Metrics/LineLength:
13
+ Max: 130
14
+ Metrics/MethodLength:
15
+ Max: 11
data/.travis.yml ADDED
@@ -0,0 +1,12 @@
1
+ ---
2
+ before_install:
3
+ - gem update bundler
4
+ language: ruby
5
+ script:
6
+ - bundle exec rake test
7
+ env:
8
+ - USE_FAKE_SAY=./test/fake/say
9
+ rvm:
10
+ - 2.0
11
+ - 2.1
12
+ - 2.2
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --markup markdown --markup-provider=redcarpet --title "mac-say Documentation" --protected --asset img:img
data/ChangeLog.md ADDED
@@ -0,0 +1,4 @@
1
+ ### 0.1.0 / 2017-01-22
2
+
3
+ * Initial release:
4
+
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+ source 'https://rubygems.org'
3
+
4
+ gemspec
5
+
6
+ group :development do
7
+ gem 'kramdown'
8
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2017 Serge Bedzhyk
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,237 @@
1
+ # mac-say
2
+
3
+ <p align="center">
4
+ <img title="mac-say logo" src ="./img/logo.png" />
5
+ </p>
6
+
7
+ > Ruby wrapper around the modern version of the macOS `say` command. Inspired by the @bratta's [mactts](https://github.com/bratta/mactts)
8
+ >
9
+ > [![Build Status](https://travis-ci.org/smileart/mac-say.svg?branch=dev)](https://travis-ci.org/smileart/mac-say) [![Coverage Status](https://coveralls.io/repos/github/smileart/mac-say/badge.svg?branch=dev)](https://coveralls.io/github/smileart/mac-say?branch=dev) [![Code Climate](https://codeclimate.com/github/smileart/mac-say/badges/gpa.svg)](https://codeclimate.com/github/smileart/mac-say) [![InchCI Status](https://inch-ci.org/github/smileart/mac-say.svg?branch=dev)](https://inch-ci.org/github/smileart/mac-say)
10
+
11
+ * [Homepage](https://rubygems.org/gems/mac-say)
12
+ * [Documentation](http://rubydoc.info/gems/mac-say/frames)
13
+ * [Email](mailto:smileart21 at gmail.com)
14
+
15
+ ## Features
16
+
17
+ * [x] Basic strings reading
18
+ * [x] Basic files reading
19
+ * [x] Multiline strings support
20
+ * [x] Dynamic voices parsing (based on real `say` output)
21
+ * [x] Voices list generation (including samples and ISO information)
22
+ * [x] Voices search (by name / language / country)
23
+ * [x] Simple (class-level) and customisable (instance-level) usage
24
+ * [ ] Observe reading progress line by line❓
25
+ * [ ] Audio output support❓
26
+
27
+ ## Install
28
+
29
+ ```sh
30
+ $ gem install mac-say
31
+ ```
32
+
33
+ ## Examples
34
+
35
+ ```ruby
36
+ require 'pp'
37
+ require 'mac/say'
38
+
39
+ # ===== Class level =====
40
+
41
+ # Get all the voices
42
+ pp Mac::Say.voices
43
+
44
+ # Collect the separate features lists
45
+ pp Mac::Say.voices.collect { |v| v[:name] }
46
+ pp Mac::Say.voices.collect { |v| v[:iso_code] }
47
+ pp Mac::Say.voices.collect { |v| v[:sample] }
48
+
49
+ # Find a voice (returns a Hash)
50
+ pp Mac::Say.voice(:name, :alex)
51
+ pp Mac::Say.voice(:country, :scotland)
52
+
53
+ # Find the voices by the feature (returns an Array)
54
+ pp Mac::Say.voice(:language, :en)
55
+
56
+ # Work with the voices collection
57
+ indian_english = Mac::Say.voice(:country, :in).select { |v| v[:iso_code][:language] == :en }.first[:name]
58
+
59
+ # Use multiline text
60
+ puts Mac::Say.say <<-DATA, indian_english
61
+ Invokes the given block passing in successive elements from self, deleting elements for which the block returns a false value.
62
+ The array may not be changed instantly every time the block is called.
63
+ If changes were made, it will return self, otherwise it returns nil.
64
+ DATA
65
+
66
+ # ===== Instance level =====
67
+
68
+ # with constant name in the constructor and custom rate
69
+ talker = Mac::Say.new(voice: :alex, rate: 300)
70
+ talker.say string: 'Hello world'
71
+
72
+ # with the voice name from the class method + dynamic sample
73
+ talker = Mac::Say.new(voice: Mac::Say.voice(:country, :scotland)[:name])
74
+ talker.say string: talker.voice(:country, :scotland)[:sample]
75
+
76
+ # with the dynamic voice name selected from the multiple voices
77
+ talker = Mac::Say.news
78
+ voice = talker.voice(:language, :en)&.sample(1)&.first&.fetch :name
79
+ talker.say string: 'Hello world!', voice: voice
80
+
81
+ # changing voice in runtime for an instance of talker (while saying something)
82
+ voice = talker.voice(:country, :kr)
83
+ talker.say string: voice[:sample], voice: voice[:name]
84
+
85
+ # or change the voice without saying anything
86
+ talker.say voice: :"ting-ting"
87
+ talker.say string: '您好,我叫Ting-Ting。我讲中文普通话。'
88
+
89
+ # Listen to all the languages with the dynamic voices + dynamic samples
90
+ polyglot = Mac::Say.new
91
+ voices = polyglot.voices
92
+
93
+ voices.each_with_index do |v, i|
94
+ puts "#{i + 1} :: #{v[:name]} :: '#{v[:sample]}'"
95
+ polyglot.say string: v[:sample], voice: v[:name]
96
+ end
97
+
98
+ # Or perform a roll call
99
+ roll_call = Mac::Say.new
100
+ voices = roll_call.voices
101
+
102
+ voices.each_with_index do |v, i|
103
+ puts "#{i + 1} :: #{v[:name]}"
104
+ roll_call.say string: v[:name], voice: v[:name]
105
+ end
106
+
107
+ # ===== Reading files =====
108
+
109
+ # Read the file (prior to the string)
110
+ file_path = File.absolute_path '../test/fixtures/text/en_gb.txt', File.dirname(__FILE__)
111
+
112
+ # with a voice from the class
113
+ voice = Mac::Say.voice(:country, :gb)&.first&.fetch(:name)
114
+ reader = Mac::Say.new(file: file_path, voice: voice)
115
+ reader.read
116
+
117
+ # with a dynamic voice from the class
118
+ voice = Mac::Say.voice(:country, :scotland)[:name]
119
+ reader = Mac::Say.new(file: file_path)
120
+ reader.read voice: voice
121
+
122
+ # with a dynamic voice from the instance
123
+ reader = Mac::Say.new(file: file_path)
124
+ reader.read(voice: reader.voice(:country, :us)[2][:name])
125
+
126
+ # with a dynamic voice from the instance
127
+ new_file_path = File.absolute_path '../test/fixtures/text/en_us.txt', File.dirname(__FILE__)
128
+
129
+ # with a dynamic file change
130
+ reader = Mac::Say.new(file: file_path)
131
+ reader.read voice: :alex
132
+ reader.read file: new_file_path
133
+
134
+ # ===== Exceptions =====
135
+
136
+ # wrong file
137
+ begin
138
+ reader = Mac::Say.new(file: 'wrong')
139
+ reader.read
140
+ rescue Mac::Say::FileNotFound => e
141
+ puts e.message
142
+ end
143
+
144
+ # wrong file
145
+ begin
146
+ reader = Mac::Say.new
147
+ reader.read file: 'too_wrong'
148
+ rescue Mac::Say::FileNotFound => e
149
+ puts e.message
150
+ end
151
+
152
+ # wrong path
153
+ begin
154
+ Mac::Say.new(say_path: '/usr/bin/wrong_say')
155
+ rescue Mac::Say::CommandNotFound => e
156
+ puts e.message
157
+ end
158
+
159
+ # wrong voice
160
+ begin
161
+ talker = Mac::Say.new(voice: :wrong)
162
+ talker.say string: 'OMG! I lost my voice!'
163
+ rescue Mac::Say::VoiceNotFound => e
164
+ puts e.message
165
+ end
166
+
167
+ # wrong voice
168
+ begin
169
+ talker = Mac::Say.new
170
+ talker.say string: 'OMG! I lost my voice!', voice: :too_wrong
171
+ rescue Mac::Say::VoiceNotFound => e
172
+ puts e.message
173
+ end
174
+
175
+ # wrong voice
176
+ begin
177
+ Mac::Say.say 'OMG! I lost my voice!', :still_wrong
178
+ rescue Mac::Say::VoiceNotFound => e
179
+ puts e.message
180
+ end
181
+
182
+ # wrong feature
183
+ begin
184
+ Mac::Say.voice(:tone, :enthusiastic)
185
+ rescue Mac::Say::UnknownVoiceFeature => e
186
+ puts e.message
187
+ end
188
+
189
+ # wrong feature
190
+ begin
191
+ Mac::Say.new.voice(:articulation, :nostalgic)
192
+ rescue Mac::Say::UnknownVoiceFeature => e
193
+ puts e.message
194
+ end
195
+ ```
196
+
197
+ ## Installing & Updating MacOS TTS Voices
198
+
199
+ Open `System Preferences` using Spotlight / Alfred / Dock and follow text or visual instructions:
200
+
201
+ ```
202
+ System Preferences → Accessibility → Speech → System Voice →
203
+ → Customize… → (select voices) → OK → (Wait for download…)
204
+ ```
205
+
206
+ ![Installing & Updating MacOS TTS Voices](./img/voices_manual.png)
207
+
208
+ ## Dev Notes
209
+
210
+ ```sh
211
+ # generated with Ore: https://github.com/ruby-ore/ore
212
+ $ mine mac-say --git --mit --rubygems-tasks --markdown --minitest --travis --yard
213
+
214
+ # generate docs (unless this resolved: https://github.com/rrrene/inch/issues/42)
215
+ $ yard --markup markdown --markup-provider=redcarpet --title "mac-say Documentation" --protected --asset img:img
216
+
217
+ # check the docs
218
+ $ inch --pedantic
219
+
220
+ # test with a fake `say`
221
+ $ USE_FAKE_SAY='./test/fake/say' bundle exec rake test
222
+
223
+ # test with rake
224
+ $ bundle exec rake test
225
+
226
+ # test with m
227
+ $ bundle exec m
228
+
229
+ # run one test by LN
230
+ $ bundle exec m ./test/test_mac-say.rb:34
231
+ ```
232
+
233
+ ## Copyright
234
+
235
+ Copyright (c) 2017 Serge Bedzhyk
236
+
237
+ See [LICENSE.txt](./LICENSE.txt) for details.
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+
4
+ require 'rubygems'
5
+
6
+ begin
7
+ require 'bundler/setup'
8
+ rescue LoadError => e
9
+ abort e.message
10
+ end
11
+
12
+ require 'rake'
13
+ require 'rubygems/tasks'
14
+ Gem::Tasks.new
15
+
16
+ require 'rake/testtask'
17
+
18
+ Rake::TestTask.new do |test|
19
+ test.libs << 'test'
20
+ test.pattern = 'test/**/test_*.rb'
21
+ test.verbose = true
22
+ end
23
+
24
+ # Wait for https://github.com/rrrene/inch/issues/42 to be resolved
25
+ # require 'yard'
26
+ # YARD::Rake::YardocTask.new
27
+ # task :doc => :yard
data/config.reek ADDED
@@ -0,0 +1,4 @@
1
+ TooManyStatements:
2
+ max_statements: 10
3
+ LongParameterList
4
+ max_params: 5
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+ require 'pp'
3
+ require_relative '../lib/mac/say'
4
+
5
+ # ===== Class level =====
6
+
7
+ # Get all the voices
8
+ pp Mac::Say.voices
9
+
10
+ # Collect the separate features lists
11
+ pp Mac::Say.voices.collect { |v| v[:name] }
12
+ pp Mac::Say.voices.collect { |v| v[:iso_code] }
13
+ pp Mac::Say.voices.collect { |v| v[:sample] }
14
+
15
+ # Find a voice (returns a Hash)
16
+ pp Mac::Say.voice(:name, :alex)
17
+ pp Mac::Say.voice(:country, :scotland)
18
+
19
+ # Find the voices by the feature (returns an Array)
20
+ pp Mac::Say.voice(:language, :en)
21
+
22
+ # Work with the voices collection
23
+ indian_english = Mac::Say.voice(:country, :in).select { |v| v[:iso_code][:language] == :en }.first[:name]
24
+
25
+ # Use multiline text
26
+ puts Mac::Say.say <<-DATA, indian_english
27
+ Invokes the given block passing in successive elements from self, deleting elements for which the block returns a false value.
28
+ The array may not be changed instantly every time the block is called.
29
+ If changes were made, it will return self, otherwise it returns nil.
30
+ DATA
31
+
32
+ # ===== Instance level =====
33
+
34
+ # with constant name in the constructor and custom rate
35
+ talker = Mac::Say.new(voice: :alex, rate: 300)
36
+ talker.say string: 'Hello world'
37
+
38
+ # with the voice name from the class method + dynamic sample
39
+ talker = Mac::Say.new(voice: Mac::Say.voice(:country, :scotland)[:name])
40
+ talker.say string: talker.voice(:country, :scotland)[:sample]
41
+
42
+ # with the dynamic voice name selected from the multiple voices
43
+ talker = Mac::Say.news
44
+ voice = talker.voice(:language, :en)&.sample(1)&.first&.fetch :name
45
+ talker.say string: 'Hello world!', voice: voice
46
+
47
+ # changing voice in runtime for an instance of talker (while saying something)
48
+ voice = talker.voice(:country, :kr)
49
+ talker.say string: voice[:sample], voice: voice[:name]
50
+
51
+ # or change the voice without saying anything
52
+ talker.say voice: :"ting-ting"
53
+ talker.say string: '您好,我叫Ting-Ting。我讲中文普通话。'
54
+
55
+ # Listen to all the languages with the dynamic voices + dynamic samples
56
+ polyglot = Mac::Say.new
57
+ voices = polyglot.voices
58
+
59
+ voices.each_with_index do |v, i|
60
+ puts "#{i + 1} :: #{v[:name]} :: '#{v[:sample]}'"
61
+ polyglot.say string: v[:sample], voice: v[:name]
62
+ end
63
+
64
+ # Or perform a roll call
65
+ roll_call = Mac::Say.new
66
+ voices = roll_call.voices
67
+
68
+ voices.each_with_index do |v, i|
69
+ puts "#{i + 1} :: #{v[:name]}"
70
+ roll_call.say string: v[:name], voice: v[:name]
71
+ end
72
+
73
+ # ===== Reading files =====
74
+
75
+ # Read the file (prior to the string)
76
+ file_path = File.absolute_path '../test/fixtures/text/en_gb.txt', File.dirname(__FILE__)
77
+
78
+ # with a voice from the class
79
+ voice = Mac::Say.voice(:country, :gb)&.first&.fetch(:name)
80
+ reader = Mac::Say.new(file: file_path, voice: voice)
81
+ reader.read
82
+
83
+ # with a dynamic voice from the class
84
+ voice = Mac::Say.voice(:country, :scotland)[:name]
85
+ reader = Mac::Say.new(file: file_path)
86
+ reader.read voice: voice
87
+
88
+ # with a dynamic voice from the instance
89
+ reader = Mac::Say.new(file: file_path)
90
+ reader.read(voice: reader.voice(:country, :us)[2][:name])
91
+
92
+ # with a dynamic voice from the instance
93
+ new_file_path = File.absolute_path '../test/fixtures/text/en_us.txt', File.dirname(__FILE__)
94
+
95
+ # with a dynamic file change
96
+ reader = Mac::Say.new(file: file_path)
97
+ reader.read voice: :alex
98
+ reader.read file: new_file_path
99
+
100
+ # ===== Exceptions =====
101
+
102
+ # wrong file
103
+ begin
104
+ reader = Mac::Say.new(file: 'wrong')
105
+ reader.read
106
+ rescue Mac::Say::FileNotFound => e
107
+ puts e.message
108
+ end
109
+
110
+ # wrong file
111
+ begin
112
+ reader = Mac::Say.new
113
+ reader.read file: 'too_wrong'
114
+ rescue Mac::Say::FileNotFound => e
115
+ puts e.message
116
+ end
117
+
118
+ # wrong path
119
+ begin
120
+ Mac::Say.new(say_path: '/usr/bin/wrong_say')
121
+ rescue Mac::Say::CommandNotFound => e
122
+ puts e.message
123
+ end
124
+
125
+ # wrong voice
126
+ begin
127
+ talker = Mac::Say.new(voice: :wrong)
128
+ talker.say string: 'OMG! I lost my voice!'
129
+ rescue Mac::Say::VoiceNotFound => e
130
+ puts e.message
131
+ end
132
+
133
+ # wrong voice
134
+ begin
135
+ talker = Mac::Say.new
136
+ talker.say string: 'OMG! I lost my voice!', voice: :too_wrong
137
+ rescue Mac::Say::VoiceNotFound => e
138
+ puts e.message
139
+ end
140
+
141
+ # wrong voice
142
+ begin
143
+ Mac::Say.say 'OMG! I lost my voice!', :still_wrong
144
+ rescue Mac::Say::VoiceNotFound => e
145
+ puts e.message
146
+ end
147
+
148
+ # wrong feature
149
+ begin
150
+ Mac::Say.voice(:tone, :enthusiastic)
151
+ rescue Mac::Say::UnknownVoiceFeature => e
152
+ puts e.message
153
+ end
154
+
155
+ # wrong feature
156
+ begin
157
+ Mac::Say.new.voice(:articulation, :nostalgic)
158
+ rescue Mac::Say::UnknownVoiceFeature => e
159
+ puts e.message
160
+ end