ispeech 1.0.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.
- data/.gitignore +6 -0
- data/Gemfile +3 -0
- data/LICENCE.txt +21 -0
- data/README.md +88 -0
- data/Rakefile +32 -0
- data/init.rb +1 -0
- data/ispeech.gemspec +25 -0
- data/lib/ispeech.rb +31 -0
- data/lib/ispeech/config.rb +27 -0
- data/lib/ispeech/constants.rb +19 -0
- data/lib/ispeech/error.rb +33 -0
- data/lib/ispeech/mock.rb +93 -0
- data/lib/ispeech/response.rb +45 -0
- data/lib/ispeech/voice.rb +121 -0
- data/lib/ispeech/voice_service.rb +65 -0
- data/lib/ispeech/voices/default.rb +42 -0
- data/script/create_voices.rb +143 -0
- data/script/test_voices.rb +87 -0
- data/spec/ispeech/config_spec.rb +29 -0
- data/spec/ispeech/mock_spec.rb +54 -0
- data/spec/ispeech/response_spec.rb +33 -0
- data/spec/ispeech/voice_service_spec.rb +68 -0
- data/spec/ispeech/voice_spec.rb +128 -0
- data/spec/ispeech_spec.rb +24 -0
- data/spec/spec_helper.rb +69 -0
- data/spec/test_data/test_config.yml +1 -0
- data/spec/test_data/test_file.mp3 +0 -0
- metadata +138 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENCE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright 2012 Birkir A. Barkarson
|
2
|
+
Standard MIT licence:
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
a copy of this software and associated documentation files (the
|
6
|
+
"Software"), to deal in the Software without restriction, including
|
7
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
Ruby interface to Ispeech's API for generating speech from text.
|
2
|
+
More info on their API at http://www.ispeech.org/api/
|
3
|
+
|
4
|
+
## Installation
|
5
|
+
|
6
|
+
gem install ispeech
|
7
|
+
|
8
|
+
## Loading
|
9
|
+
|
10
|
+
require 'ispeech'
|
11
|
+
|
12
|
+
## Config
|
13
|
+
|
14
|
+
Automatically looks for a config/ispeech.yml file.
|
15
|
+
Configuration only requires the 'api_key'
|
16
|
+
|
17
|
+
### Manual configuration
|
18
|
+
|
19
|
+
Ispeech.config = Ispeech::Config.new('some_api_key_hash')
|
20
|
+
|
21
|
+
or
|
22
|
+
|
23
|
+
Ispeech.config = Ispeech::Config.read(path_to_my_own_yaml_config_file)
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
Get the voice service object:
|
28
|
+
|
29
|
+
service = Ispeech.voice_service
|
30
|
+
|
31
|
+
or
|
32
|
+
|
33
|
+
service = Ispeech::VoiceService.new(my_config_object)
|
34
|
+
|
35
|
+
### Generate sounds
|
36
|
+
|
37
|
+
response = service.generate_sound('speak this text', language_or_voice_options)
|
38
|
+
|
39
|
+
or
|
40
|
+
|
41
|
+
response = service.generate_with_voice('speak this text', my_voice_object)
|
42
|
+
|
43
|
+
|
44
|
+
### Language and voice options
|
45
|
+
|
46
|
+
All available voices are defined in [voices/default.rb](https://github.com/birkirb/ispeech/blob/master/lib/ispeech/voices/default.rb)
|
47
|
+
|
48
|
+
You can specificy language, gender, voice name in any combination
|
49
|
+
|
50
|
+
response = service.generate_sound('speak this text', {:language => :en, :gender => :male, :speaker => :usenglishfemale})
|
51
|
+
|
52
|
+
If you have access to different set of voice there are rake tasks to generate a different voice map and any customization of the voice map should be easy.
|
53
|
+
Refer to the spec for details of that.
|
54
|
+
|
55
|
+
### Saving generated sounds
|
56
|
+
|
57
|
+
The response is saved to a tempfile which can be moved, copied or otherwise streamed.
|
58
|
+
|
59
|
+
response = service.generate_sound('speak this text')
|
60
|
+
tempfile = response.download_to_tempfile
|
61
|
+
|
62
|
+
FileUtils.mv(tempfile.path, 'speak_this_text.mp3')
|
63
|
+
|
64
|
+
## Mocking
|
65
|
+
|
66
|
+
For convinence purposes you can use an inbuilt mock to avoid web request while testing.
|
67
|
+
|
68
|
+
require 'ispeech/mock'
|
69
|
+
Ispeech::Mock.enable!
|
70
|
+
|
71
|
+
### Turning off
|
72
|
+
|
73
|
+
Ispeech::Mock.disable!
|
74
|
+
|
75
|
+
### Expectations
|
76
|
+
|
77
|
+
Only two expectations can be set
|
78
|
+
|
79
|
+
Ispeech::VoiceService.expect_ok_response
|
80
|
+
|
81
|
+
or
|
82
|
+
|
83
|
+
Ispeech::VoiceService.expect_error_response
|
84
|
+
|
85
|
+
|
86
|
+
## Copyright
|
87
|
+
|
88
|
+
Copyright (c) 2012 Birkir A. Barkarson. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
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 'ispeech/constants'
|
8
|
+
|
9
|
+
RSpec::Core::RakeTask.new('spec') do |t|
|
10
|
+
t.rspec_opts = ["-fd", "-c"]
|
11
|
+
end
|
12
|
+
|
13
|
+
namespace :generate do
|
14
|
+
desc "Generates MP3 with test strings for all voices."
|
15
|
+
task :test_voices do
|
16
|
+
exec('ruby -I lib script/test_voices.rb')
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "Generate default voice class based on a voice list retrieved from iSpeech's API"
|
20
|
+
task :default_voices_class do
|
21
|
+
exec('ruby -I lib script/create_voices.rb')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
desc "Clean up generated and downloaded files"
|
26
|
+
task :clean do
|
27
|
+
include Ispeech::Scripts
|
28
|
+
FileUtils.rm_rf(LOCAL_TEMP_DIR)
|
29
|
+
FileUtils.rm(VOICES_ENUMERATOR_FILE) rescue nil
|
30
|
+
end
|
31
|
+
|
32
|
+
task :default => :spec
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'ispeech'
|
data/ispeech.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "ispeech/constants"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "ispeech"
|
7
|
+
s.version = Ispeech::VERSION
|
8
|
+
s.authors = ["Birkir A. Barkarson"]
|
9
|
+
s.email = ["birkirb@stoicviking.net"]
|
10
|
+
s.homepage = "https://github.com/birkirb/ispeech"
|
11
|
+
s.summary = %q{Generate speech from the ispeech text to voice service.}
|
12
|
+
s.description = %q{Ruby interface to Ispeech's API for generating speech from text. More info at http://www.ispeech.org/api/}
|
13
|
+
|
14
|
+
s.rubyforge_project = "ispeech"
|
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
|
+
s.add_development_dependency("webmock", '>= 1.8.0')
|
25
|
+
end
|
data/lib/ispeech.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'ispeech/error'
|
2
|
+
require 'ispeech/config'
|
3
|
+
require 'ispeech/voices/default'
|
4
|
+
require 'ispeech/voice'
|
5
|
+
require 'ispeech/constants'
|
6
|
+
require 'ispeech/response'
|
7
|
+
require 'ispeech/voice_service'
|
8
|
+
|
9
|
+
if RUBY_VERSION < "1.9"
|
10
|
+
require 'backports'
|
11
|
+
end
|
12
|
+
|
13
|
+
module Ispeech
|
14
|
+
|
15
|
+
def self.config
|
16
|
+
@@config ||= Ispeech::Config.read rescue nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.config=(config)
|
20
|
+
if config.is_a?(Ispeech::Config)
|
21
|
+
@@config = config
|
22
|
+
else
|
23
|
+
raise Error.new("Ispeech configuration required. Not #{config.class}")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.voice_service
|
28
|
+
VoiceService.new(config)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module Ispeech
|
5
|
+
class Config
|
6
|
+
attr_reader :api_key,
|
7
|
+
:target_url
|
8
|
+
|
9
|
+
DEFAULT_TARGET_URL = 'http://api.ispeech.org/api/rest'
|
10
|
+
|
11
|
+
def initialize(api_key, target_url = nil)
|
12
|
+
@api_key = api_key
|
13
|
+
@target_url = URI.parse(target_url || DEFAULT_TARGET_URL)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.read(config_file = nil)
|
17
|
+
config_file ||= File.join('config', 'ispeech.yml')
|
18
|
+
begin
|
19
|
+
yaml = YAML.load_file(config_file)
|
20
|
+
self.new(yaml['api_key'], yaml['target_url'])
|
21
|
+
rescue => err
|
22
|
+
raise Error.new("Failed to read configuration file", err)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module Ispeech
|
4
|
+
|
5
|
+
VERSION = "1.0.1"
|
6
|
+
|
7
|
+
class Voice
|
8
|
+
QUALITY_LOW = 8000
|
9
|
+
QUALITY_HIGH = 22050
|
10
|
+
GENDER_FEMALE = :female
|
11
|
+
GENDER_MALE = :male
|
12
|
+
end
|
13
|
+
|
14
|
+
module Scripts
|
15
|
+
LOCAL_TEMP_DIR = 'tmp'
|
16
|
+
VOICES_ENUMERATOR_FILE = File.join('tmp', 'voice_enumerator.out')
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Ispeech
|
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
|
data/lib/ispeech/mock.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
module Ispeech
|
2
|
+
module Mock
|
3
|
+
|
4
|
+
RESPONSE_MP3_FILE = File.join(File.dirname(__FILE__), '..', '..', 'spec', 'test_data', 'test_file.mp3')
|
5
|
+
FAKE_ERROR = Error.new("The mock wants you to fail!")
|
6
|
+
EMPTY_PROC = proc {}
|
7
|
+
|
8
|
+
def self.enable!
|
9
|
+
Ispeech::Response.class_eval(<<-EVAL, __FILE__, __LINE__)
|
10
|
+
def download_to_tempfile
|
11
|
+
download_to_tempfile_with_mock
|
12
|
+
end
|
13
|
+
def initialize(response)
|
14
|
+
initialize_with_fake_response
|
15
|
+
end
|
16
|
+
EVAL
|
17
|
+
Ispeech::VoiceService.class_eval(<<-EVAL, __FILE__, __LINE__)
|
18
|
+
def post(params)
|
19
|
+
post_with_set_response(params)
|
20
|
+
end
|
21
|
+
EVAL
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.disable!
|
25
|
+
Ispeech::Response.class_eval(<<-EVAL, __FILE__, __LINE__)
|
26
|
+
def download_to_tempfile
|
27
|
+
download_to_tempfile_without_mock
|
28
|
+
end
|
29
|
+
def initialize(response)
|
30
|
+
initalize_without_fake_response(response)
|
31
|
+
end
|
32
|
+
EVAL
|
33
|
+
Ispeech::VoiceService.class_eval(<<-EVAL, __FILE__, __LINE__)
|
34
|
+
def post(params)
|
35
|
+
post_without_set_response(params)
|
36
|
+
end
|
37
|
+
EVAL
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module Ispeech
|
44
|
+
class Response
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
alias :download_to_tempfile_without_mock :download_to_tempfile
|
49
|
+
alias :initalize_without_fake_response :initialize
|
50
|
+
|
51
|
+
def download_to_tempfile_with_mock
|
52
|
+
tempfile = Tempfile.new(SecureRandom.uuid)
|
53
|
+
File.open(Mock::RESPONSE_MP3_FILE) do |f|
|
54
|
+
tempfile.write(f.read)
|
55
|
+
tempfile.flush
|
56
|
+
end
|
57
|
+
tempfile
|
58
|
+
end
|
59
|
+
|
60
|
+
def initialize_with_fake_response
|
61
|
+
VoiceService.expected_response.call
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
module Ispeech
|
68
|
+
class VoiceService
|
69
|
+
|
70
|
+
@@expected_response = Mock::EMPTY_PROC
|
71
|
+
|
72
|
+
def self.expected_response
|
73
|
+
@@expected_response
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.expect_ok_response
|
77
|
+
@@expected_response = Mock::EMPTY_PROC
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.expect_error_response
|
81
|
+
@@expected_response = proc { raise Mock::FAKE_ERROR }
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
alias :post_without_set_response :post
|
87
|
+
|
88
|
+
def post_with_set_response(params)
|
89
|
+
self.class.expected_response
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'net/http'
|
3
|
+
require 'tempfile'
|
4
|
+
require 'securerandom'
|
5
|
+
|
6
|
+
module Ispeech
|
7
|
+
class Response
|
8
|
+
|
9
|
+
RESPONSE_ERROR_MESSAGE = 'message'
|
10
|
+
RESPONSE_ERROR_CODE = 'code'
|
11
|
+
ERROR_UNEXPECTED_RESPONSE = Error.new("Response was not a valid HTTP response.")
|
12
|
+
|
13
|
+
def initialize(response)
|
14
|
+
if response.is_a?(Net::HTTPResponse)
|
15
|
+
if 200 == response.code.to_i
|
16
|
+
@response = response
|
17
|
+
else
|
18
|
+
if response.content_length.to_i > 0
|
19
|
+
params = CGI::parse(response.body)
|
20
|
+
raise ServiceError.new(params[RESPONSE_ERROR_MESSAGE].first, params[RESPONSE_ERROR_CODE].first)
|
21
|
+
else
|
22
|
+
raise ServiceError.new(response.code_type, response.code)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
else
|
26
|
+
raise ERROR_UNEXPECTED_RESPONSE
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def download_to_tempfile
|
31
|
+
content = generated_file
|
32
|
+
file = Tempfile.new(SecureRandom.uuid)
|
33
|
+
file.write(content)
|
34
|
+
file.flush
|
35
|
+
file # Leaving open. Will be closed once object is finalized.
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def generated_file
|
41
|
+
@response.body
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|