Class: Mac::Say

Inherits:
Object
  • Object
show all
Defined in:
lib/mac/say.rb,
lib/mac/say/version.rb

Overview

A class wrapper around the MacOS say command Allows to use simple TTS on Mac right from Ruby scripts

Defined Under Namespace

Classes: CommandNotFound, FileNotFound, UnknownVoiceAttribute, VoiceNotFound

Constant Summary collapse

VOICE_PATTERN =

A regex pattern to parse say voices list output

/(^[\w\s-]+)\s+([\w-]+)\s+#\s([\p{Graph}\p{Zs}]+$)/i
VOICE_ATTRIBUTES =

The list of the voice attributes available

[
  :name,
  :language,
  :country,
  :sample,
  :gender,
  :joke,
  :quality,
  :singing
]
VERSION =

mac-say version

'0.2.1'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(voice: :alex, rate: 175, file: nil, say_path: ENV['USE_FAKE_SAY'] || '/usr/bin/say') ⇒ Say

Say constructor: sets initial configuration for say command to use

Parameters:

  • say_path (String) (defaults to: ENV['USE_FAKE_SAY'] || '/usr/bin/say')

    the full path to the say app binary (default: '/usr/bin/say' or USE_FAKE_SAY environment variable)

  • voice (Symbol) (defaults to: :alex)

    voice to be used by the say command (default: :alex)

  • rate (Integer) (defaults to: 175)

    speech rate in words per minute (default: 175) accepts values in (175..720)

  • file (String) (defaults to: nil)

    path to the file to read (default: nil)

Raises:

  • (VoiceNotFound)

    if the given voice doesn't exist or wasn't installed



81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/mac/say.rb', line 81

def initialize(voice: :alex, rate: 175, file: nil, say_path: ENV['USE_FAKE_SAY'] || '/usr/bin/say')
  @config = {
    say_path: say_path,
    voice: voice,
    rate: rate,
    file: file
  }

  @voices = nil
  load_voices

  raise VoiceNotFound, "Voice '#{voice}' isn't a valid voice" unless valid_voice? voice
end

Instance Attribute Details

#configHash (readonly)

Current config

Returns:

  • (Hash)

    a Hash with current configuration



71
72
73
# File 'lib/mac/say.rb', line 71

def config
  @config
end

#voicesArray<Hash> (readonly)

Current voices list

Examples:

Get all the voices

Mac::Say.voices #=>
   [
       {
           :name     => :agnes,
           :language => :en,
           :country  => :us,
           :sample   => "Isn't it nice to have a computer that will talk to you?",
           :gender   => :female,
           :joke     => false,
           :quality  => :low,
           :singing  => false
       },
       {
           :name      => :albert,
           :language  => :en,
           :country   => :us,
           :sample    => "I have a frog in my throat. No, I mean a real frog!",
           :gender    => :male,
           :joke      => true,
           :quality   => :medium,
           :singing   => false
       },
       ...
   ]

Returns:

  • (Array<Hash>)

    an array of voices Hashes supported by the say command



67
68
69
# File 'lib/mac/say.rb', line 67

def voices
  @voices
end

Class Method Details

.say(string, voice = :alex) ⇒ Array<String, Integer>

Read the given string with the given voice

Parameters:

  • string (String)

    a text to read using say command

  • voice (Symbol) (defaults to: :alex)

    voice to be used by the say command (default: :alex)

Returns:

  • (Array<String, Integer>)

    an array with the actual say command used and it's exit code. E.g.: ["/usr/bin/say -v 'alex' -r 175", 0]

Raises:



105
106
107
108
# File 'lib/mac/say.rb', line 105

def self.say(string, voice = :alex)
  mac = new(voice: voice.downcase.to_sym)
  mac.say(string: string)
end

.voice(attribute, value) ⇒ Array<Hash>, ... .voice {|voice| ... } ⇒ Array<Hash>, ...

Look for voices by their attributes (e.g. :name, :language, :country, :gender, etc.)

Examples:

Find voices by one or more attributes


Mac::Say.new.voice(:joke, false)
Mac::Say.new.voice(:gender, :female)

Mac::Say.new.voice { |v| v[:joke] == true && v[:gender] == :female }
Mac::Say.new.voice { |v| v[:language] == :en && v[:gender] == :male && v[:quality] == :high && v[:joke] == false }

Overloads:

  • .voice(attribute, value) ⇒ Array<Hash>, ...

    Parameters:

    • attribute (Symbol)

      the attribute to search voices by

    • value (Symbol, String)

      the value of the attribute to search voices by

  • .voice {|voice| ... } ⇒ Array<Hash>, ...

    Yields:

    • (voice)

      Passes the given block to @voices.find_all

Returns:

  • (Array<Hash>, Hash, nil)

    an array with all the voices matched by the attribute or a voice Hash if only one voice corresponds to the attribute, nil if no voices found

Raises:

  • (UnknownVoiceAttribute)

    if the voice attribute isn't supported def self.voice(attribute = nil, value = nil, &block)



167
168
169
170
171
172
173
174
175
# File 'lib/mac/say.rb', line 167

def self.voice(attribute = nil, value = nil, &block)
  mac = new

  if block_given?
    mac.voice(&block)
  else
    mac.voice(attribute, value)
  end
end

.voicesArray<Hash>

Get all the voices supported by the say command on current machine

Examples:

Get all the voices

Mac::Say.voices #=>
   [
       {
           :name      => :agnes,
           :language  => :en,
           :country   => :us,
           :sample    => "Isn't it nice to have a computer that will talk to you?",
           :gender    => :female,
           :joke      => false,
           :quality   => :low,
           :singing   => false
       },
       {
           :name      => :albert,
           :language  => :en,
           :country   => :us,
           :sample    => "I have a frog in my throat. No, I mean a real frog!",
           :gender    => :male,
           :joke      => true,
           :quality   => :medium,
           :singing   => false
       },
       ...
   ]

Returns:

  • (Array<Hash>)

    an array of voices Hashes supported by the say command



239
240
241
242
# File 'lib/mac/say.rb', line 239

def self.voices
  mac = new
  mac.voices
end

Instance Method Details

#say(string: nil, file: nil, voice: nil, rate: nil) ⇒ Array<String, Integer> Also known as: read

Read the given string/file with the given voice and rate

Providing file, voice or rate arguments changes instance state and influence all the subsequent #say calls unless they have their own custom arguments

Examples:

Say something (for more examples check README.md or examples/examples.rb files)

Mac::Say.new.say string: 'Hello world' #=> ["/usr/bin/say -v 'alex' -r 175", 0]
Mac::Say.new.say string: 'Hello world', voice: :fiona #=> ["/usr/bin/say -v 'fiona' -r 175", 0]
Mac::Say.new.say file: /tmp/text.txt, rate: 300 #=> ["/usr/bin/say -f /tmp/text.txt -v 'alex' -r 300", 0]

Parameters:

  • string (String) (defaults to: nil)

    a text to read using say command (default: nil)

  • file (String) (defaults to: nil)

    path to the file to read (default: nil)

  • voice (Symbol) (defaults to: nil)

    voice to be used by the say command (default: :alex)

  • rate (Integer) (defaults to: nil)

    speech rate in words per minute (default: 175) accepts values in (175..720)

Returns:

  • (Array<String, Integer>)

    an array with the actual say command used and it's exit code. E.g.: ["/usr/bin/say -v 'alex' -r 175", 0]

Raises:

  • (CommandNotFound)

    if the say command wasn't found

  • (VoiceNotFound)

    if the given voice doesn't exist or wasn't installed

  • (FileNotFound)

    if the given file wasn't found or isn't readable by the current user



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/mac/say.rb', line 131

def say(string: nil, file: nil, voice: nil, rate: nil)
  if voice
    raise VoiceNotFound, "Voice '#{voice}' isn't a valid voice" unless valid_voice?(voice)
    @config[:voice] = voice
  end

  if file
    raise FileNotFound, "File '#{file}' wasn't found or it's not readable by the current user" unless valid_file_path?(file)
    @config[:file] = file
  end

  @config[:rate] = rate if rate

  execute_command(string)
end

#voice(attribute, value) ⇒ Array<Hash>, ... #voice {|voice| ... } ⇒ Array<Hash>, ...

Look for voices by their attributes (e.g. :name, :language, :country, :gender, etc.)

Examples:

Find voices by one or more attributes


Mac::Say.new.voice(:joke, false)
Mac::Say.new.voice(:gender, :female)

Mac::Say.new.voice { |v| v[:joke] == true && v[:gender] == :female }
Mac::Say.new.voice { |v| v[:language] == :en && v[:gender] == :male && v[:quality] == :high && v[:joke] == false }

Overloads:

  • #voice(attribute, value) ⇒ Array<Hash>, ...

    Parameters:

    • attribute (Symbol)

      the attribute to search voices by

    • value (Symbol, String)

      the value of the attribute to search voices by

  • #voice {|voice| ... } ⇒ Array<Hash>, ...

    Yields:

    • (voice)

      Passes the given block to @voices.find_all

Returns:

  • (Array<Hash>, Hash, nil)

    an array with all the voices matched by the attribute or a voice Hash if only one voice corresponds to the attribute, nil if no voices found

Raises:



197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/mac/say.rb', line 197

def voice(attribute = nil, value = nil, &block)
  return unless (attribute && !value.nil?) || block_given?
  raise UnknownVoiceAttribute, "Voice has no '#{attribute}' attribute" if attribute && !VOICE_ATTRIBUTES.include?(attribute)

  if block_given?
    found_voices = @voices.find_all(&block)
  else
    found_voices = @voices.find_all {|voice| voice[attribute] === value }
  end

  return if found_voices.empty?
  found_voices.count == 1 ? found_voices.first : found_voices
end