ispeech 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|