@jambonz/speech-utils 0.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.
- package/.eslintignore +1 -0
- package/.eslintrc.json +126 -0
- package/.github/workflows/ci.yml +27 -0
- package/.github/workflows/publish.yml +23 -0
- package/LICENSE +21 -0
- package/README.md +2 -0
- package/build_stubs.sh +11 -0
- package/config/test.json +13 -0
- package/index.js +32 -0
- package/lib/get-ibm-access-token.js +48 -0
- package/lib/get-nuance-access-token.js +49 -0
- package/lib/get-tts-voices.js +111 -0
- package/lib/purge-tts-cache.js +46 -0
- package/lib/synth-audio.js +469 -0
- package/lib/utils.js +115 -0
- package/package.json +48 -0
- package/protos/nuance/synthesizer.proto +332 -0
- package/protos/riva/proto/riva_audio.proto +36 -0
- package/protos/riva/proto/riva_tts.proto +77 -0
- package/stubs/nuance/synthesizer_grpc_pb.js +104 -0
- package/stubs/nuance/synthesizer_pb.js +6815 -0
- package/stubs/riva/proto/riva_audio_grpc_pb.js +1 -0
- package/stubs/riva/proto/riva_audio_pb.js +31 -0
- package/stubs/riva/proto/riva_tts_grpc_pb.js +99 -0
- package/stubs/riva/proto/riva_tts_pb.js +1278 -0
- package/test/docker-compose-testbed.yaml +39 -0
- package/test/docker_start.js +12 -0
- package/test/docker_stop.js +12 -0
- package/test/ibm.js +78 -0
- package/test/index.js +5 -0
- package/test/nuance.js +50 -0
- package/test/synth.js +431 -0
- package/test/tmp/redis.conf +2054 -0
- package/test/webhook/Dockerfile +23 -0
- package/test/webhook/app.js +125 -0
- package/test/webhook/package-lock.json +1045 -0
- package/test/webhook/package.json +15 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
version: '3.9'
|
|
2
|
+
|
|
3
|
+
networks:
|
|
4
|
+
speech-utils:
|
|
5
|
+
driver: bridge
|
|
6
|
+
ipam:
|
|
7
|
+
config:
|
|
8
|
+
- subnet: 172.41.0.0/16
|
|
9
|
+
|
|
10
|
+
services:
|
|
11
|
+
redis:
|
|
12
|
+
image: redis:alpine
|
|
13
|
+
ports:
|
|
14
|
+
- "3379:6379"
|
|
15
|
+
networks:
|
|
16
|
+
speech-utils:
|
|
17
|
+
ipv4_address: 172.41.0.5
|
|
18
|
+
|
|
19
|
+
redis-auth:
|
|
20
|
+
image: redis:alpine
|
|
21
|
+
command: redis-server /tmp/redis.conf
|
|
22
|
+
ports:
|
|
23
|
+
- "3380:6379"
|
|
24
|
+
volumes:
|
|
25
|
+
- ./tmp:/tmp
|
|
26
|
+
networks:
|
|
27
|
+
speech-utils:
|
|
28
|
+
ipv4_address: 172.41.0.6
|
|
29
|
+
|
|
30
|
+
webhook-tts-scaffold:
|
|
31
|
+
image: jambonz/webhook-tts-test-scaffold:latest
|
|
32
|
+
ports:
|
|
33
|
+
- "3100:3000/tcp"
|
|
34
|
+
volumes:
|
|
35
|
+
- ./test-apps:/tmp
|
|
36
|
+
networks:
|
|
37
|
+
speech-utils:
|
|
38
|
+
ipv4_address: 172.41.0.10
|
|
39
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const test = require('tape').test ;
|
|
2
|
+
const exec = require('child_process').exec ;
|
|
3
|
+
|
|
4
|
+
test('starting docker network..', (t) => {
|
|
5
|
+
exec(`docker-compose -f ${__dirname}/docker-compose-testbed.yaml up -d`, (err, stdout, stderr) => {
|
|
6
|
+
setTimeout(() => {
|
|
7
|
+
t.end(err);
|
|
8
|
+
}, 2000);
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const test = require('tape').test ;
|
|
2
|
+
const exec = require('child_process').exec ;
|
|
3
|
+
|
|
4
|
+
test('stopping docker network..', (t) => {
|
|
5
|
+
t.timeoutAfter(10000);
|
|
6
|
+
exec(`docker-compose -f ${__dirname}/docker-compose-testbed.yaml down`, (err, stdout, stderr) => {
|
|
7
|
+
//console.log(`stderr: ${stderr}`);
|
|
8
|
+
process.exit(0);
|
|
9
|
+
});
|
|
10
|
+
t.end() ;
|
|
11
|
+
});
|
|
12
|
+
|
package/test/ibm.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const test = require('tape').test ;
|
|
2
|
+
const config = require('config');
|
|
3
|
+
const opts = config.get('redis');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const logger = require('pino')({level: 'error'});
|
|
6
|
+
process.on('unhandledRejection', (reason, p) => {
|
|
7
|
+
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const stats = {
|
|
11
|
+
increment: () => {},
|
|
12
|
+
histogram: () => {}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
test('IBM - create access key', async(t) => {
|
|
16
|
+
const fn = require('..');
|
|
17
|
+
const {client, getIbmAccessToken} = fn(opts, logger);
|
|
18
|
+
|
|
19
|
+
if (!process.env.IBM_API_KEY ) {
|
|
20
|
+
t.pass('skipping IBM test since no IBM api_key provided');
|
|
21
|
+
t.end();
|
|
22
|
+
client.quit();
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
let obj = await getIbmAccessToken(process.env.IBM_API_KEY);
|
|
27
|
+
//console.log({obj}, 'received access token from IBM');
|
|
28
|
+
t.ok(obj.access_token && !obj.servedFromCache, 'successfull received access token from IBM');
|
|
29
|
+
|
|
30
|
+
obj = await getIbmAccessToken(process.env.IBM_API_KEY);
|
|
31
|
+
//console.log({obj}, 'received access token from IBM - second request');
|
|
32
|
+
t.ok(obj.access_token && obj.servedFromCache, 'successfully received access token from cache');
|
|
33
|
+
|
|
34
|
+
await client.flushallAsync();
|
|
35
|
+
t.end();
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
console.error(err);
|
|
39
|
+
t.end(err);
|
|
40
|
+
}
|
|
41
|
+
client.quit();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('IBM - retrieve tts voices test', async(t) => {
|
|
45
|
+
const fn = require('..');
|
|
46
|
+
const {client, getTtsVoices} = fn(opts, logger);
|
|
47
|
+
|
|
48
|
+
if (!process.env.IBM_TTS_API_KEY || !process.env.IBM_TTS_REGION) {
|
|
49
|
+
t.pass('skipping IBM test since no IBM api_key and/or region provided');
|
|
50
|
+
t.end();
|
|
51
|
+
client.quit();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const opts = {
|
|
56
|
+
vendor: 'ibm',
|
|
57
|
+
credentials: {
|
|
58
|
+
tts_api_key: process.env.IBM_TTS_API_KEY,
|
|
59
|
+
tts_region: process.env.IBM_TTS_REGION
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
const obj = await getTtsVoices(opts);
|
|
63
|
+
const {voices} = obj.result;
|
|
64
|
+
//console.log(JSON.stringify(voices));
|
|
65
|
+
t.ok(voices.length > 0 && voices[0].language,
|
|
66
|
+
`GetVoices: successfully retrieved ${voices.length} voices from IBM`);
|
|
67
|
+
|
|
68
|
+
await client.flushallAsync();
|
|
69
|
+
|
|
70
|
+
t.end();
|
|
71
|
+
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
console.error(err);
|
|
75
|
+
t.end(err);
|
|
76
|
+
}
|
|
77
|
+
client.quit();
|
|
78
|
+
});
|
package/test/index.js
ADDED
package/test/nuance.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const test = require('tape').test ;
|
|
2
|
+
const config = require('config');
|
|
3
|
+
const opts = config.get('redis');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const logger = require('pino')({level: 'error'});
|
|
6
|
+
process.on('unhandledRejection', (reason, p) => {
|
|
7
|
+
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const stats = {
|
|
11
|
+
increment: () => {},
|
|
12
|
+
histogram: () => {}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
test('Nuance tests', async(t) => {
|
|
16
|
+
const fn = require('..');
|
|
17
|
+
const {client, getTtsVoices} = fn(opts, logger);
|
|
18
|
+
|
|
19
|
+
if (!process.env.NUANCE_CLIENT_ID || !process.env.NUANCE_SECRET ) {
|
|
20
|
+
t.pass('skipping Nuance test since no Nuance client_id and secret provided');
|
|
21
|
+
t.end();
|
|
22
|
+
client.quit();
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const opts = {
|
|
27
|
+
vendor: 'nuance',
|
|
28
|
+
credentials: {
|
|
29
|
+
client_id: process.env.NUANCE_CLIENT_ID,
|
|
30
|
+
secret: process.env.NUANCE_SECRET
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
let voices = await getTtsVoices(opts);
|
|
34
|
+
//console.log(`received ${voices.length} voices from Nuance`);
|
|
35
|
+
//console.log(JSON.stringify(voices));
|
|
36
|
+
t.ok(voices.length > 0 && voices[0].language,
|
|
37
|
+
`GetVoices: successfully retrieved ${voices.length} voices from Nuance`);
|
|
38
|
+
|
|
39
|
+
await client.flushallAsync();
|
|
40
|
+
|
|
41
|
+
t.end();
|
|
42
|
+
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
console.error(err);
|
|
46
|
+
t.end(err);
|
|
47
|
+
}
|
|
48
|
+
client.quit();
|
|
49
|
+
});
|
|
50
|
+
|
package/test/synth.js
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
const test = require('tape').test;
|
|
2
|
+
const config = require('config');
|
|
3
|
+
const opts = config.get('redis');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const {makeSynthKey} = require('../lib/utils');
|
|
6
|
+
const logger = require('pino')();
|
|
7
|
+
const bent = require('bent');
|
|
8
|
+
const getJSON = bent('json')
|
|
9
|
+
|
|
10
|
+
process.on('unhandledRejection', (reason, p) => {
|
|
11
|
+
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const stats = {
|
|
15
|
+
increment: () => {
|
|
16
|
+
},
|
|
17
|
+
histogram: () => {
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
test('Google speech synth tests', async(t) => {
|
|
22
|
+
const fn = require('..');
|
|
23
|
+
const {synthAudio, client} = fn(opts, logger);
|
|
24
|
+
|
|
25
|
+
if (!process.env.GCP_FILE && !process.env.GCP_JSON_KEY) {
|
|
26
|
+
t.pass('skipping google speech synth tests since neither GCP_FILE nor GCP_JSON_KEY provided');
|
|
27
|
+
return t.end();
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const str = process.env.GCP_JSON_KEY || fs.readFileSync(process.env.GCP_FILE);
|
|
31
|
+
const creds = JSON.parse(str);
|
|
32
|
+
let opts = await synthAudio(stats, {
|
|
33
|
+
vendor: 'google',
|
|
34
|
+
credentials: {
|
|
35
|
+
credentials: {
|
|
36
|
+
client_email: creds.client_email,
|
|
37
|
+
private_key: creds.private_key,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
language: 'en-GB',
|
|
41
|
+
gender: 'MALE',
|
|
42
|
+
text: 'This is a test. This is only a test',
|
|
43
|
+
salt: 'foo.bar',
|
|
44
|
+
});
|
|
45
|
+
t.ok(!opts.servedFromCache, `successfully synthesized google audio to ${opts.filePath}`);
|
|
46
|
+
|
|
47
|
+
opts = await synthAudio(stats, {
|
|
48
|
+
vendor: 'google',
|
|
49
|
+
credentials: {
|
|
50
|
+
credentials: {
|
|
51
|
+
client_email: creds.client_email,
|
|
52
|
+
private_key: creds.private_key,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
language: 'en-GB',
|
|
56
|
+
gender: 'MALE',
|
|
57
|
+
text: 'This is a test. This is only a test',
|
|
58
|
+
});
|
|
59
|
+
t.ok(opts.servedFromCache, `successfully retrieved cached google audio from ${opts.filePath}`);
|
|
60
|
+
|
|
61
|
+
opts = await synthAudio(stats, {
|
|
62
|
+
vendor: 'google',
|
|
63
|
+
credentials: {
|
|
64
|
+
credentials: {
|
|
65
|
+
client_email: creds.client_email,
|
|
66
|
+
private_key: creds.private_key,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
disableTtsCache: true,
|
|
70
|
+
language: 'en-GB',
|
|
71
|
+
gender: 'MALE',
|
|
72
|
+
text: 'This is a test. This is only a test',
|
|
73
|
+
});
|
|
74
|
+
t.ok(!opts.servedFromCache, `successfully synthesized google audio regardless of current cache to ${opts.filePath}`);
|
|
75
|
+
} catch (err) {
|
|
76
|
+
console.error(err);
|
|
77
|
+
t.end(err);
|
|
78
|
+
}
|
|
79
|
+
client.quit();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('AWS speech synth tests', async(t) => {
|
|
83
|
+
const fn = require('..');
|
|
84
|
+
const {synthAudio, client} = fn(opts, logger);
|
|
85
|
+
|
|
86
|
+
if (!process.env.AWS_ACCESS_KEY_ID || !process.env.AWS_SECRET_ACCESS_KEY || !process.env.AWS_REGION) {
|
|
87
|
+
t.pass('skipping AWS speech synth tests since AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, or AWS_REGION not provided');
|
|
88
|
+
return t.end();
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
let opts = await synthAudio(stats, {
|
|
92
|
+
vendor: 'aws',
|
|
93
|
+
credentials: {
|
|
94
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
95
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
96
|
+
region: process.env.AWS_REGION,
|
|
97
|
+
},
|
|
98
|
+
language: 'en-US',
|
|
99
|
+
voice: 'Joey',
|
|
100
|
+
text: 'This is a test. This is only a test',
|
|
101
|
+
});
|
|
102
|
+
t.ok(!opts.servedFromCache, `successfully synthesized aws audio to ${opts.filePath}`);
|
|
103
|
+
|
|
104
|
+
opts = await synthAudio(stats, {
|
|
105
|
+
vendor: 'aws',
|
|
106
|
+
credentials: {
|
|
107
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
108
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
109
|
+
region: process.env.AWS_REGION,
|
|
110
|
+
},
|
|
111
|
+
language: 'en-US',
|
|
112
|
+
voice: 'Joey',
|
|
113
|
+
text: 'This is a test. This is only a test',
|
|
114
|
+
});
|
|
115
|
+
t.ok(opts.servedFromCache, `successfully retrieved aws audio from cache ${opts.filePath}`);
|
|
116
|
+
} catch (err) {
|
|
117
|
+
console.error(err);
|
|
118
|
+
t.end(err);
|
|
119
|
+
}
|
|
120
|
+
client.quit();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('Azure speech synth tests', async(t) => {
|
|
124
|
+
const fn = require('..');
|
|
125
|
+
const {synthAudio, client} = fn(opts, logger);
|
|
126
|
+
|
|
127
|
+
if (!process.env.MICROSOFT_API_KEY || !process.env.MICROSOFT_REGION) {
|
|
128
|
+
t.pass('skipping Microsoft speech synth tests since MICROSOFT_API_KEY or MICROSOFT_REGION not provided');
|
|
129
|
+
return t.end();
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
const longText = `Henry is best known for his six marriages, including his efforts to have his first marriage
|
|
133
|
+
(to Catherine of Aragon) annulled. His disagreement with Pope Clement VII about such an
|
|
134
|
+
annulment led Henry to initiate the English Reformation,
|
|
135
|
+
separating the Church of England from papal authority. He appointed himself Supreme Head of the Church of England
|
|
136
|
+
and dissolved convents and monasteries, for which he was excommunicated.
|
|
137
|
+
Henry is also known as "the father of the Royal Navy," as he invested heavily in the navy,
|
|
138
|
+
increasing its size from a few to more than 50 ships, and established the Navy Board.`;
|
|
139
|
+
|
|
140
|
+
let opts = await synthAudio(stats, {
|
|
141
|
+
vendor: 'microsoft',
|
|
142
|
+
credentials: {
|
|
143
|
+
api_key: process.env.MICROSOFT_API_KEY,
|
|
144
|
+
region: process.env.MICROSOFT_REGION,
|
|
145
|
+
},
|
|
146
|
+
language: 'en-US',
|
|
147
|
+
voice: 'en-US-ChristopherNeural',
|
|
148
|
+
text: longText,
|
|
149
|
+
});
|
|
150
|
+
t.ok(!opts.servedFromCache, `successfully synthesized microsoft audio to ${opts.filePath}`);
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
opts = await synthAudio(stats, {
|
|
154
|
+
vendor: 'microsoft',
|
|
155
|
+
credentials: {
|
|
156
|
+
api_key: process.env.MICROSOFT_API_KEY,
|
|
157
|
+
region: process.env.MICROSOFT_REGION,
|
|
158
|
+
},
|
|
159
|
+
language: 'en-US',
|
|
160
|
+
voice: 'en-US-ChristopherNeural',
|
|
161
|
+
text: longText,
|
|
162
|
+
});
|
|
163
|
+
t.ok(opts.servedFromCache, `successfully retrieved microsoft audio from cache ${opts.filePath}`);
|
|
164
|
+
} catch (err) {
|
|
165
|
+
console.error(err);
|
|
166
|
+
t.end(err);
|
|
167
|
+
}
|
|
168
|
+
client.quit();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('Azure custom voice speech synth tests', async(t) => {
|
|
172
|
+
const fn = require('..');
|
|
173
|
+
const {synthAudio, client} = fn(opts, logger);
|
|
174
|
+
|
|
175
|
+
if (!process.env.MICROSOFT_CUSTOM_API_KEY || !process.env.MICROSOFT_DEPLOYMENT_ID || !process.env.MICROSOFT_CUSTOM_REGION) {
|
|
176
|
+
t.pass('skipping Microsoft speech synth custom voice tests since MICROSOFT_CUSTOM_API_KEY or MICROSOFT_DEPLOYMENT_ID or MICROSOFT_CUSTOM_REGION not provided');
|
|
177
|
+
return t.end();
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
const text = 'Hi, this is my custom voice. How does it sound to you? Do I have a future as a virtual bot?';
|
|
181
|
+
let opts = await synthAudio(stats, {
|
|
182
|
+
vendor: 'microsoft',
|
|
183
|
+
credentials: {
|
|
184
|
+
api_key: process.env.MICROSOFT_CUSTOM_API_KEY,
|
|
185
|
+
region: process.env.MICROSOFT_CUSTOM_REGION,
|
|
186
|
+
use_custom_tts: true,
|
|
187
|
+
custom_tts_endpoint: process.env.MICROSOFT_DEPLOYMENT_ID,
|
|
188
|
+
},
|
|
189
|
+
language: 'en-US',
|
|
190
|
+
voice: process.env.MICROSOFT_CUSTOM_VOICE,
|
|
191
|
+
text,
|
|
192
|
+
});
|
|
193
|
+
t.ok(!opts.servedFromCache, `successfully synthesized microsoft audio to ${opts.filePath}`);
|
|
194
|
+
|
|
195
|
+
opts = await synthAudio(stats, {
|
|
196
|
+
vendor: 'microsoft',
|
|
197
|
+
credentials: {
|
|
198
|
+
api_key: process.env.MICROSOFT_CUSTOM_API_KEY,
|
|
199
|
+
region: process.env.MICROSOFT_CUSTOM_REGION,
|
|
200
|
+
use_custom_tts: true,
|
|
201
|
+
custom_tts_endpoint: process.env.MICROSOFT_DEPLOYMENT_ID,
|
|
202
|
+
},
|
|
203
|
+
language: 'en-US',
|
|
204
|
+
voice: process.env.MICROSOFT_CUSTOM_VOICE,
|
|
205
|
+
text,
|
|
206
|
+
});
|
|
207
|
+
t.ok(opts.servedFromCache, `successfully retrieved microsoft custom voice audio from cache ${opts.filePath}`);
|
|
208
|
+
} catch (err) {
|
|
209
|
+
console.error(err);
|
|
210
|
+
t.end(err);
|
|
211
|
+
}
|
|
212
|
+
client.quit();
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test('Nuance speech synth tests', async(t) => {
|
|
216
|
+
const fn = require('..');
|
|
217
|
+
const {synthAudio, client} = fn(opts, logger);
|
|
218
|
+
|
|
219
|
+
if (!process.env.NUANCE_CLIENT_ID || !process.env.NUANCE_SECRET) {
|
|
220
|
+
t.pass('skipping Nuance speech synth tests since NUANCE_CLIENT_ID or NUANCE_SECRET not provided');
|
|
221
|
+
return t.end();
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
let opts = await synthAudio(stats, {
|
|
225
|
+
vendor: 'nuance',
|
|
226
|
+
credentials: {
|
|
227
|
+
client_id: process.env.NUANCE_CLIENT_ID,
|
|
228
|
+
secret: process.env.NUANCE_SECRET,
|
|
229
|
+
},
|
|
230
|
+
language: 'en-US',
|
|
231
|
+
voice: 'Evan',
|
|
232
|
+
text: 'This is a test. This is only a test',
|
|
233
|
+
});
|
|
234
|
+
t.ok(!opts.servedFromCache, `successfully synthesized nuance audio to ${opts.filePath}`);
|
|
235
|
+
|
|
236
|
+
opts = await synthAudio(stats, {
|
|
237
|
+
vendor: 'nuance',
|
|
238
|
+
credentials: {
|
|
239
|
+
client_id: process.env.NUANCE_CLIENT_ID,
|
|
240
|
+
secret: process.env.NUANCE_SECRET,
|
|
241
|
+
},
|
|
242
|
+
language: 'en-US',
|
|
243
|
+
voice: 'Evan',
|
|
244
|
+
text: 'This is a test. This is only a test',
|
|
245
|
+
});
|
|
246
|
+
t.ok(opts.servedFromCache, `successfully retrieved nuance audio from cache ${opts.filePath}`);
|
|
247
|
+
} catch (err) {
|
|
248
|
+
console.error(err);
|
|
249
|
+
t.end(err);
|
|
250
|
+
}
|
|
251
|
+
client.quit();
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test('Nvidia speech synth tests', async(t) => {
|
|
255
|
+
const fn = require('..');
|
|
256
|
+
const {synthAudio, client} = fn(opts, logger);
|
|
257
|
+
|
|
258
|
+
if (!process.env.RIVA_URI) {
|
|
259
|
+
t.pass('skipping Nvidia speech synth tests since RIVA_URI not provided');
|
|
260
|
+
return t.end();
|
|
261
|
+
}
|
|
262
|
+
try {
|
|
263
|
+
let opts = await synthAudio(stats, {
|
|
264
|
+
vendor: 'nvidia',
|
|
265
|
+
credentials: {
|
|
266
|
+
riva_uri: process.env.RIVA_URI,
|
|
267
|
+
},
|
|
268
|
+
language: 'en-US',
|
|
269
|
+
voice: 'English-US.Female-1',
|
|
270
|
+
text: 'This is a test. This is only a test',
|
|
271
|
+
});
|
|
272
|
+
t.ok(!opts.servedFromCache, `successfully synthesized nuance audio to ${opts.filePath}`);
|
|
273
|
+
|
|
274
|
+
opts = await synthAudio(stats, {
|
|
275
|
+
vendor: 'nvidia',
|
|
276
|
+
credentials: {
|
|
277
|
+
riva_uri: process.env.RIVA_URI,
|
|
278
|
+
},
|
|
279
|
+
language: 'en-US',
|
|
280
|
+
voice: 'English-US.Female-1',
|
|
281
|
+
text: 'This is a test. This is only a test',
|
|
282
|
+
});
|
|
283
|
+
t.ok(opts.servedFromCache, `successfully retrieved nuance audio from cache ${opts.filePath}`);
|
|
284
|
+
} catch (err) {
|
|
285
|
+
console.error(err);
|
|
286
|
+
t.end(err);
|
|
287
|
+
}
|
|
288
|
+
client.quit();
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test('IBM watson speech synth tests', async(t) => {
|
|
292
|
+
const fn = require('..');
|
|
293
|
+
const {synthAudio, client} = fn(opts, logger);
|
|
294
|
+
|
|
295
|
+
if (!process.env.IBM_TTS_API_KEY || !process.env.IBM_TTS_REGION) {
|
|
296
|
+
t.pass('skipping IBM Watson speech synth tests since IBM_TTS_API_KEY or IBM_TTS_API_KEY not provided');
|
|
297
|
+
return t.end();
|
|
298
|
+
}
|
|
299
|
+
const text = `<speak> Hi there and welcome to jambones! jambones is the <sub alias="seapass">CPaaS</sub> designed with the needs of communication service providers in mind. This is an example of simple text-to-speech, but there is so much more you can do. Try us out!</speak>`;
|
|
300
|
+
try {
|
|
301
|
+
let opts = await synthAudio(stats, {
|
|
302
|
+
vendor: 'ibm',
|
|
303
|
+
credentials: {
|
|
304
|
+
tts_api_key: process.env.IBM_TTS_API_KEY,
|
|
305
|
+
tts_region: process.env.IBM_TTS_REGION,
|
|
306
|
+
},
|
|
307
|
+
language: 'en-US',
|
|
308
|
+
voice: 'en-US_AllisonV2Voice',
|
|
309
|
+
text,
|
|
310
|
+
});
|
|
311
|
+
t.ok(!opts.servedFromCache, `successfully synthesized ibm audio to ${opts.filePath}`);
|
|
312
|
+
|
|
313
|
+
opts = await synthAudio(stats, {
|
|
314
|
+
vendor: 'ibm',
|
|
315
|
+
credentials: {
|
|
316
|
+
tts_api_key: process.env.IBM_TTS_API_KEY,
|
|
317
|
+
tts_region: process.env.IBM_TTS_REGION,
|
|
318
|
+
},
|
|
319
|
+
language: 'en-US',
|
|
320
|
+
voice: 'en-US_AllisonV2Voice',
|
|
321
|
+
text,
|
|
322
|
+
});
|
|
323
|
+
t.ok(opts.servedFromCache, `successfully retrieved ibm audio from cache ${opts.filePath}`);
|
|
324
|
+
} catch (err) {
|
|
325
|
+
console.error(JSON.stringify(err));
|
|
326
|
+
t.end(err);
|
|
327
|
+
}
|
|
328
|
+
client.quit();
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
test('Custom Vendor speech synth tests', async(t) => {
|
|
332
|
+
const fn = require('..');
|
|
333
|
+
const {synthAudio, client} = fn(opts, logger);
|
|
334
|
+
|
|
335
|
+
try {
|
|
336
|
+
let opts = await synthAudio(stats, {
|
|
337
|
+
vendor: 'custom:somethingnew',
|
|
338
|
+
credentials: {
|
|
339
|
+
use_for_tts: 1,
|
|
340
|
+
custom_tts_url: "http://127.0.0.1:3100/somethingnew",
|
|
341
|
+
auth_token: 'some_jwt_token'
|
|
342
|
+
},
|
|
343
|
+
language: 'en-US',
|
|
344
|
+
voice: 'English-US.Female-1',
|
|
345
|
+
text: 'This is a test. This is only a test',
|
|
346
|
+
});
|
|
347
|
+
t.ok(!opts.servedFromCache, `successfully synthesized custom vendor audio to ${opts.filePath}`);
|
|
348
|
+
let obj = await getJSON(`http://127.0.0.1:3100/lastRequest/somethingnew`);
|
|
349
|
+
t.ok(obj.headers.Authorization == 'Bearer some_jwt_token', 'Custom Vendor Authentication Header is correct');
|
|
350
|
+
t.ok(obj.body.language == 'en-US', 'Custom Vendor Language is correct');
|
|
351
|
+
t.ok(obj.body.format == 'audio/mpeg', 'Custom Vendor format is correct');
|
|
352
|
+
t.ok(obj.body.voice == 'English-US.Female-1', 'Custom Vendor voice is correct');
|
|
353
|
+
t.ok(obj.body.type == 'text', 'Custom Vendor type is correct');
|
|
354
|
+
t.ok(obj.body.text == 'This is a test. This is only a test', 'Custom Vendor text is correct');
|
|
355
|
+
|
|
356
|
+
opts = await synthAudio(stats, {
|
|
357
|
+
vendor: 'custom:somethingnew2',
|
|
358
|
+
credentials: {
|
|
359
|
+
use_for_tts: 1,
|
|
360
|
+
custom_tts_url: "http://127.0.0.1:3100/somethingnew2",
|
|
361
|
+
auth_token: 'some_jwt_token'
|
|
362
|
+
},
|
|
363
|
+
language: 'en-US',
|
|
364
|
+
voice: 'English-US.Female-1',
|
|
365
|
+
text: '<speak>This is a test. This is only a test</speak>',
|
|
366
|
+
});
|
|
367
|
+
t.ok(!opts.servedFromCache, `successfully synthesized Custom Vendor audio to ${opts.filePath}`);
|
|
368
|
+
obj = await getJSON(`http://127.0.0.1:3100/lastRequest/somethingnew2`);
|
|
369
|
+
t.ok(obj.body.type == 'ssml', 'Custom Vendor type is correct');
|
|
370
|
+
t.ok(obj.body.text == '<speak>This is a test. This is only a test</speak>');
|
|
371
|
+
} catch (err) {
|
|
372
|
+
console.error(err);
|
|
373
|
+
t.end(err);
|
|
374
|
+
}
|
|
375
|
+
client.quit();
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
test('TTS Cache tests', async(t) => {
|
|
379
|
+
const fn = require('..');
|
|
380
|
+
const {purgeTtsCache, client} = fn(opts, logger);
|
|
381
|
+
|
|
382
|
+
try {
|
|
383
|
+
// save some random tts keys to cache
|
|
384
|
+
const minRecords = 8;
|
|
385
|
+
for (const i in Array(minRecords).fill(0)) {
|
|
386
|
+
await client.setAsync(makeSynthKey({vendor: i, language: i, voice: i, engine: i, text: i}), i);
|
|
387
|
+
}
|
|
388
|
+
const {purgedCount} = await purgeTtsCache();
|
|
389
|
+
t.ok(purgedCount >= minRecords, `successfully purged at least ${minRecords} tts records from cache`);
|
|
390
|
+
|
|
391
|
+
const cached = (await client.keysAsync('tts:*')).length;
|
|
392
|
+
t.equal(cached, 0, `successfully purged all tts records from cache`);
|
|
393
|
+
|
|
394
|
+
} catch (err) {
|
|
395
|
+
console.error(JSON.stringify(err));
|
|
396
|
+
t.end(err);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
try {
|
|
400
|
+
// save some random tts keys to cache
|
|
401
|
+
for (const i in Array(10).fill(0)) {
|
|
402
|
+
await client.setAsync(makeSynthKey({vendor: i, language: i, voice: i, engine: i, text: i}), i);
|
|
403
|
+
}
|
|
404
|
+
// save a specific key to tts cache
|
|
405
|
+
const opts = {vendor: 'aws', language: 'en-US', voice: 'MALE', engine: 'Engine', text: 'Hello World!'};
|
|
406
|
+
await client.setAsync(makeSynthKey(opts), opts.text);
|
|
407
|
+
|
|
408
|
+
const {purgedCount} = await purgeTtsCache({all: false, ...opts});
|
|
409
|
+
t.ok(purgedCount === 1, `successfully purged one specific tts record from cache`);
|
|
410
|
+
|
|
411
|
+
// returns error for unknown key
|
|
412
|
+
const {purgedCount: purgedCountWhenErrored, error} = await purgeTtsCache({
|
|
413
|
+
all: false,
|
|
414
|
+
vendor: 'non-existing',
|
|
415
|
+
language: 'non-existing',
|
|
416
|
+
voice: 'non-existing',
|
|
417
|
+
});
|
|
418
|
+
t.ok(purgedCountWhenErrored === 0, `purged no records when specified key was not found`);
|
|
419
|
+
t.ok(error, `error returned when specified key was not found`);
|
|
420
|
+
|
|
421
|
+
// make sure other tts keys are still there
|
|
422
|
+
const cached = (await client.keysAsync('tts:*')).length;
|
|
423
|
+
t.ok(cached >= 1, `successfully kept all non-specified tts records in cache`);
|
|
424
|
+
|
|
425
|
+
} catch (err) {
|
|
426
|
+
console.error(JSON.stringify(err));
|
|
427
|
+
t.end(err);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
client.quit();
|
|
431
|
+
});
|