acapela 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ config/
2
+ vendor/
3
+ Gemfile.lock
4
+ pkg/
5
+ tmp/
6
+ script/voice_enumerator.out
7
+ script/php_code.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour -fd
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rake'
4
+ require 'bundler'
5
+ require 'bundler/gem_tasks'
6
+ require 'rspec/core/rake_task'
7
+ require 'acapela/constants'
8
+
9
+ RSpec::Core::RakeTask.new(:spec)
10
+
11
+ namespace :generate do
12
+ desc "Generates MP3 with test strings for all voices."
13
+ task :test_voices do
14
+ exec('ruby -I lib script/test_voices.rb')
15
+ end
16
+
17
+ desc "Generate default voice class based on enumrated values from Acapela"
18
+ task :default_voices_class do
19
+ exec('ruby -I lib script/create_voices.rb')
20
+ end
21
+
22
+ desc "Generate default voice class based on PHP example voice list"
23
+ namespace :default_voices_class do
24
+ task :php do
25
+ exec('ruby -I lib script/create_voices.rb php')
26
+ end
27
+ end
28
+ end
29
+
30
+ desc "Clean up generated and downloaded files"
31
+ task :clean do
32
+ include Acapela::Scripts
33
+ FileUtils.rm_rf(LOCAL_TEMP_DIR)
34
+ FileUtils.rm(PHP_VOICE_ARRAY_FILE) rescue nil
35
+ FileUtils.rm(VOICES_ENUMERATOR_FILE) rescue nil
36
+ end
37
+
38
+ task :default => :spec
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "acapela/constants"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "acapela"
7
+ s.version = Acapela::VERSION
8
+ s.authors = ["Birkir A. Barkarson"]
9
+ s.email = ["birkirb@stoicviking.net"]
10
+ s.homepage = "https://github.com/birkirb/acapela"
11
+ s.summary = %q{Generate speech from the Acapela text to voice service.}
12
+ s.description = %q{Ruby interface to Acapela's API for generating speech from text. More info http://www.acapela-vaas.com/}
13
+
14
+ s.rubyforge_project = "acapela"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_runtime_dependency("backports")
22
+ s.add_development_dependency("rspec", '>= 2.6.0')
23
+ s.add_development_dependency("mocha", '>= 0.9.0')
24
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'acapela'
@@ -0,0 +1,27 @@
1
+ require 'acapela/error'
2
+ require 'acapela/config'
3
+ require 'acapela/voices/default'
4
+ require 'acapela/voice'
5
+ require 'acapela/constants'
6
+ require 'acapela/response'
7
+ require 'acapela/voice_service'
8
+
9
+ if RUBY_VERSION < "1.9"
10
+ require 'backports'
11
+ end
12
+
13
+ module Acapela
14
+
15
+ def self.config
16
+ @@config ||= Acapela::Config.read rescue nil
17
+ end
18
+
19
+ def self.config=(config)
20
+ if config.is_a?(Acapela::Config)
21
+ @@config = config
22
+ else
23
+ raise Error.new("Acapela configuration required. Not #{config.class}")
24
+ end
25
+ end
26
+
27
+ end
@@ -0,0 +1,38 @@
1
+ require 'yaml'
2
+ require 'uri'
3
+
4
+ module Acapela
5
+ class Config
6
+ attr_reader :application,
7
+ :version,
8
+ :environment,
9
+ :login,
10
+ :password,
11
+ :protocol,
12
+ :target_url
13
+
14
+ DEFAULT_PROTOCOL = '2'
15
+ DEFAULT_VERSION = '1-30'
16
+ DEFAULT_TARGET_URL = 'http://vaas.acapela-group.com/Services/Synthesizer'
17
+
18
+ def initialize(login, application, password, target_url = nil, version = nil, protocol = nil)
19
+ @login = login
20
+ @application = application
21
+ @password = password
22
+ @protocol = protocol || DEFAULT_PROTOCOL
23
+ @version = version || DEFAULT_VERSION
24
+ @target_url = URI.parse(target_url || DEFAULT_TARGET_URL)
25
+ end
26
+
27
+ def self.read(config_file = nil)
28
+ config_file ||= File.join('config', 'acapela.yml')
29
+ begin
30
+ yaml = YAML.load_file(config_file)
31
+ self.new(yaml['login'], yaml['application'], yaml['password'], yaml['target_url'] , yaml['version'], yaml['protocol'])
32
+ rescue => err
33
+ raise Error.new("Failed to read configuration file", err)
34
+ end
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,31 @@
1
+ require 'uri'
2
+
3
+ module Acapela
4
+
5
+ VERSION = "0.8.1"
6
+
7
+ class Voice
8
+ QUALITY_LOW = '8k'
9
+ QUALITY_HIGH = '22k'
10
+ GENDER_FEMALE = :female
11
+ GENDER_MALE = :male
12
+ end
13
+
14
+ module Scripts
15
+ LOCAL_TEMP_DIR = 'tmp'
16
+ TEST_VOICES_DIR = File.join(LOCAL_TEMP_DIR, 'voices')
17
+
18
+
19
+ PHP_VOICE_ARRAY_URL = URI.parse("http://vaas.acapela-group.com/webservices/1-34-01/publish/export/EVAL_VAAS/voices_lists/php_code.txt")
20
+ PHP_VOICE_ARRAY_FILE = File.join('script', 'php_code.txt')
21
+ PHP_ARRAY_REGEXP = /array\((.*)?\),?$/
22
+
23
+ VOICES_ENUMERATOR_URL = URI.parse("http://vaas.acapela-group.com/Services/Enumerator")
24
+ VOICES_ENUMERATOR_FILE = File.join('script', 'voice_enumerator.out')
25
+ # Enmuerator Example: leila22k=ar_SA/Arabic (Saudi Arabia)/Leila/HQ/F/PCM/22050
26
+ VOICE_ENUMERATOR_REGEXP = /(.*)?=(\w\w_\w\w)\/(.*)?\/(\w+)\/(\w+)\/(\w+)\/(\w+)\//
27
+
28
+ DEFAULT_VOICES_RUBY_FILE = File.join('lib', 'acapela', 'voices', 'default.rb')
29
+ end
30
+
31
+ end
@@ -0,0 +1,33 @@
1
+ module Acapela
2
+
3
+ class Error < StandardError
4
+ attr_accessor :original_error
5
+
6
+ def initialize(message, original_error = nil)
7
+ super(message)
8
+ @original_error = original_error
9
+ end
10
+
11
+ def to_s
12
+ if @original_error.nil?
13
+ super
14
+ else
15
+ "#{super}\nCause: #{@original_error.to_s}"
16
+ end
17
+ end
18
+ end
19
+
20
+ class ServiceError < Error
21
+ attr_accessor :code
22
+
23
+ def initialize(message, code = '')
24
+ super(message)
25
+ @code = code
26
+ end
27
+
28
+ def to_s
29
+ "Code: #{code}, Message: #{super}"
30
+ end
31
+ end
32
+
33
+ end
@@ -0,0 +1,5 @@
1
+ require 'acapela/mocks/constants'
2
+ require 'acapela/mocks/voice_service'
3
+ require 'acapela/mocks/response'
4
+
5
+ Acapela::Response.mock_on
@@ -0,0 +1,11 @@
1
+ module Acapela
2
+ module Mocks
3
+
4
+ RESPONSE_TEST_FILE = File.join(File.dirname(__FILE__), 'test_file.mp3')
5
+
6
+ EXAMPLE_ACAPELA_RESPONSE_ACCESS_DENIED = "res=NOK&err_code=ACCESS_DENIED_ERROR&err_msg=Invalid%20identifiers&w=&create_echo="
7
+ EXAMPLE_ACAPELA_RESPONSE_INVALID_PARAM = "res=NOK&err_code=INVALID_PARAM_ERROR&err_msg=This%20voice%20is%20not%20available&w=&create_echo="
8
+ EXAMPLE_ACAPELA_RESPONSE_OK = "w=&snd_time=1407.94&get_count=0&snd_id=210375264_cacefb1f8f862&asw_pos_init_offset=0&asw_pos_text_offset=0&snd_url=http://vaas.acapela-group.com/MESSAGES/009086065076095086065065083/EVAL_ACCOUNT/sounds/210375264_cacefb1f8f862.mp3&snd_size=9098&res=OK&create_echo="
9
+
10
+ end
11
+ end
@@ -0,0 +1,29 @@
1
+ module Acapela
2
+ class Response
3
+
4
+ def self.mock_on
5
+ self.class_eval(<<-EVAL, __FILE__, __LINE__)
6
+ def fetch_file_from_url
7
+ fetch_file_from_url_with_mock
8
+ end
9
+ EVAL
10
+ end
11
+
12
+ def self.mock_off
13
+ self.class_eval(<<-EVAL, __FILE__, __LINE__)
14
+ def fetch_file_from_url
15
+ fetch_file_from_url_without_mock
16
+ end
17
+ EVAL
18
+ end
19
+
20
+ private
21
+
22
+ alias :fetch_file_from_url_without_mock :fetch_file_from_url
23
+
24
+ def fetch_file_from_url_with_mock
25
+ File.read(Acapela::Mocks::RESPONSE_TEST_FILE)
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,36 @@
1
+ module Acapela
2
+ class VoiceService
3
+ include Acapela::Mocks
4
+
5
+ @@expected_response = EXAMPLE_ACAPELA_RESPONSE_OK
6
+ @@last_posted_params = nil
7
+
8
+ def self.expected_response
9
+ @@expected_response
10
+ end
11
+
12
+ def self.last_posted_params
13
+ @@last_posted_params
14
+ end
15
+
16
+ def self.expect_ok_response
17
+ @@expected_response = EXAMPLE_ACAPELA_RESPONSE_OK
18
+ end
19
+
20
+ def self.expect_invalid_param_response
21
+ @@expected_response = EXAMPLE_ACAPELA_RESPONSE_INVALID_PARAM
22
+ end
23
+
24
+ def self.expect_access_denied_response
25
+ @@expected_response = EXAMPLE_ACAPELA_RESPONSE_ACCESS_DENIED
26
+ end
27
+
28
+ private
29
+
30
+ def post(params)
31
+ @@last_posted_params = params
32
+ self.class.expected_response
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,64 @@
1
+ require 'cgi'
2
+ require 'net/http'
3
+ require 'tempfile'
4
+
5
+ module Acapela
6
+ class Response
7
+
8
+ attr_reader :id,
9
+ :time,
10
+ :size,
11
+ :url
12
+
13
+ RESPONSE_STATE = 'res'
14
+ RESPONSE_STATE_REGEXP = /#{RESPONSE_STATE}=/
15
+ RESPONSE_SOUND_URL = 'snd_url'
16
+ RESPONSE_STATE_OK = 'OK'
17
+ RESPONSE_ERROR_MESSAGE = 'err_msg'
18
+ RESPONSE_ERROR_CODE = 'err_code'
19
+
20
+ ERROR_UNEXPECTED_RESPONSE = ServiceError.new("Unexpected response.")
21
+ ERROR_MISSING_SOUND_URL = ServiceError.new("Parameters are missing sound url.")
22
+
23
+ def initialize(response)
24
+ if RESPONSE_STATE_REGEXP.match(response)
25
+ params = CGI::parse(response)
26
+ else
27
+ raise ERROR_UNEXPECTED_RESPONSE
28
+ end
29
+
30
+ if RESPONSE_STATE_OK == params[RESPONSE_STATE].first
31
+ if url = params[RESPONSE_SOUND_URL]
32
+ @url = URI.parse(url.first)
33
+ @id = params['snd_id'].first
34
+ @time = params['snd_time'].first.to_f
35
+ @size = params['snd_size'].first.to_i
36
+ else
37
+ raise ERROR_MISSING_SOUND_URL
38
+ end
39
+ else
40
+ raise ServiceError.new(params[RESPONSE_ERROR_MESSAGE].first, params[RESPONSE_ERROR_CODE].first)
41
+ end
42
+ end
43
+
44
+ def download_to_tempfile
45
+ content = fetch_file_from_url
46
+ file = Tempfile.new(self.id)
47
+ file.write(content)
48
+ file.flush
49
+ file # Leaving open. Will be closed once object is finalized.
50
+ end
51
+
52
+ private
53
+
54
+ def fetch_file_from_url
55
+ response = Net::HTTP.get_response(self.url)
56
+ if 200 == response.code.to_i
57
+ response.body
58
+ else
59
+ raise Error.new("Download failed with status: #{response.code}.")
60
+ end
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,121 @@
1
+ require 'set'
2
+
3
+ module Acapela
4
+ class Voice
5
+ attr_reader :languages, :speaker, :gender, :quality
6
+
7
+ def initialize(speaker, gender, *languages)
8
+ @speaker = speaker
9
+ @gender = gender
10
+ @quality = QUALITY_HIGH
11
+
12
+ @languages = Set.new
13
+ languages.each { |lang| @languages.add(lang) }
14
+ end
15
+
16
+ def id
17
+ "#{speaker.downcase}#{quality}"
18
+ end
19
+
20
+ def language
21
+ languages.first
22
+ end
23
+
24
+ def low_quality!
25
+ @quality = QUALITY_LOW
26
+ end
27
+
28
+ def high_quality!
29
+ @quality = QUALITY_HIGH
30
+ end
31
+
32
+ def self.override_map(override_voice_map = {})
33
+ current_map = self.map
34
+ override_voice_map.each do |language, voices|
35
+ language_sym = language.to_sym
36
+ if voices.nil?
37
+ # Remove voice directive.
38
+ current_map.delete(language_sym)
39
+ elsif voices.is_a?(String) || voices.is_a?(Symbol)
40
+ # Map voice directive.
41
+ current_map[language_sym] = self.map[voices]
42
+ elsif voices.is_a?(Hash)
43
+ # Replace/Override
44
+ if entry = self.map[language_sym]
45
+ new_entry = entry.dup
46
+ else
47
+ new_entry = Hash.new
48
+ end
49
+
50
+ voices.each do |gender, speakers|
51
+ if gender.is_a?(Symbol) && speakers.is_a?(Array)
52
+ new_entry[gender] = speakers
53
+ end
54
+ end
55
+ current_map[language_sym] = new_entry
56
+ end
57
+ end
58
+ @@map = current_map
59
+ end
60
+
61
+ def self.map
62
+ reset_map unless defined?(@@map)
63
+ @@map
64
+ end
65
+
66
+ def self.reset_map
67
+ @@map = Voices::PER_LANGUAGE.dup
68
+ end
69
+
70
+ def self.extract_from_options(options = {})
71
+ if speaker = options[:speaker]
72
+ named_voice(speaker)
73
+ else
74
+ language = options[:language] || :en
75
+ gender = options[:gender]
76
+ speakers_for_language = self.map[language]
77
+
78
+ speakers = case gender
79
+ when GENDER_FEMALE
80
+ speakers_for_language[GENDER_FEMALE]
81
+ when GENDER_MALE
82
+ speakers_for_language[GENDER_MALE]
83
+ else
84
+ speakers_for_language[GENDER_FEMALE] +
85
+ speakers_for_language[GENDER_MALE]
86
+ end
87
+
88
+ if speakers.empty?
89
+ nil
90
+ else
91
+ named_voice(speakers.sample)
92
+ end
93
+ end
94
+ end
95
+
96
+ def self.named_voice(speaker)
97
+ unless defined?(@@voices)
98
+ @@voices = Hash.new
99
+ self.map.each do |language, voices|
100
+ [GENDER_MALE, GENDER_FEMALE].each do |gender|
101
+ voices[gender].each do |name|
102
+ downcase_name = name.downcase.to_sym
103
+ if @@voices[downcase_name].nil?
104
+ @@voices[downcase_name] = Voice.new(name, gender, language.to_s)
105
+ else
106
+ @@voices[downcase_name].languages.add(language.to_s)
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ if info = @@voices[speaker.to_s.downcase.to_sym]
114
+ info
115
+ else
116
+ raise Error.new("Voice does not exist.")
117
+ end
118
+ end
119
+
120
+ end
121
+ end
@@ -0,0 +1,84 @@
1
+ require 'net/http'
2
+
3
+ module Acapela
4
+ class VoiceService
5
+
6
+ CLIENT_ENVIRONMENT = "RUBY_#{RUBY_VERSION}"
7
+
8
+ ERROR_MISSING_CONFIG = Error.new("VoiceService requires configuration.")
9
+
10
+ attr_reader :config
11
+
12
+ def initialize(config = Acapela.config)
13
+ if config.is_a?(Acapela::Config)
14
+ @config = config
15
+ else
16
+ raise ERROR_MISSING_CONFIG
17
+ end
18
+ end
19
+
20
+ def generate_sound(text, options = {})
21
+ voice = Voice.extract_from_options(options)
22
+
23
+ case options[:quality]
24
+ when :low
25
+ voice.low_quality!
26
+ when :high
27
+ voice.high_quality!
28
+ end
29
+
30
+ generate_with_voice(text, voice)
31
+ end
32
+
33
+ def generate_with_voice(text, voice)
34
+ params = {
35
+ :prot_vers => @config.protocol,
36
+ :cl_env => CLIENT_ENVIRONMENT,
37
+ :cl_vers => @config.version,
38
+ :cl_login => @config.login,
39
+ :cl_app => @config.application,
40
+ :cl_pwd => @config.password,
41
+ :req_type => 'NEW',
42
+ #:req_snd_id => nil,
43
+ :req_voice => voice.id,
44
+ :req_text => text,
45
+ #:req_vol => nil, # Volume: min = 50, default = 32768, max = 65535
46
+ #:req_spd => nil, # Speed: min = 60, default = 180, max = 360
47
+ #:req_vct => nil, # Shaping: min = 50, default = 100, max = 150
48
+ # Equalizer: min = -100, default = 0, max = 100
49
+ #:req_eq1 => nil, # Band 275Hz
50
+ #:req_eq2 => nil, # Band: 2.2kHz
51
+ #:req_eq3 => nil, # Band: 5kHz
52
+ #:req_eq4 => nil, # Band: 8.3kHz
53
+ #:req_snd_type => 'MP3', # Sound file type: MP3, WAV, RAW
54
+ #:req_snd_ext => '.mp3', # Sound file extentions: .mp3 .wav .raw
55
+ #:req_snd_kbps => 'CBR_48', # Variable bit rate VBR_5 to VRB_9 (5 = max quality, 9 min) or Constant Bit Rate CBR_8,16,32,48
56
+ #:req_alt_snd_type => 'MP3', # Alternative Sound file type: MP3, WAV, RAW
57
+ #:req_alt_snd_ext => '.mp3', # Alternative Sound file extentions: .mp3 .wav .raw
58
+ #:req_wp => nil, # 'ON' to receive word file URL
59
+ #:req_bp => nil, # 'ON' to receive bookmark file URL
60
+ #:req_mp => nil, # 'ON' to receive mouth file URL
61
+ #:req_comment => '', Information to store about the operation.
62
+ #:req_start_time => nil, # The start time of the request will be used to calculate the deadline for request treatment
63
+ #:req_timeout => nil, # The time allocated to request treatment in seconds.
64
+ #:req_asw_type => 'SOUND', # Type of response:
65
+ # "INFO", key/value params.
66
+ # "SOUND", sound bytes once genearted.
67
+ # "STREAM", bytes as they are generated.
68
+ #:req_asw_as_alt_snd => 'no', # Receive alternative file as response.
69
+ #:req_err_as_id3 => 'no', # Reveive errors encapsulated in ID3 tag of MP3.
70
+ #:req_echo => '', # Receive some creation request fields in response.
71
+ #:req_asw_redirect_url => nil, # Redirect response to a different URL.
72
+ }
73
+
74
+ Response.new(post(params))
75
+ end
76
+
77
+ private
78
+
79
+ def post(params)
80
+ body = Net::HTTP.post_form(@config.target_url, params).body
81
+ end
82
+
83
+ end
84
+ end
@@ -0,0 +1,40 @@
1
+ module Acapela
2
+ module Voices
3
+ PER_LANGUAGE = {
4
+ :ar => {:female=>["Leila", "Salma"], :male=>["Mehdi", "Nizar"]},
5
+ :ca => {:female=>["Laia"], :male=>[]},
6
+ :cs => {:female=>["Eliska"], :male=>[]},
7
+ :da => {:female=>["Mette"], :male=>["Rasmus"]},
8
+ :de => {:female=>["Julia", "Sarah"], :male=>["Andreas", "Klaus"]},
9
+ :el => {:female=>[], :male=>["Dimitris"]},
10
+ :en => {:female=>["Deepa", "Lucy", "QueenElizabeth", "Rachel", "Heather", "Laura", "Nelly", "Tracy"], :male=>["Graham", "Nizareng", "Peter", "Kenny", "Micah", "Ryan", "Saul"]},
11
+ :en_GB => {:female=>["Lucy", "QueenElizabeth", "Rachel"], :male=>["Graham", "Nizareng", "Peter"]},
12
+ :en_IN => {:female=>["Deepa"], :male=>[]},
13
+ :en_US => {:female=>["Heather", "Laura", "Nelly", "Tracy"], :male=>["Kenny", "Micah", "Ryan", "Saul"]},
14
+ :es => {:female=>["Ines", "Maria", "Rosa"], :male=>["Antonio"]},
15
+ :es_ES => {:female=>["Ines", "Maria"], :male=>["Antonio"]},
16
+ :es_US => {:female=>["Rosa"], :male=>[]},
17
+ :fi => {:female=>["Sanna"], :male=>[]},
18
+ :fr => {:female=>["Justine", "Louise", "Alice", "Claire", "Julie", "Margaux"], :male=>["Antoine", "Bruno"]},
19
+ :fr_BE => {:female=>["Justine"], :male=>[]},
20
+ :fr_CA => {:female=>["Louise"], :male=>[]},
21
+ :fr_FR => {:female=>["Alice", "Claire", "Julie", "Margaux"], :male=>["Antoine", "Bruno"]},
22
+ :gb => {:female=>[], :male=>["Kal"]},
23
+ :it => {:female=>["Chiara", "Fabiana", "chiara", "fabiana"], :male=>["Vittorio", "vittorio"]},
24
+ :nl => {:female=>["Sofie", "Zoe", "Femke", "Jasmijn"], :male=>["Jeroen", "Daan", "Max"]},
25
+ :nl_BE => {:female=>["Sofie", "Zoe"], :male=>["Jeroen"]},
26
+ :nl_NL => {:female=>["Femke", "Jasmijn"], :male=>["Daan", "Max"]},
27
+ :no => {:female=>["Bente", "Kari"], :male=>["Olav"]},
28
+ :pl => {:female=>["Ania"], :male=>[]},
29
+ :pt => {:female=>["Marcia", "Celia"], :male=>[]},
30
+ :pt_BR => {:female=>["Marcia"], :male=>[]},
31
+ :pt_PT => {:female=>["Celia"], :male=>[]},
32
+ :ru => {:female=>["Alyona"], :male=>[]},
33
+ :sc => {:female=>["Mia"], :male=>[]},
34
+ :sv => {:female=>["Elin", "Emma"], :male=>["Emil", "Erik", "Samuel"]},
35
+ :sv_FI => {:female=>[], :male=>["Samuel"]},
36
+ :sv_SE => {:female=>["Elin", "Emma"], :male=>["Emil", "Erik"]},
37
+ :tr => {:female=>["Ipek"], :male=>[]}
38
+ }
39
+ end
40
+ end
@@ -0,0 +1,137 @@
1
+ require 'net/http'
2
+ require 'set'
3
+ require 'fileutils'
4
+ require 'acapela/constants'
5
+
6
+ include Acapela::Scripts
7
+
8
+ def save_uri_to_file_if_missing(uri, filename)
9
+ if !File.exists?(filename)
10
+ response = Net::HTTP.get_response(uri)
11
+
12
+ if 200 == response.code.to_i
13
+ voice_hash = Hash.new
14
+ File.open(filename,'w') do |f|
15
+ f.write(response.body)
16
+ end
17
+ else
18
+ puts "Download failed with: #{response.code}\nBody#{response.body}"
19
+ exit 1
20
+ end
21
+ end
22
+ end
23
+
24
+ def php_array_to_ruby_array_of_hashes(text)
25
+ voices = Array.new
26
+ text.lines do |line|
27
+ if match = PHP_ARRAY_REGEXP.match(line)
28
+ line_hash = eval("{#{match[1]}}")
29
+ voices << line_hash
30
+ end
31
+ end
32
+ voices
33
+ end
34
+
35
+ def enumerator_text_to_ruby_array_of_hashes(text)
36
+ voices = Array.new
37
+ text.lines do |line|
38
+ if match = VOICE_ENUMERATOR_REGEXP.match(line)
39
+ voices << {'language_locale' => match[2], 'speaker' => match[4], 'gender' => match[6]}
40
+ end
41
+ end
42
+ voices
43
+ end
44
+
45
+ def gender_code_to_symbol(gender)
46
+ case gender
47
+ when 'F'
48
+ Acapela::Voice::GENDER_FEMALE
49
+ when 'M'
50
+ Acapela::Voice::GENDER_MALE
51
+ else
52
+ raise "Unknown gender code: #{gender}"
53
+ end
54
+ end
55
+
56
+ def simple_language_code(locale)
57
+ locale.split('_').first
58
+ end
59
+
60
+ class Set
61
+ def inspect
62
+ self.to_a.inspect
63
+ end
64
+ end
65
+
66
+ def voice_array_to_voices_per_language(voices)
67
+ language_voices = Hash.new { |h,v| h[v] = { :female => Set.new, :male => Set.new } }
68
+
69
+ voices.each do |voice_hash|
70
+ gender = gender_code_to_symbol(voice_hash['gender'])
71
+ locale = voice_hash['language_locale']
72
+ simple_locale = simple_language_code(locale)
73
+
74
+ language_voices[locale][gender].add(voice_hash['speaker'])
75
+ language_voices[simple_locale][gender].add(voice_hash['speaker'])
76
+ end
77
+
78
+ # Clean up instance where specific locale is same as generic one.
79
+ language_voices.delete_if do |key,value|
80
+ simple_locale = simple_language_code(key)
81
+ if key != simple_locale && language_voices[simple_locale] == value
82
+ true
83
+ else
84
+ false
85
+ end
86
+ end
87
+
88
+ language_voices
89
+ end
90
+
91
+ def construct_class(voices_per_language)
92
+ text_hashes = voices_per_language.keys.sort.map do |key|
93
+ "".rjust(6) + ":" + key.to_s.ljust(8) + "=>".ljust(4) + voices_per_language[key].inspect
94
+ end
95
+
96
+ klass_text = <<-CLASS
97
+ module Acapela
98
+ module Voices
99
+ PER_LANGUAGE = {
100
+ #{text_hashes.join(",\n")}
101
+ }
102
+ end
103
+ end
104
+ CLASS
105
+ klass_text
106
+ end
107
+
108
+ def write_klass(klass)
109
+ klass_dir = File.dirname(DEFAULT_VOICES_RUBY_FILE)
110
+
111
+ if !File.exist?(klass_dir)
112
+ FileUtils.mkdir_p(klass_dir)
113
+ end
114
+
115
+ File.open(DEFAULT_VOICES_RUBY_FILE, 'w') do |f|
116
+ f.write(klass)
117
+ end
118
+ end
119
+
120
+ def main
121
+ if ARGV[0] == 'php'
122
+ save_uri_to_file_if_missing(PHP_VOICE_ARRAY_URL, PHP_VOICE_ARRAY_FILE)
123
+ voices = php_array_to_ruby_array_of_hashes(File.read(PHP_VOICE_ARRAY_FILE))
124
+ else
125
+ save_uri_to_file_if_missing(VOICES_ENUMERATOR_URL, VOICES_ENUMERATOR_FILE)
126
+ voices = enumerator_text_to_ruby_array_of_hashes(File.read(VOICES_ENUMERATOR_FILE))
127
+ end
128
+
129
+ voices_per_language = voice_array_to_voices_per_language(voices)
130
+ klass = construct_class(voices_per_language)
131
+ write_klass(klass)
132
+ puts "Done."
133
+ end
134
+
135
+ if $0 == __FILE__
136
+ main
137
+ end
@@ -0,0 +1,77 @@
1
+ # Encoding: UTF-8
2
+
3
+ trap("SIGINT") do
4
+ puts "Stopping..."
5
+ @interrupted = true
6
+ end
7
+
8
+ SAMPLE_SENTENCES = {
9
+ :ar => "اسمي {name} وأنا أتكلم يمكن.", # Arabic (Saudi Arabia)
10
+ :ca => "El meu nom és {name} i que pot parlar.", # Catalan (Spain)
11
+ :cs => "Mé jméno je {name} a mohu mluvit.", # Czech
12
+ :da => "Mit navn er {name}, og jeg kan tale.", # Danish
13
+ :de => "Mein Name ist {name}, und ich kann sprechen.", # German
14
+ :el => "Το όνομά μου είναι {name}, και μπορώ να μιλήσω.", # Greek
15
+ :en => "My name is {name} and I can talk.", # English
16
+ :en_GB => "My name is {name} and I live in Great Britain.", # English (Great Britain)
17
+ :en_IN => "My name is {name} and I live in India.", # English (India)
18
+ :en_US => "My name is {name} and I live in the United States.", # English (US)
19
+ :es => "Mi nombre es {name}, y puede hablar.", # Spanish
20
+ :es_ES => "Mi nombre es {name} y vivo en España.", # Spanish (Spain)
21
+ :es_US => "Mi nombre es {name} y yo vivimos en Estados Unidos.", # Spanish (US?)
22
+ :fi => "Nimeni on {name}, ja voin puhua.", # Finnish
23
+ :fr => "Mon nom est {name}, et je peux parler.", # French
24
+ :fr_BE => "Mon nom est {name} et je vis en Belgique.", # French (Belgium)
25
+ :fr_CA => "Mon nom est {name} et je vis au Canada.", # French (Canada)
26
+ :fr_FR => "Mon nom est {name} et je vis en France.", # French (French)
27
+ :gb => "Mitt namn är {name} och jag bor i Gotenburg.", # Gotenburg (Swedish), should be sv_SV_gotenburg ?
28
+ :it => "Il mio nome è {name}, e posso parlare.", # Italian
29
+ :ja => "私の名前は{name}です。私は話せます。", # Japanese
30
+ :nl => "Mijn naam is {name}, en ik spreek kan.", # Dutch
31
+ :nl_BE => "Mijn naam is {name} en ik woon in België.", # Dutch (Belgium)
32
+ :nl_NL => "Mijn naam is {name} en ik woon in Nederland.", # Dutch (Netherlands)
33
+ :no => "Mitt navn er {name}, og jeg kan snakke.", # Norwegian
34
+ :pl => "Nazywam się {name}, i mogę mówić.", # Polish
35
+ :pt => "Meu nome é {name}, e eu posso falar.", # Portuguese
36
+ :pt_BR => "Meu nome é {name} e eu vivo no Brasil.", # Portuguese (Brazil)
37
+ :pt_PT => "Meu nome é {name} e eu vivo em Portugal.", # Portuguese (Portugal)
38
+ :ru => "Меня зовут {name}, и я могу сказать.", # Russia
39
+ :sc => "Mitt namn är {name} och jag bor i Scania.", # Scanian (Sweden), should be sv_SE_scania
40
+ :sv => "Mitt namn är {name}, och jag kan tala.", # Swedish
41
+ :sv_FI => "Mitt namn är {name} och jag bor i Finland.", # Swedish (Finland)
42
+ :sv_SE => "Mitt namn är {name} och jag bor i Sverige.", # Swedish (Sweden)
43
+ :tr => "Benim adım {name} ve konuşamıyorum.", # Turkish
44
+ :zh => "我的名字是{name},我可以說。", # Chinese
45
+ }
46
+
47
+ require 'acapela'
48
+ include Acapela::Scripts
49
+
50
+ service = Acapela::VoiceService.new(Acapela.config)
51
+ FileUtils.mkdir_p(TEST_VOICES_DIR)
52
+
53
+ Acapela::Voices::PER_LANGUAGE.each do |language, voice_gender|
54
+ voice_gender.each do |gender, speakers|
55
+ speakers.each do |speaker|
56
+ begin
57
+ if text = SAMPLE_SENTENCES[language.to_sym]
58
+ text = text.gsub('{name}', speaker)
59
+ destination_file = File.join(TEST_VOICES_DIR, "#{language}_#{speaker}.mp3")
60
+ if !File.exists?(destination_file)
61
+ response = service.generate_sound(text, :speaker => speaker)
62
+ mp3_file = response.download_to_tempfile
63
+ FileUtils.cp(mp3_file.path, destination_file)
64
+ mp3_file.close!
65
+ puts "Generated: #{text}"
66
+ end
67
+ else
68
+ puts "No text for: #{speaker} in #{language}"
69
+ end
70
+ rescue => err
71
+ puts "FAILED WITH: #{language}, #{gender}, #{speakers.inspect}, #{speaker}\nReason: #{err.message}"
72
+ end
73
+
74
+ exit 0 if @interrupted
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe Acapela::Config, 'When created' do
4
+
5
+ default_config = YAML.load_file('config/acapela.yml')
6
+ default_config['login'].should_not == nil
7
+ default_config['application'].should_not == nil
8
+ default_config['password'].should_not == nil
9
+
10
+ context 'with a given login, application and password' do
11
+ it 'should report those and other default values' do
12
+ config = Acapela::Config.new(default_config['login'], default_config['application'], default_config['password'])
13
+ config.version.should == Acapela::Config::DEFAULT_VERSION
14
+ config.protocol.should == Acapela::Config::DEFAULT_PROTOCOL
15
+ config.target_url.should == URI.parse(Acapela::Config::DEFAULT_TARGET_URL)
16
+ config.environment.should be_nil
17
+
18
+ config.login.should == default_config['login']
19
+ config.application.should == default_config['application']
20
+ end
21
+ end
22
+
23
+ context 'from a config file' do
24
+ it 'should default to the local config/acapela.yml' do
25
+ config = Acapela::Config.read
26
+ config.login.should == default_config['login']
27
+ config.application.should == default_config['application']
28
+ end
29
+
30
+ it 'should accept an arbitary config file argument' do
31
+ config = Acapela::Config.read(test_config)
32
+ config.login.should_not be_nil
33
+ config.application.should_not be_nil
34
+
35
+ config.login.should_not == default_config['login']
36
+ config.application.should_not == default_config['application']
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+ require 'cgi'
3
+
4
+ describe Acapela::Response do
5
+
6
+ after(:each) do
7
+ Acapela::Response.mock_on
8
+ end
9
+
10
+ it 'should throw an error when created with bogus parameters' do
11
+ expect do
12
+ Acapela::Response.new('stuff=yeah')
13
+ end.to raise_error(Acapela::Error, Acapela::Response::ERROR_UNEXPECTED_RESPONSE.message)
14
+ end
15
+
16
+ it 'should report sound related stats' do
17
+ response = Acapela::Response.new(Acapela::Mocks::EXAMPLE_ACAPELA_RESPONSE_OK)
18
+ response.url.should be_kind_of(URI)
19
+ response.time.should be_kind_of(Float)
20
+ response.size.should be_kind_of(Integer)
21
+ response.id.should be_kind_of(String)
22
+ response.time.should == 1407.94
23
+ response.size.should == 9098
24
+ response.id.should == '210375264_cacefb1f8f862'
25
+ end
26
+
27
+ it 'should download and return a tempfile with the object from the response url' do
28
+ response = Acapela::Response.new(Acapela::Mocks::EXAMPLE_ACAPELA_RESPONSE_OK)
29
+
30
+ file = response.download_to_tempfile
31
+ file.should be_kind_of(Tempfile)
32
+ file.rewind
33
+ contents = file.read
34
+ file.close
35
+ contents.should == File.read(Acapela::Mocks::RESPONSE_TEST_FILE)
36
+ end
37
+
38
+ it 'should raise an error when file download fails with non 200 response' do
39
+ Acapela::Response.mock_off # Returns the test file contents.
40
+ response = Acapela::Response.new(Acapela::Mocks::EXAMPLE_ACAPELA_RESPONSE_OK)
41
+ Net::HTTP.expects(:get_response).returns(Net::HTTPNotFound.new("Body?", 404, "Something went wrong."))
42
+ expect do
43
+ response.download_to_tempfile
44
+ end.to raise_error(Acapela::Error)
45
+ end
46
+
47
+ end
@@ -0,0 +1,3 @@
1
+ login: 'TEST'
2
+ application: 'TEST_APP'
3
+ password: 'TEST_PWD'
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+
3
+ describe Acapela::VoiceService do
4
+
5
+ text_string = 'This is a test'
6
+ mock_service = true
7
+ if mock_service
8
+ require 'acapela/mocks/voice_service'
9
+
10
+ after(:each) do
11
+ Acapela::VoiceService.expect_ok_response
12
+ end
13
+ end
14
+
15
+ context 'When create without parameters' do
16
+ service = Acapela::VoiceService.new
17
+
18
+ it 'should use the default config' do
19
+ service.config.should be_kind_of(Acapela::Config)
20
+ service.config.should == Acapela.config
21
+ end
22
+
23
+ it 'should allow generation of MP3 sound' do
24
+ sound_url = service.generate_sound(text_string, :language => :en)
25
+ sound_url.should be_kind_of(Acapela::Response)
26
+ end
27
+ end
28
+
29
+ it 'should throw an error when created with nil config' do
30
+ expect do
31
+ service = Acapela::VoiceService.new(nil)
32
+ end.to raise_error(Acapela::Error, Acapela::VoiceService::ERROR_MISSING_CONFIG.message)
33
+ end
34
+
35
+ context 'When created with config with invalid login/password' do
36
+ service = Acapela::VoiceService.new(Acapela::Config.read(test_config))
37
+
38
+ it 'should raise the reported access error' do
39
+ Acapela::VoiceService.expect_access_denied_response if mock_service
40
+ expect do
41
+ service.generate_sound(text_string, :language => :en)
42
+ end.to raise_error(Acapela::ServiceError, "Code: ACCESS_DENIED_ERROR, Message: Invalid identifiers")
43
+ end
44
+ end
45
+
46
+ context 'When generating sound' do
47
+ it 'should allow low quality setting' do
48
+ service = Acapela::VoiceService.new
49
+ if mock_service
50
+ service.generate_sound(text_string, :speaker => 'tracy', :quality => :low)
51
+ Acapela::VoiceService.last_posted_params[:req_voice].should == 'tracy8k'
52
+ else
53
+ pending("Without mock, the returned file quality needs to be confirmed")
54
+ end
55
+ end
56
+
57
+ it 'should allow custom voice setting' do
58
+ service = Acapela::VoiceService.new
59
+ if mock_service
60
+ voice = Acapela::Voice.new('johnny', :male, 'en')
61
+ service.generate_with_voice(text_string, voice)
62
+ Acapela::VoiceService.last_posted_params[:req_voice].should == 'johnny22k'
63
+ else
64
+ pending("Without mock, the returned file quality needs to be confirmed")
65
+ end
66
+ end
67
+ end
68
+
69
+ end
@@ -0,0 +1,128 @@
1
+ require 'spec_helper'
2
+
3
+ describe Acapela::Voice do
4
+
5
+ context 'When created' do
6
+ speaker_name = 'tom'
7
+ test_voice = Acapela::Voice.new(speaker_name, Acapela::Voice::GENDER_FEMALE)
8
+
9
+ it 'with name and gender should report default values' do
10
+ test_voice.speaker.should == speaker_name
11
+ test_voice.gender.should == Acapela::Voice::GENDER_FEMALE
12
+ test_voice.quality.should == Acapela::Voice::QUALITY_HIGH
13
+ test_voice.languages.should be_kind_of(Set)
14
+ test_voice.languages.should be_empty
15
+ test_voice.language.should be_nil
16
+ test_voice.id.should == "#{speaker_name}#{test_voice.quality}"
17
+ end
18
+
19
+ it 'should allow flipping quality setting' do
20
+ test_voice.low_quality!
21
+ test_voice.quality.should == Acapela::Voice::QUALITY_LOW
22
+ test_voice.high_quality!
23
+ test_voice.quality.should == Acapela::Voice::QUALITY_HIGH
24
+ end
25
+ end
26
+
27
+ context 'With a set of defined voices' do
28
+ it 'should throw an error if the voice does not exist' do
29
+ expect do
30
+ Acapela::Voice.named_voice('Birkir')
31
+ end.to raise_error(Acapela::Error, "Voice does not exist.")
32
+ end
33
+
34
+ it 'should return the active voice map' do
35
+ Acapela::Voice.map.should == Acapela::Voices::PER_LANGUAGE
36
+ end
37
+
38
+ context 'should find a voice' do
39
+ it 'given an existing speaker name' do
40
+ voice = Acapela::Voice.named_voice('Tracy')
41
+ voice.speaker.should == 'Tracy'
42
+ voice.language.should == 'en'
43
+ voice.gender.should == Acapela::Voice::GENDER_FEMALE
44
+ voice.languages.to_a.should == ['en']
45
+ voice.id.should == 'tracy22k'
46
+ end
47
+
48
+ it 'given an existing speaker name in lower case' do
49
+ voice = Acapela::Voice.named_voice('bruno')
50
+ voice.speaker.should == 'Bruno'
51
+ voice.gender.should == Acapela::Voice::GENDER_MALE
52
+ voice.language.should == 'fr'
53
+ end
54
+
55
+ it 'given an existing speaker name as a symbol' do
56
+ voice = Acapela::Voice.named_voice(:rosa)
57
+ voice.speaker.should == 'Rosa'
58
+ voice.gender.should == Acapela::Voice::GENDER_FEMALE
59
+ voice.language.should == 'es'
60
+ end
61
+ end
62
+
63
+ context 'should extract a voice when given an option that' do
64
+ it 'specifies nothing, defaulting to English' do
65
+ voice = Acapela::Voice.extract_from_options
66
+ ['Tracy', 'Heather', 'Kenny'].should include(voice.speaker)
67
+ end
68
+
69
+ it 'specifies a speaker' do
70
+ voice = Acapela::Voice.extract_from_options(:speaker => 'antoine')
71
+ voice.speaker.should == 'Antoine'
72
+ voice.gender.should == Acapela::Voice::GENDER_MALE
73
+ voice.language.should == 'fr'
74
+ end
75
+
76
+ it 'specifies a language' do
77
+ voice = Acapela::Voice.extract_from_options(:language => :es)
78
+ voice.speaker.should == 'Rosa'
79
+ end
80
+
81
+ it 'specifies a language and no gender' do
82
+ voice = Acapela::Voice.extract_from_options(:language => :fr)
83
+ voice.should_not be_nil
84
+ ['Antoine', 'Bruno'].should include(voice.speaker)
85
+ end
86
+
87
+ it 'specifies a language and gender' do
88
+ voice = Acapela::Voice.extract_from_options(:language => :es, :gender => Acapela::Voice::GENDER_FEMALE)
89
+ voice.speaker.should == 'Rosa'
90
+
91
+ voice = Acapela::Voice.extract_from_options(:language => :es, :gender => Acapela::Voice::GENDER_MALE)
92
+ voice.should be_nil
93
+ end
94
+ end
95
+ end
96
+
97
+ context 'Allows for voices to be overriden or changed' do
98
+ after(:each) do
99
+ Acapela::Voice.reset_map
100
+ end
101
+
102
+ it 'should allow removing specific entries' do
103
+ Acapela::Voice.map[:en][:male].first.should == 'Kenny'
104
+ Acapela::Voice.override_map(:en => nil)
105
+ Acapela::Voice.map[:en].should be_nil
106
+ end
107
+
108
+ it 'should allow mapping one entry to another' do
109
+ Acapela::Voice.map[:en][:male].first.should == 'Kenny'
110
+ Acapela::Voice.override_map(:en => :fr)
111
+ Acapela::Voice.map[:en][:male].first.should == 'Antoine'
112
+ end
113
+
114
+ it 'should allow replacing an entry' do
115
+ Acapela::Voice.map[:en][:male].first.should == 'Kenny'
116
+ Acapela::Voice.override_map(:en => {:male => ['Johnny']})
117
+ Acapela::Voice.map[:en][:male].should == ['Johnny']
118
+ Acapela::Voice.map[:en][:female].first.should == 'Tracy'
119
+ end
120
+
121
+ it 'should allow adding an entry' do
122
+ Acapela::Voice.map[:is].should be_nil
123
+ Acapela::Voice.override_map(:is => {:male => ['Birkir'], :female => ['Harpa']})
124
+ Acapela::Voice.map[:is][:male].first.should == 'Birkir'
125
+ Acapela::Voice.map[:is][:female].first.should == 'Harpa'
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe Acapela do
4
+ it 'should reference a config file' do
5
+ config = Acapela.config
6
+
7
+ config.should_not be_nil
8
+ config.should be_a_kind_of(Acapela::Config)
9
+ end
10
+
11
+ it 'should allow setting of the config file' do
12
+ default_config = Acapela.config
13
+
14
+ begin
15
+ test_config = Acapela::Config.read(test_config)
16
+
17
+ Acapela.config = test_config
18
+ Acapela.config.should == test_config
19
+ ensure
20
+ Acapela.config = default_config
21
+ end
22
+ end
23
+
24
+ end
@@ -0,0 +1,45 @@
1
+ CONFIG_DIR = 'config'
2
+ CONFIG_FILE = File.join(CONFIG_DIR, 'acapela.yml')
3
+ DEFAULT_CONFIG_FILE = <<-YAML
4
+ login: 'EVAL_VAAS'
5
+ application: 'MY_APPLICATION'
6
+ password: 'MY_PASSWORD'
7
+ YAML
8
+
9
+ def create_missing_config_file
10
+ unless File.exists?(CONFIG_FILE)
11
+ Dir.mkdir(CONFIG_DIR)
12
+ File.open(CONFIG_FILE, 'w') do |f|
13
+ f.write(DEFAULT_CONFIG_FILE)
14
+ end
15
+ end
16
+ end
17
+
18
+ def test_config
19
+ File.join('spec', 'acapela', 'test_config.yml')
20
+ end
21
+
22
+ create_missing_config_file
23
+
24
+ require File.join(File.dirname(__FILE__), '..', 'init')
25
+ require 'bundler'
26
+ Bundler.require(:development)
27
+ require 'acapela/mocks'
28
+
29
+ def silence_warnings
30
+ begin
31
+ old_verbose, $VERBOSE = $VERBOSE, nil
32
+ yield
33
+ ensure
34
+ $VERBOSE = old_verbose
35
+ end
36
+ end
37
+
38
+ # Some test voices.
39
+ silence_warnings do
40
+ Acapela::Voices::PER_LANGUAGE = {
41
+ :en => {:female=>["Tracy", "Heather"], :male=>["Kenny"]},
42
+ :es => {:female=>["Rosa"], :male=>[]},
43
+ :fr => {:female=>[], :male=>["Antoine", "Bruno"]},
44
+ }
45
+ end
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: acapela
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.8.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Birkir A. Barkarson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-10-22 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: backports
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: 2.6.0
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: 2.6.0
46
+ - !ruby/object:Gem::Dependency
47
+ name: mocha
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: 0.9.0
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: 0.9.0
62
+ description: Ruby interface to Acapela's API for generating speech from text. More
63
+ info http://www.acapela-vaas.com/
64
+ email:
65
+ - birkirb@stoicviking.net
66
+ executables: []
67
+ extensions: []
68
+ extra_rdoc_files: []
69
+ files:
70
+ - .gitignore
71
+ - .rspec
72
+ - Gemfile
73
+ - Rakefile
74
+ - acapela.gemspec
75
+ - init.rb
76
+ - lib/acapela.rb
77
+ - lib/acapela/config.rb
78
+ - lib/acapela/constants.rb
79
+ - lib/acapela/error.rb
80
+ - lib/acapela/mocks.rb
81
+ - lib/acapela/mocks/constants.rb
82
+ - lib/acapela/mocks/response.rb
83
+ - lib/acapela/mocks/test_file.mp3
84
+ - lib/acapela/mocks/voice_service.rb
85
+ - lib/acapela/response.rb
86
+ - lib/acapela/voice.rb
87
+ - lib/acapela/voice_service.rb
88
+ - lib/acapela/voices/default.rb
89
+ - script/create_voices.rb
90
+ - script/test_voices.rb
91
+ - spec/acapela/config_spec.rb
92
+ - spec/acapela/response_spec.rb
93
+ - spec/acapela/test_config.yml
94
+ - spec/acapela/voice_service_spec.rb
95
+ - spec/acapela/voice_spec.rb
96
+ - spec/acapela_spec.rb
97
+ - spec/spec_helper.rb
98
+ homepage: https://github.com/birkirb/acapela
99
+ licenses: []
100
+ post_install_message:
101
+ rdoc_options: []
102
+ require_paths:
103
+ - lib
104
+ required_ruby_version: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ none: false
112
+ requirements:
113
+ - - ! '>='
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ requirements: []
117
+ rubyforge_project: acapela
118
+ rubygems_version: 1.8.24
119
+ signing_key:
120
+ specification_version: 3
121
+ summary: Generate speech from the Acapela text to voice service.
122
+ test_files: []
123
+ has_rdoc: