tts-acapela 0.1.0
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/README.rdoc +2 -0
- data/Rakefile +57 -0
- data/ext/acapela.c +163 -0
- data/ext/acapela.h +14 -0
- data/ext/extconf.rb +14 -0
- data/lib/tts-acapela.rb +7 -0
- data/lib/tts/acapela.rb +41 -0
- data/lib/tts/cache.rb +41 -0
- data/spec/acceptance/acapela_spec.rb +154 -0
- data/spec/acceptance/cached_acapela_spec.rb +63 -0
- data/spec/lib/tts/acapela_spec.rb +36 -0
- data/spec/lib/tts/cache_spec.rb +106 -0
- data/spec/spec_helper.rb +5 -0
- metadata +74 -0
data/README.rdoc
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'rspec', '>= 2'
|
3
|
+
require 'rspec'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
|
6
|
+
namespace :gem do
|
7
|
+
|
8
|
+
desc "Builds the gem"
|
9
|
+
task :build do
|
10
|
+
system "gem build *.gemspec && mkdir -p pkg/ && mv *.gem pkg/"
|
11
|
+
end
|
12
|
+
|
13
|
+
desc "Builds and installs the gem"
|
14
|
+
task :install => :build do
|
15
|
+
system "gem install pkg/"
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
namespace :ext do
|
21
|
+
|
22
|
+
task :create_makefile do
|
23
|
+
chdir("ext") { ruby "extconf.rb" }
|
24
|
+
end
|
25
|
+
|
26
|
+
task :compile => :create_makefile do
|
27
|
+
chdir("ext") { sh "make" }
|
28
|
+
end
|
29
|
+
|
30
|
+
task :move_objects do
|
31
|
+
chdir("ext") do
|
32
|
+
mkdir_p "objs"
|
33
|
+
Dir["*.o"].each do |filename|
|
34
|
+
mv filename, "objs"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
task :build => [ :compile, :move_objects ] do
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
desc "Run all specs in spec/lib directory"
|
46
|
+
RSpec::Core::RakeTask.new(:spec) do |task|
|
47
|
+
task.pattern = "spec/lib/**/*_spec.rb"
|
48
|
+
end
|
49
|
+
|
50
|
+
namespace :spec do
|
51
|
+
|
52
|
+
desc "Run all specs in spec/acceptance directory"
|
53
|
+
RSpec::Core::RakeTask.new(:acceptance => "ext:build") do |task|
|
54
|
+
task.pattern = "spec/acceptance/**/*_spec.rb"
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
data/ext/acapela.c
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
#include "acapela.h"
|
2
|
+
|
3
|
+
// private functions
|
4
|
+
|
5
|
+
VALUE notConnectedErrorClass;
|
6
|
+
VALUE responseErrorClass;
|
7
|
+
VALUE valueErrorClass;
|
8
|
+
|
9
|
+
void checkConnection(const char* methodName, VALUE self) {
|
10
|
+
if (acapela_connected(self) == Qfalse)
|
11
|
+
rb_raise(notConnectedErrorClass, "method '%s' can just be executed with a open connection", methodName);
|
12
|
+
}
|
13
|
+
|
14
|
+
void checkResponse(const char* action, nscRESULT response) {
|
15
|
+
if (response != NSC_OK)
|
16
|
+
rb_raise(responseErrorClass, "error %d while performing action '%s'\n", response, action);
|
17
|
+
}
|
18
|
+
|
19
|
+
int callback_speech_data(
|
20
|
+
const unsigned char *data,
|
21
|
+
unsigned int dataSize,
|
22
|
+
PNSC_SOUND_DATA soundData,
|
23
|
+
void *instanceData
|
24
|
+
) {
|
25
|
+
fwrite(data, dataSize, 1, (FILE*)instanceData);
|
26
|
+
return 0;
|
27
|
+
}
|
28
|
+
|
29
|
+
void initializeExecutionData(NSC_EXEC_DATA* executionData, FILE *file) {
|
30
|
+
executionData->pfnSpeechData = callback_speech_data;
|
31
|
+
executionData->pfnSpeechEvent = NULL;
|
32
|
+
executionData->ulEventFilter = NSC_EVTBIT_TEXT;
|
33
|
+
executionData->bEventSynchroReq = 1;
|
34
|
+
executionData->vsSoundData.uiSize = 0;
|
35
|
+
executionData->vsSoundData.pSoundBuffer = NULL;
|
36
|
+
executionData->pAppInstanceData = (void*)file;
|
37
|
+
}
|
38
|
+
|
39
|
+
// public functions
|
40
|
+
|
41
|
+
void Init_acapela() {
|
42
|
+
VALUE ttsModule = rb_define_module("TTS");
|
43
|
+
VALUE acapelaClass = rb_define_class_under(ttsModule, "Acapela", rb_cObject);
|
44
|
+
notConnectedErrorClass = rb_const_get(acapelaClass, rb_intern("NotConnectedError"));
|
45
|
+
responseErrorClass = rb_const_get(acapelaClass, rb_intern("ResponseError"));
|
46
|
+
valueErrorClass = rb_const_get(acapelaClass, rb_intern("ValueError"));
|
47
|
+
|
48
|
+
rb_define_method(acapelaClass, "connect", acapela_connect, 0);
|
49
|
+
rb_define_method(acapelaClass, "connected?", acapela_connected, 0);
|
50
|
+
rb_define_method(acapelaClass, "disconnect", acapela_disconnect, 0);
|
51
|
+
rb_define_method(acapelaClass, "voices", acapela_voices, 0);
|
52
|
+
rb_define_method(acapelaClass, "synthesize", acapela_synthesize, 1);
|
53
|
+
}
|
54
|
+
|
55
|
+
VALUE acapela_connect(VALUE self) {
|
56
|
+
nscHSRV *serverHandle;
|
57
|
+
nscHANDLE *dispatcherHandle;
|
58
|
+
|
59
|
+
if (acapela_connected(self) == Qtrue)
|
60
|
+
return Qfalse;
|
61
|
+
|
62
|
+
VALUE host = rb_ivar_get(self, rb_intern("@host"));
|
63
|
+
VALUE commandPort = rb_ivar_get(self, rb_intern("@command_port"));
|
64
|
+
VALUE dataPort = rb_ivar_get(self, rb_intern("@data_port"));
|
65
|
+
|
66
|
+
serverHandle = (nscHSRV*)malloc(sizeof(nscHSRV));
|
67
|
+
checkResponse("connecting", nscCreateServerContextEx(NSC_AF_INET, NUM2INT(commandPort), NUM2INT(dataPort), StringValuePtr(host), serverHandle));
|
68
|
+
rb_ivar_set(self, rb_intern("@connection"), (VALUE)serverHandle);
|
69
|
+
|
70
|
+
dispatcherHandle = (nscHANDLE*)malloc(sizeof(nscHANDLE));
|
71
|
+
checkResponse("creating dispatcher", nscCreateDispatcher(dispatcherHandle));
|
72
|
+
rb_ivar_set(self, rb_intern("@dispatcher"), (VALUE)dispatcherHandle);
|
73
|
+
|
74
|
+
return Qtrue;
|
75
|
+
}
|
76
|
+
|
77
|
+
VALUE acapela_connected(VALUE self) {
|
78
|
+
return rb_ivar_get(self, rb_intern("@connection")) == Qnil ? Qfalse : Qtrue;
|
79
|
+
}
|
80
|
+
|
81
|
+
VALUE acapela_disconnect(VALUE self) {
|
82
|
+
nscHSRV *serverHandle;
|
83
|
+
nscHANDLE *dispatcherHandle;
|
84
|
+
|
85
|
+
if (acapela_connected(self) == Qfalse)
|
86
|
+
return Qfalse;
|
87
|
+
|
88
|
+
dispatcherHandle = (nscHANDLE*)rb_ivar_get(self, rb_intern("@dispatcher"));
|
89
|
+
checkResponse("deleting dispatcher", nscDeleteDispatcher(*dispatcherHandle));
|
90
|
+
rb_ivar_set(self, rb_intern("@dispatcher"), Qnil);
|
91
|
+
free(dispatcherHandle);
|
92
|
+
|
93
|
+
serverHandle = (nscHSRV*)rb_ivar_get(self, rb_intern("@connection"));
|
94
|
+
checkResponse("disconnecting", nscReleaseServerContext(*serverHandle));
|
95
|
+
rb_ivar_set(self, rb_intern("@connection"), Qnil);
|
96
|
+
free(serverHandle);
|
97
|
+
|
98
|
+
return Qtrue;
|
99
|
+
}
|
100
|
+
|
101
|
+
VALUE acapela_voices(VALUE self) {
|
102
|
+
VALUE result;
|
103
|
+
|
104
|
+
nscRESULT response;
|
105
|
+
nscHSRV *serverHandle;
|
106
|
+
|
107
|
+
nscHANDLE voiceHandle;
|
108
|
+
NSC_FINDVOICE_DATA voice;
|
109
|
+
|
110
|
+
checkConnection("voices", self);
|
111
|
+
|
112
|
+
serverHandle = (nscHSRV*)rb_ivar_get(self, rb_intern("@connection"));
|
113
|
+
|
114
|
+
result = rb_ary_new();
|
115
|
+
response = nscFindFirstVoice(*serverHandle, NULL, 0, 0, 0, &voice, &voiceHandle);
|
116
|
+
while (response == NSC_OK) {
|
117
|
+
rb_ary_push(result, rb_str_new2(voice.cVoiceName));
|
118
|
+
response = nscFindNextVoice(voiceHandle, &voice);
|
119
|
+
}
|
120
|
+
nscCloseFindVoice(voiceHandle);
|
121
|
+
|
122
|
+
return result;
|
123
|
+
}
|
124
|
+
|
125
|
+
VALUE acapela_synthesize(VALUE self, VALUE text) {
|
126
|
+
VALUE result;
|
127
|
+
|
128
|
+
nscRESULT response;
|
129
|
+
nscHSRV* serverHandle;
|
130
|
+
nscHANDLE* dispatcherHandle;
|
131
|
+
VALUE voice;
|
132
|
+
int sampleFrequency;
|
133
|
+
nscHANDLE ttsHandle;
|
134
|
+
NSC_EXEC_DATA executionData;
|
135
|
+
char* filename;
|
136
|
+
FILE* file;
|
137
|
+
nscCHANID channelId;
|
138
|
+
|
139
|
+
checkConnection("synthesize", self);
|
140
|
+
|
141
|
+
serverHandle = (nscHSRV*)rb_ivar_get(self, rb_intern("@connection"));
|
142
|
+
dispatcherHandle = (nscHANDLE*)rb_ivar_get(self, rb_intern("@dispatcher"));
|
143
|
+
voice = rb_ivar_get(self, rb_intern("@voice"));
|
144
|
+
sampleFrequency = rb_ivar_get(self, rb_intern("@sample_frequency"));
|
145
|
+
|
146
|
+
checkResponse("opening channel", nscInitChannel(*serverHandle, StringValuePtr(voice), NUM2INT(sampleFrequency), 0, *dispatcherHandle, &channelId));
|
147
|
+
checkResponse("lock channel", nscLockChannel(*serverHandle, channelId, *dispatcherHandle, &ttsHandle));
|
148
|
+
checkResponse("add text", nscAddTextEx(ttsHandle, "UTF-8", StringValuePtr(text), strlen(StringValuePtr(text)), NULL));
|
149
|
+
|
150
|
+
filename = tmpnam(NULL);
|
151
|
+
file = fopen(filename, "w");
|
152
|
+
initializeExecutionData(&executionData, file);
|
153
|
+
checkResponse("execute channel", nscExecChannel(ttsHandle, &executionData));
|
154
|
+
fclose(file);
|
155
|
+
|
156
|
+
checkResponse("unlock channel", nscUnlockChannel(ttsHandle));
|
157
|
+
checkResponse("closing channel", nscCloseChannel(*serverHandle, channelId));
|
158
|
+
|
159
|
+
VALUE fileClass = rb_const_get(rb_cObject, rb_intern("File"));
|
160
|
+
result = rb_funcall(fileClass, rb_intern("new"), 1, rb_str_new2(filename));
|
161
|
+
|
162
|
+
return result;
|
163
|
+
}
|
data/ext/acapela.h
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#include <stdio.h>
|
2
|
+
#include <stdlib.h>
|
3
|
+
#include <string.h>
|
4
|
+
|
5
|
+
#include "ruby.h"
|
6
|
+
#include "nscube.h"
|
7
|
+
|
8
|
+
void Init_acapela();
|
9
|
+
|
10
|
+
VALUE acapela_connect(VALUE self);
|
11
|
+
VALUE acapela_connected(VALUE self);
|
12
|
+
VALUE acapela_disconnect(VALUE self);
|
13
|
+
VALUE acapela_voices(VALUE self);
|
14
|
+
VALUE acapela_synthesize(VALUE self, VALUE text);
|
data/ext/extconf.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
|
3
|
+
extension_name = "acapela"
|
4
|
+
|
5
|
+
dir_config extension_name, "/opt/Acapela/TelecomTTS/include"
|
6
|
+
|
7
|
+
found_nscube = find_library "nscube", "nscCreateServerContext", "/opt/Acapela/TelecomTTS/lib"
|
8
|
+
|
9
|
+
if found_nscube
|
10
|
+
create_makefile extension_name
|
11
|
+
else
|
12
|
+
puts "library 'nscube' is required, but wasn't found.\n"
|
13
|
+
exit 1
|
14
|
+
end
|
data/lib/tts-acapela.rb
ADDED
data/lib/tts/acapela.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
|
2
|
+
module TTS
|
3
|
+
|
4
|
+
class Acapela
|
5
|
+
|
6
|
+
class Error < StandardError; end
|
7
|
+
class NotConnectedError < Error; end
|
8
|
+
class ResponseError < Error; end
|
9
|
+
class ValueError < Error; end
|
10
|
+
class VoiceNotSupportedError < Error; end
|
11
|
+
|
12
|
+
attr_accessor :host
|
13
|
+
attr_accessor :command_port
|
14
|
+
attr_accessor :data_port
|
15
|
+
attr_accessor :voice
|
16
|
+
attr_reader :sample_frequency
|
17
|
+
attr_accessor :cache_directory
|
18
|
+
|
19
|
+
def initialize(options = { })
|
20
|
+
self.host = options[:host] || "127.0.0.1"
|
21
|
+
self.command_port = options[:command_port] || 6666
|
22
|
+
self.data_port = options[:data_port] || 6665
|
23
|
+
self.voice = options[:voice]
|
24
|
+
self.sample_frequency = options[:sample_frequency] || 22050
|
25
|
+
self.cache_directory = options[:cache_directory] || "/tmp"
|
26
|
+
end
|
27
|
+
|
28
|
+
def sample_frequency=(value)
|
29
|
+
raise ValueError, "sample frequency #{value} is not supported. try 22050, 16000, 11025 or 8000." unless [ 22050, 16000, 11025, 8000 ].include?(value)
|
30
|
+
@sample_frequency = value
|
31
|
+
end
|
32
|
+
|
33
|
+
def settings_stamp
|
34
|
+
"#{@voice}#{@sample_frequency}"
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "ext", "acapela"))
|
data/lib/tts/cache.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module TTS
|
5
|
+
|
6
|
+
class Cache
|
7
|
+
|
8
|
+
attr_accessor :tts
|
9
|
+
attr_accessor :cache_directory
|
10
|
+
|
11
|
+
def initialize(tts, cache_directory)
|
12
|
+
@tts, @cache_directory = tts, cache_directory
|
13
|
+
FileUtils.mkdir_p @cache_directory
|
14
|
+
end
|
15
|
+
|
16
|
+
def clear
|
17
|
+
FileUtils.rm Dir[File.join(@cache_directory, "*.pcm")]
|
18
|
+
end
|
19
|
+
|
20
|
+
def synthesize(text)
|
21
|
+
cache_filename = cache_path(text)
|
22
|
+
unless File.exists?(cache_filename)
|
23
|
+
file = @tts.synthesize text
|
24
|
+
FileUtils.mv file.path, cache_filename
|
25
|
+
end
|
26
|
+
File.new cache_filename
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def cache_path(text)
|
32
|
+
File.join @cache_directory, cache_filename(text)
|
33
|
+
end
|
34
|
+
|
35
|
+
def cache_filename(text)
|
36
|
+
Digest::MD5.hexdigest("#{text}#{@tts.settings_stamp}") + ".pcm"
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
|
3
|
+
|
4
|
+
describe TTS::Acapela do
|
5
|
+
|
6
|
+
before :each do
|
7
|
+
@acapela = described_class.new :host => "46.51.190.56"
|
8
|
+
end
|
9
|
+
|
10
|
+
shared_examples_for "a method that need an open connection" do
|
11
|
+
|
12
|
+
it "should raise an #{TTS::Acapela::NotConnectedError} if disconnected" do
|
13
|
+
@acapela.disconnect
|
14
|
+
lambda do
|
15
|
+
do_call
|
16
|
+
end.should raise_error(TTS::Acapela::NotConnectedError);
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "connect" do
|
22
|
+
|
23
|
+
after :each do
|
24
|
+
@acapela.disconnect
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should change the state to connected" do
|
28
|
+
lambda do
|
29
|
+
@acapela.connect
|
30
|
+
end.should change(@acapela, :connected?).from(false).to(true)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should not raise an error if it's already connected" do
|
34
|
+
@acapela.connect
|
35
|
+
lambda do
|
36
|
+
@acapela.connect
|
37
|
+
end.should_not raise_error
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "disconnect" do
|
43
|
+
|
44
|
+
before :each do
|
45
|
+
@acapela.connect
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should change the state to disconnected" do
|
49
|
+
lambda do
|
50
|
+
@acapela.disconnect
|
51
|
+
end.should change(@acapela, :connected?).from(true).to(false)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should not raise an error if it's already disconnected" do
|
55
|
+
@acapela.disconnect
|
56
|
+
lambda do
|
57
|
+
@acapela.disconnect
|
58
|
+
end.should_not raise_error
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "voices" do
|
64
|
+
|
65
|
+
before :each do
|
66
|
+
@acapela.connect
|
67
|
+
end
|
68
|
+
|
69
|
+
after :each do
|
70
|
+
@acapela.disconnect
|
71
|
+
end
|
72
|
+
|
73
|
+
def do_call
|
74
|
+
@acapela.voices
|
75
|
+
end
|
76
|
+
|
77
|
+
it_should_behave_like "a method that need an open connection"
|
78
|
+
|
79
|
+
it "should return an array of voices" do
|
80
|
+
voices = do_call
|
81
|
+
voices.should be_instance_of(Array)
|
82
|
+
voices.should == [
|
83
|
+
"julia22k", "klaus22k", "lucy22k", "bruno22k", "claire22k", "julie22k",
|
84
|
+
"peter22k", "sarah22k", "rachel22k", "justine22k", "graham22k", "alice22k"
|
85
|
+
]
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
describe "synthesize" do
|
91
|
+
|
92
|
+
before :each do
|
93
|
+
@acapela.connect
|
94
|
+
@acapela.voice = "sarah22k"
|
95
|
+
end
|
96
|
+
|
97
|
+
after :each do
|
98
|
+
File.delete @file.path if @file
|
99
|
+
@acapela.disconnect
|
100
|
+
end
|
101
|
+
|
102
|
+
def do_call(text = "Hallo Welt")
|
103
|
+
@acapela.synthesize text
|
104
|
+
end
|
105
|
+
|
106
|
+
it_should_behave_like "a method that need an open connection"
|
107
|
+
|
108
|
+
it "should return a file with the synthesized text" do
|
109
|
+
@file = do_call
|
110
|
+
@file.should be_instance_of(File)
|
111
|
+
end
|
112
|
+
|
113
|
+
context "with voice 'julia22k'" do
|
114
|
+
|
115
|
+
before :each do
|
116
|
+
@acapela.voice = "julia22k"
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should return the correct file" do
|
120
|
+
@file = do_call
|
121
|
+
md5 = Digest::MD5.hexdigest File.read(@file.path)
|
122
|
+
md5.should == "7e50b6c1666ec97be6c84b9dacf0ef89"
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
context "with voice 'lucy22k'" do
|
128
|
+
|
129
|
+
before :each do
|
130
|
+
@acapela.voice = "lucy22k"
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should return the correct file" do
|
134
|
+
@file = do_call
|
135
|
+
md5 = Digest::MD5.hexdigest File.read(@file.path)
|
136
|
+
md5.should == "8288953a25090a2089b86ac324e582dc"
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
context "with a text with special characters" do
|
142
|
+
|
143
|
+
it "should return the correct file" do
|
144
|
+
@file = do_call "Schönhauser-Allee"
|
145
|
+
`cp #{@file.path} /home/phifty/Desktop/test.pcm`
|
146
|
+
md5 = Digest::MD5.hexdigest File.read(@file.path)
|
147
|
+
md5.should == "bf3ab75c5b55b4815e810ac6b0c83ee0"
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
|
2
|
+
|
3
|
+
describe TTS::Cache do
|
4
|
+
|
5
|
+
before :each do
|
6
|
+
@acapela = TTS::Acapela.new :host => "46.51.190.56", :voice => "julia22k", :sample_frequency => 22050
|
7
|
+
@acapela.connect
|
8
|
+
|
9
|
+
@cache = described_class.new @acapela, "/tmp/cache"
|
10
|
+
end
|
11
|
+
|
12
|
+
after :each do
|
13
|
+
@cache.clear
|
14
|
+
@acapela.disconnect
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "synthesize" do
|
18
|
+
|
19
|
+
before :each do
|
20
|
+
@cache_filename = "/tmp/cache/58bb7452ad8aaa2a25509ec076cc979d.pcm"
|
21
|
+
end
|
22
|
+
|
23
|
+
context "for a new request" do
|
24
|
+
|
25
|
+
before :each do
|
26
|
+
@cache.clear
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should create a new cache file" do
|
30
|
+
File.exists?(@cache_filename).should be_false
|
31
|
+
@cache.synthesize "Hallo"
|
32
|
+
File.exists?(@cache_filename).should be_true
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should return the right file" do
|
36
|
+
file = @cache.synthesize "Hallo"
|
37
|
+
file.path.should == @cache_filename
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
context "for a cached request" do
|
43
|
+
|
44
|
+
before :each do
|
45
|
+
@cache.synthesize "Hallo"
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should not create a new cache file" do
|
49
|
+
File.exists?(@cache_filename).should be_true
|
50
|
+
@cache.synthesize "Hallo"
|
51
|
+
File.exists?(@cache_filename).should be_true
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should return the right file" do
|
55
|
+
file = @cache.synthesize "Hallo"
|
56
|
+
file.path.should == @cache_filename
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
|
2
|
+
|
3
|
+
describe TTS::Acapela do
|
4
|
+
|
5
|
+
before :each do
|
6
|
+
@acapela = described_class.new :voice => "julia22k", :sample_frequency => 22050
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "sample_frequency=" do
|
10
|
+
|
11
|
+
it "should set the sample frequency" do
|
12
|
+
@acapela.sample_frequency = 22050
|
13
|
+
@acapela.sample_frequency == 22050
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should raise an #{TTS::Acapela::ValueError} on an invalid value" do
|
17
|
+
lambda do
|
18
|
+
@acapela.sample_frequency = 5
|
19
|
+
end.should raise_error(TTS::Acapela::ValueError)
|
20
|
+
|
21
|
+
lambda do
|
22
|
+
@acapela.sample_frequency = "invalid"
|
23
|
+
end.should raise_error(TTS::Acapela::ValueError)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "settings_stamp" do
|
29
|
+
|
30
|
+
it "should return a string with a snapshort of the synthesize-settings" do
|
31
|
+
@acapela.settings_stamp.should == "julia22k22050"
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
|
2
|
+
|
3
|
+
describe TTS::Cache do
|
4
|
+
|
5
|
+
before :each do
|
6
|
+
FileUtils.stub(:mkdir_p)
|
7
|
+
|
8
|
+
@acapela = mock TTS::Acapela, :settings_stamp => "julia22k22050"
|
9
|
+
|
10
|
+
@cache = described_class.new @acapela, "/tmp/cache"
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "initialize" do
|
14
|
+
|
15
|
+
it "should try to create the cache directory" do
|
16
|
+
FileUtils.should_receive(:mkdir_p).with("/tmp/cache")
|
17
|
+
described_class.new @acapela, "/tmp/cache"
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "clear" do
|
23
|
+
|
24
|
+
before :each do
|
25
|
+
FileUtils.stub(:rm)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should remove all *.pcm files from the cache directory" do
|
29
|
+
FileUtils.should_receive(:rm).with(Dir["/tmp/cache/*.pcm"])
|
30
|
+
@cache.clear
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "synthesize" do
|
36
|
+
|
37
|
+
before :each do
|
38
|
+
@cache_filename = "/tmp/cache/58bb7452ad8aaa2a25509ec076cc979d.pcm"
|
39
|
+
|
40
|
+
@cached_file = mock File
|
41
|
+
File.stub(:new).and_return(@cached_file)
|
42
|
+
|
43
|
+
@file = mock File, :path => "/tmp/file"
|
44
|
+
@acapela.stub(:synthesize).and_return(@file)
|
45
|
+
end
|
46
|
+
|
47
|
+
def do_synthesize(text = "Hallo")
|
48
|
+
@cache.synthesize text
|
49
|
+
end
|
50
|
+
|
51
|
+
context "for a new request" do
|
52
|
+
|
53
|
+
before :each do
|
54
|
+
File.stub(:exists?).and_return(false)
|
55
|
+
FileUtils.stub(:mv)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should check the existance of the right file" do
|
59
|
+
File.should_receive(:exists?).with(@cache_filename).and_return(true)
|
60
|
+
do_synthesize
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should synthesize a new file" do
|
64
|
+
@acapela.should_receive(:synthesize).with("Hallo").and_return(@file)
|
65
|
+
file = do_synthesize
|
66
|
+
file.should == @cached_file
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should move the synthesized file to the cache" do
|
70
|
+
FileUtils.should_receive(:mv).with(@file.path, @cache_filename)
|
71
|
+
do_synthesize
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should return the synthesized and cached file" do
|
75
|
+
file = do_synthesize
|
76
|
+
file.should == @cached_file
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
context "for a cached request" do
|
82
|
+
|
83
|
+
before :each do
|
84
|
+
File.stub(:exists?).and_return(true)
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should check the existance of the right file" do
|
88
|
+
File.should_receive(:exists?).with(@cache_filename).and_return(true)
|
89
|
+
do_synthesize
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should not synthesize a new file" do
|
93
|
+
@acapela.should_not_receive(:synthesize)
|
94
|
+
do_synthesize
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should return the cached file" do
|
98
|
+
file = do_synthesize
|
99
|
+
file.should == @cached_file
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tts-acapela
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Philipp Brüll
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2010-12-03 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &70207859994280 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '2'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70207859994280
|
25
|
+
description: It uses the native nscapi library to connect to the server.
|
26
|
+
email: b.phifty@gmail.com
|
27
|
+
executables: []
|
28
|
+
extensions:
|
29
|
+
- ext/extconf.rb
|
30
|
+
extra_rdoc_files:
|
31
|
+
- README.rdoc
|
32
|
+
files:
|
33
|
+
- README.rdoc
|
34
|
+
- Rakefile
|
35
|
+
- ext/acapela.c
|
36
|
+
- ext/acapela.h
|
37
|
+
- ext/extconf.rb
|
38
|
+
- lib/tts/acapela.rb
|
39
|
+
- lib/tts/cache.rb
|
40
|
+
- lib/tts-acapela.rb
|
41
|
+
- spec/acceptance/acapela_spec.rb
|
42
|
+
- spec/acceptance/cached_acapela_spec.rb
|
43
|
+
- spec/lib/tts/acapela_spec.rb
|
44
|
+
- spec/lib/tts/cache_spec.rb
|
45
|
+
- spec/spec_helper.rb
|
46
|
+
homepage:
|
47
|
+
licenses: []
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ! '>='
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: 1.8.7
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
requirements: []
|
65
|
+
rubyforge_project: tts-acapela
|
66
|
+
rubygems_version: 1.8.10
|
67
|
+
signing_key:
|
68
|
+
specification_version: 3
|
69
|
+
summary: A client for the Acapela TTS server.
|
70
|
+
test_files:
|
71
|
+
- spec/acceptance/acapela_spec.rb
|
72
|
+
- spec/acceptance/cached_acapela_spec.rb
|
73
|
+
- spec/lib/tts/acapela_spec.rb
|
74
|
+
- spec/lib/tts/cache_spec.rb
|