acapela 0.8.1

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.
@@ -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: