@jambonz/speech-utils 0.1.0 → 0.1.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/index.js +1 -0
- package/lib/constants.js +3 -0
- package/lib/get-ibm-access-token.js +1 -1
- package/lib/get-nuance-access-token.js +1 -1
- package/lib/get-tts-voices.js +27 -1
- package/lib/get-verbio-token.js +51 -0
- package/lib/synth-audio.js +56 -4
- package/lib/utils.js +8 -1
- package/package.json +1 -1
- package/test/list-voices.js +26 -0
- package/test/synth.js +34 -0
package/index.js
CHANGED
|
@@ -14,6 +14,7 @@ module.exports = (opts, logger) => {
|
|
|
14
14
|
purgeTtsCache: require('./lib/purge-tts-cache').bind(null, client, logger),
|
|
15
15
|
addFileToCache: require('./lib/add-file-to-cache').bind(null, client, logger),
|
|
16
16
|
synthAudio: require('./lib/synth-audio').bind(null, client, createHash, retrieveHash, logger),
|
|
17
|
+
getVerbioAccessToken: require('./lib/get-verbio-token').bind(null, client, logger),
|
|
17
18
|
getNuanceAccessToken: require('./lib/get-nuance-access-token').bind(null, client, logger),
|
|
18
19
|
getIbmAccessToken: require('./lib/get-ibm-access-token').bind(null, client, logger),
|
|
19
20
|
getAwsAuthToken: require('./lib/get-aws-sts-token').bind(null, logger, createHash, retrieveHash),
|
package/lib/constants.js
ADDED
|
@@ -2,8 +2,8 @@ const formurlencoded = require('form-urlencoded');
|
|
|
2
2
|
const {Pool} = require('undici');
|
|
3
3
|
const pool = new Pool('https://iam.cloud.ibm.com');
|
|
4
4
|
const {makeIbmKey, noopLogger} = require('./utils');
|
|
5
|
+
const { HTTP_TIMEOUT } = require('./constants');
|
|
5
6
|
const debug = require('debug')('jambonz:realtimedb-helpers');
|
|
6
|
-
const HTTP_TIMEOUT = 5000;
|
|
7
7
|
|
|
8
8
|
async function getIbmAccessToken(client, logger, apiKey) {
|
|
9
9
|
logger = logger || noopLogger;
|
|
@@ -2,8 +2,8 @@ const formurlencoded = require('form-urlencoded');
|
|
|
2
2
|
const {Pool} = require('undici');
|
|
3
3
|
const pool = new Pool('https://auth.crt.nuance.com');
|
|
4
4
|
const {makeNuanceKey, makeBasicAuthHeader, noopLogger} = require('./utils');
|
|
5
|
+
const { HTTP_TIMEOUT } = require('./constants');
|
|
5
6
|
const debug = require('debug')('jambonz:realtimedb-helpers');
|
|
6
|
-
const HTTP_TIMEOUT = 5000;
|
|
7
7
|
|
|
8
8
|
async function getNuanceAccessToken(client, logger, clientId, secret, scope) {
|
|
9
9
|
logger = logger || noopLogger;
|
package/lib/get-tts-voices.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
const assert = require('assert');
|
|
2
2
|
const {noopLogger, createNuanceClient, createKryptonClient} = require('./utils');
|
|
3
3
|
const getNuanceAccessToken = require('./get-nuance-access-token');
|
|
4
|
+
const getVerbioAccessToken = require('./get-verbio-token');
|
|
4
5
|
const {GetVoicesRequest, Voice} = require('../stubs/nuance/synthesizer_pb');
|
|
5
6
|
const TextToSpeechV1 = require('ibm-watson/text-to-speech/v1');
|
|
6
7
|
const { IamAuthenticator } = require('ibm-watson/auth');
|
|
7
8
|
const ttsGoogle = require('@google-cloud/text-to-speech');
|
|
8
9
|
const { PollyClient, DescribeVoicesCommand } = require('@aws-sdk/client-polly');
|
|
9
10
|
const getAwsAuthToken = require('./get-aws-sts-token');
|
|
11
|
+
const {Pool} = require('undici');
|
|
12
|
+
const { HTTP_TIMEOUT } = require('./constants');
|
|
13
|
+
const verbioVoicePool = new Pool('https://us.rest.speechcenter.verbio.com');
|
|
10
14
|
|
|
11
15
|
const getIbmVoices = async(client, logger, credentials) => {
|
|
12
16
|
const {tts_region, tts_api_key} = credentials;
|
|
@@ -117,6 +121,26 @@ const getAwsVoices = async(_client, createHash, retrieveHash, logger, credential
|
|
|
117
121
|
}
|
|
118
122
|
};
|
|
119
123
|
|
|
124
|
+
const getVerbioVoices = async(client, logger, credentials) => {
|
|
125
|
+
try {
|
|
126
|
+
const access_token = await getVerbioAccessToken(client, logger, credentials);
|
|
127
|
+
const { body} = await verbioVoicePool.request({
|
|
128
|
+
path: '/api/v1/voices',
|
|
129
|
+
method: 'GET',
|
|
130
|
+
headers: {
|
|
131
|
+
'Authorization': `Bearer ${access_token.access_token}`,
|
|
132
|
+
'User-Agent': 'jambonz'
|
|
133
|
+
},
|
|
134
|
+
timeout: HTTP_TIMEOUT,
|
|
135
|
+
followRedirects: false
|
|
136
|
+
});
|
|
137
|
+
return await body.json();
|
|
138
|
+
} catch (err) {
|
|
139
|
+
logger.info({err}, 'getVerbioVoices - failed to list voices for Verbio');
|
|
140
|
+
throw err;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
120
144
|
/**
|
|
121
145
|
* Synthesize speech to an mp3 file, and also cache the generated speech
|
|
122
146
|
* in redis (base64 format) for 24 hours so as to avoid unnecessarily paying
|
|
@@ -136,7 +160,7 @@ const getAwsVoices = async(_client, createHash, retrieveHash, logger, credential
|
|
|
136
160
|
async function getTtsVoices(client, createHash, retrieveHash, logger, {vendor, credentials}) {
|
|
137
161
|
logger = logger || noopLogger;
|
|
138
162
|
|
|
139
|
-
assert.ok(['nuance', 'ibm', 'google', 'aws', 'polly'].includes(vendor),
|
|
163
|
+
assert.ok(['nuance', 'ibm', 'google', 'aws', 'polly', 'verbio'].includes(vendor),
|
|
140
164
|
`getTtsVoices not supported for vendor ${vendor}`);
|
|
141
165
|
|
|
142
166
|
switch (vendor) {
|
|
@@ -149,6 +173,8 @@ async function getTtsVoices(client, createHash, retrieveHash, logger, {vendor, c
|
|
|
149
173
|
case 'aws':
|
|
150
174
|
case 'polly':
|
|
151
175
|
return getAwsVoices(client, createHash, retrieveHash, logger, credentials);
|
|
176
|
+
case 'verbio':
|
|
177
|
+
return getVerbioVoices(client, logger, credentials);
|
|
152
178
|
default:
|
|
153
179
|
break;
|
|
154
180
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const {Pool} = require('undici');
|
|
2
|
+
const { noopLogger, makeVerbioKey } = require('./utils');
|
|
3
|
+
const { HTTP_TIMEOUT } = require('./constants');
|
|
4
|
+
const pool = new Pool('https://auth.speechcenter.verbio.com:444');
|
|
5
|
+
const debug = require('debug')('jambonz:realtimedb-helpers');
|
|
6
|
+
|
|
7
|
+
async function getVerbioAccessToken(client, logger, credentials) {
|
|
8
|
+
logger = logger || noopLogger;
|
|
9
|
+
const { client_id, client_secret } = credentials;
|
|
10
|
+
try {
|
|
11
|
+
const key = makeVerbioKey(client_id);
|
|
12
|
+
const access_token = await client.get(key);
|
|
13
|
+
if (access_token) {
|
|
14
|
+
return {access_token, servedFromCache: true};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const payload = {
|
|
18
|
+
client_id,
|
|
19
|
+
client_secret
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const {statusCode, headers, body} = await pool.request({
|
|
23
|
+
path: '/api/v1/token',
|
|
24
|
+
method: 'POST',
|
|
25
|
+
headers: {
|
|
26
|
+
'Content-Type': 'application/json',
|
|
27
|
+
'User-Agent': 'jambonz'
|
|
28
|
+
},
|
|
29
|
+
body: JSON.stringify(payload),
|
|
30
|
+
timeout: HTTP_TIMEOUT,
|
|
31
|
+
followRedirects: false
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (200 !== statusCode) {
|
|
35
|
+
logger.debug({statusCode, headers, body: await body.text()}, 'error fetching access token from Verbio');
|
|
36
|
+
const err = new Error();
|
|
37
|
+
err.statusCode = statusCode;
|
|
38
|
+
throw err;
|
|
39
|
+
}
|
|
40
|
+
const json = await body.json();
|
|
41
|
+
const expiry = Math.floor(json.expiration_time - Date.now() / 1000 - 30);
|
|
42
|
+
await client.set(key, json.access_token, 'EX', expiry);
|
|
43
|
+
return {...json, servedFromCache: false};
|
|
44
|
+
} catch (err) {
|
|
45
|
+
debug(err, `getVerbioAccessToken: Error retrieving Verbio access token for client_id ${client_id}`);
|
|
46
|
+
logger.error(err, `getVerbioAccessToken: Error retrieving Verbio access token for client_id ${client_id}`);
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = getVerbioAccessToken;
|
package/lib/synth-audio.js
CHANGED
|
@@ -22,6 +22,7 @@ const {
|
|
|
22
22
|
noopLogger
|
|
23
23
|
} = require('./utils');
|
|
24
24
|
const getNuanceAccessToken = require('./get-nuance-access-token');
|
|
25
|
+
const getVerbioAccessToken = require('./get-verbio-token');
|
|
25
26
|
const {
|
|
26
27
|
SynthesisRequest,
|
|
27
28
|
Voice,
|
|
@@ -86,7 +87,7 @@ async function synthAudio(client, createHash, retrieveHash, logger, stats, { acc
|
|
|
86
87
|
logger = logger || noopLogger;
|
|
87
88
|
|
|
88
89
|
assert.ok(['google', 'aws', 'polly', 'microsoft', 'wellsaid', 'nuance', 'nvidia', 'ibm', 'elevenlabs',
|
|
89
|
-
'whisper', 'deepgram', 'playht', 'rimelabs'].includes(vendor) ||
|
|
90
|
+
'whisper', 'deepgram', 'playht', 'rimelabs', 'verbio'].includes(vendor) ||
|
|
90
91
|
vendor.startsWith('custom'),
|
|
91
92
|
`synthAudio supported vendors are google, aws, microsoft, nuance, nvidia and wellsaid ..etc, not ${vendor}`);
|
|
92
93
|
if ('google' === vendor) {
|
|
@@ -139,6 +140,10 @@ async function synthAudio(client, createHash, retrieveHash, logger, stats, { acc
|
|
|
139
140
|
assert.ok(credentials.api_key, 'synthAudio requires api_key when whisper is used');
|
|
140
141
|
} else if (vendor.startsWith('custom')) {
|
|
141
142
|
assert.ok(credentials.custom_tts_url, `synthAudio requires custom_tts_url in credentials when ${vendor} is used`);
|
|
143
|
+
} else if ('verbio' === vendor) {
|
|
144
|
+
assert.ok(voice, 'synthAudio requires voice when verbio is used');
|
|
145
|
+
assert.ok(credentials.client_id, 'synthAudio requires client_id when verbio is used');
|
|
146
|
+
assert.ok(credentials.client_secret, 'synthAudio requires client_secret when verbio is used');
|
|
142
147
|
}
|
|
143
148
|
const key = makeSynthKey({
|
|
144
149
|
account_sid,
|
|
@@ -149,7 +154,7 @@ async function synthAudio(client, createHash, retrieveHash, logger, stats, { acc
|
|
|
149
154
|
text
|
|
150
155
|
});
|
|
151
156
|
let filePath;
|
|
152
|
-
if (['nuance', 'nvidia'].includes(vendor) ||
|
|
157
|
+
if (['nuance', 'nvidia', 'verbio'].includes(vendor) ||
|
|
153
158
|
(
|
|
154
159
|
(process.env.JAMBONES_TTS_TRIM_SILENCE || !process.env.JAMBONES_DISABLE_TTS_STREAMING) &&
|
|
155
160
|
['microsoft', 'azure'].includes(vendor)
|
|
@@ -234,6 +239,11 @@ async function synthAudio(client, createHash, retrieveHash, logger, stats, { acc
|
|
|
234
239
|
credentials, stats, voice, text, renderForCaching, disableTtsStreaming});
|
|
235
240
|
if (audioBuffer?.filePath) return audioBuffer;
|
|
236
241
|
break;
|
|
242
|
+
case 'verbio':
|
|
243
|
+
audioBuffer = await synthVerbio(client, logger, {
|
|
244
|
+
credentials, stats, voice, text, renderForCaching, disableTtsStreaming});
|
|
245
|
+
if (audioBuffer?.filePath) return audioBuffer;
|
|
246
|
+
break;
|
|
237
247
|
case 'deepgram':
|
|
238
248
|
audioBuffer = await synthDeepgram(logger, {credentials, stats, model, text,
|
|
239
249
|
renderForCaching, disableTtsStreaming});
|
|
@@ -380,7 +390,7 @@ async function _synthOnPremMicrosoft(logger, {
|
|
|
380
390
|
text,
|
|
381
391
|
filePath
|
|
382
392
|
}) {
|
|
383
|
-
const {use_custom_tts, custom_tts_endpoint_url} = credentials;
|
|
393
|
+
const {use_custom_tts, custom_tts_endpoint_url, api_key} = credentials;
|
|
384
394
|
let content = text;
|
|
385
395
|
|
|
386
396
|
if (use_custom_tts && !content.startsWith('<speak')) {
|
|
@@ -404,7 +414,8 @@ async function _synthOnPremMicrosoft(logger, {
|
|
|
404
414
|
const post = bent('POST', 'buffer', {
|
|
405
415
|
'X-Microsoft-OutputFormat': trimSilence ? 'raw-8khz-16bit-mono-pcm' : 'audio-16khz-32kbitrate-mono-mp3',
|
|
406
416
|
'Content-Type': 'application/ssml+xml',
|
|
407
|
-
'User-Agent': 'Jambonz'
|
|
417
|
+
'User-Agent': 'Jambonz',
|
|
418
|
+
...(api_key && {'Ocp-Apim-Subscription-Key': api_key})
|
|
408
419
|
});
|
|
409
420
|
const mp3 = await post(custom_tts_endpoint_url, content);
|
|
410
421
|
return mp3;
|
|
@@ -452,6 +463,7 @@ const synthMicrosoft = async(logger, {
|
|
|
452
463
|
params += ',write_cache_file=1';
|
|
453
464
|
if (region) params += `,region=${region}`;
|
|
454
465
|
if (custom_tts_endpoint) params += `,endpointId=${custom_tts_endpoint}`;
|
|
466
|
+
if (custom_tts_endpoint_url) params += `,endpoint=${custom_tts_endpoint_url}`;
|
|
455
467
|
if (process.env.JAMBONES_HTTP_PROXY_IP) params += `,http_proxy_ip=${process.env.JAMBONES_HTTP_PROXY_IP}`;
|
|
456
468
|
if (process.env.JAMBONES_HTTP_PROXY_PORT) params += `,http_proxy_port=${process.env.JAMBONES_HTTP_PROXY_PORT}`;
|
|
457
469
|
params += '}';
|
|
@@ -817,6 +829,46 @@ const synthRimelabs = async(logger, {
|
|
|
817
829
|
throw err;
|
|
818
830
|
}
|
|
819
831
|
};
|
|
832
|
+
const synthVerbio = async(client, logger, {credentials, stats, voice, text, renderForCaching, disableTtsStreaming}) => {
|
|
833
|
+
//https://doc.speechcenter.verbio.com/#tag/Text-To-Speech-REST-API
|
|
834
|
+
if (text.length > 2000) {
|
|
835
|
+
throw new Error('Verbio cannot synthesize for the text length larger than 2000 characters');
|
|
836
|
+
}
|
|
837
|
+
const token = await getVerbioAccessToken(client, logger, credentials);
|
|
838
|
+
if (!process.env.JAMBONES_DISABLE_TTS_STREAMING && !renderForCaching && !disableTtsStreaming) {
|
|
839
|
+
let params = '';
|
|
840
|
+
params += `{access_token=${token.access_token}`;
|
|
841
|
+
params += ',vendor=verbio';
|
|
842
|
+
params += `,voice=${voice}`;
|
|
843
|
+
params += ',write_cache_file=1';
|
|
844
|
+
params += '}';
|
|
845
|
+
|
|
846
|
+
return {
|
|
847
|
+
filePath: `say:${params}${text.replace(/\n/g, ' ')}`,
|
|
848
|
+
servedFromCache: false,
|
|
849
|
+
rtt: 0
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
try {
|
|
854
|
+
const post = bent('https://us.rest.speechcenter.verbio.com', 'POST', 'buffer', {
|
|
855
|
+
'Authorization': `Bearer ${token.access_token}`,
|
|
856
|
+
'User-Agent': 'jambonz',
|
|
857
|
+
'Content-Type': 'application/json'
|
|
858
|
+
});
|
|
859
|
+
const r8 = await post('/api/v1/synthesize', {
|
|
860
|
+
voice_id: voice,
|
|
861
|
+
output_sample_rate: '8k',
|
|
862
|
+
output_encoding: 'pcm16',
|
|
863
|
+
text
|
|
864
|
+
});
|
|
865
|
+
return r8;
|
|
866
|
+
} catch (err) {
|
|
867
|
+
logger.info({err}, 'synth Verbio returned error');
|
|
868
|
+
stats.increment('tts.count', ['vendor:verbio', 'accepted:no']);
|
|
869
|
+
throw err;
|
|
870
|
+
}
|
|
871
|
+
};
|
|
820
872
|
|
|
821
873
|
const synthWhisper = async(logger, {credentials, stats, voice, text, renderForCaching, disableTtsStreaming}) => {
|
|
822
874
|
const {api_key, model_id, baseURL, timeout, speed} = credentials;
|
package/lib/utils.js
CHANGED
|
@@ -3,10 +3,10 @@ const {SynthesizerClient} = require('../stubs/nuance/synthesizer_grpc_pb');
|
|
|
3
3
|
const {RivaSpeechSynthesisClient} = require('../stubs/riva/proto/riva_tts_grpc_pb');
|
|
4
4
|
const {Pool} = require('undici');
|
|
5
5
|
const pool = new Pool('https://auth.crt.nuance.com');
|
|
6
|
-
const HTTP_TIMEOUT = 5000;
|
|
7
6
|
const NUANCE_AUTH_ENDPOINT = 'tts.api.nuance.com:443';
|
|
8
7
|
const grpc = require('@grpc/grpc-js');
|
|
9
8
|
const formurlencoded = require('form-urlencoded');
|
|
9
|
+
const { HTTP_TIMEOUT } = require('./constants');
|
|
10
10
|
|
|
11
11
|
const debug = require('debug')('jambonz:realtimedb-helpers');
|
|
12
12
|
/**
|
|
@@ -49,6 +49,12 @@ function makeAwsKey(awsAccessKeyId) {
|
|
|
49
49
|
return `aws:${hash.digest('hex')}`;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
function makeVerbioKey(client_id) {
|
|
53
|
+
const hash = crypto.createHash('sha1');
|
|
54
|
+
hash.update(client_id);
|
|
55
|
+
return `verbio:${hash.digest('hex')}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
52
58
|
function makeNuanceKey(clientId, secret, scope) {
|
|
53
59
|
const hash = crypto.createHash('sha1');
|
|
54
60
|
hash.update(`${clientId}:${secret}:${scope}`);
|
|
@@ -117,6 +123,7 @@ module.exports = {
|
|
|
117
123
|
makeNuanceKey,
|
|
118
124
|
makeIbmKey,
|
|
119
125
|
makeAwsKey,
|
|
126
|
+
makeVerbioKey,
|
|
120
127
|
getNuanceAccessToken,
|
|
121
128
|
createNuanceClient,
|
|
122
129
|
createKryptonClient,
|
package/package.json
CHANGED
package/test/list-voices.js
CHANGED
|
@@ -12,6 +12,32 @@ const stats = {
|
|
|
12
12
|
histogram: () => {}
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
+
test('Verbio - get Access key and voices', async(t) => {
|
|
16
|
+
const fn = require('..');
|
|
17
|
+
const {client, getTtsVoices, getVerbioAccessToken} = fn(opts, logger);
|
|
18
|
+
if (!process.env.VERBIO_CLIENT_ID || !process.env.VERBIO_CLIENT_SECRET) {
|
|
19
|
+
t.pass('skipping Verbio test since no Verbio Keys provided');
|
|
20
|
+
t.end();
|
|
21
|
+
client.quit();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const credentials = {
|
|
27
|
+
client_id: process.env.VERBIO_CLIENT_ID,
|
|
28
|
+
client_secret: process.env.VERBIO_CLIENT_SECRET
|
|
29
|
+
};
|
|
30
|
+
let obj = await getVerbioAccessToken(credentials);
|
|
31
|
+
t.ok(obj.access_token , 'successfully received access token not from cache');
|
|
32
|
+
const voices = await getTtsVoices({vendor: 'verbio', credentials});
|
|
33
|
+
t.ok(voices && voices.length != 0, 'successfully received verbio voices');
|
|
34
|
+
} catch (err) {
|
|
35
|
+
console.error(err);
|
|
36
|
+
t.end(err);
|
|
37
|
+
}
|
|
38
|
+
client.quit();
|
|
39
|
+
});
|
|
40
|
+
|
|
15
41
|
test('IBM - create access key', async(t) => {
|
|
16
42
|
const fn = require('..');
|
|
17
43
|
const {client, getIbmAccessToken} = fn(opts, logger);
|
package/test/synth.js
CHANGED
|
@@ -670,6 +670,40 @@ test('whisper speech synth tests', async(t) => {
|
|
|
670
670
|
language: 'en-US',
|
|
671
671
|
voice: 'alloy',
|
|
672
672
|
text,
|
|
673
|
+
renderForCaching: true
|
|
674
|
+
});
|
|
675
|
+
t.ok(!opts.servedFromCache, `successfully synthesized whisper audio to ${opts.filePath}`);
|
|
676
|
+
|
|
677
|
+
} catch (err) {
|
|
678
|
+
console.error(JSON.stringify(err));
|
|
679
|
+
t.end(err);
|
|
680
|
+
}
|
|
681
|
+
client.quit();
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
test('Verbio speech synth tests', async(t) => {
|
|
685
|
+
const fn = require('..');
|
|
686
|
+
const {synthAudio, client} = fn(opts, logger);
|
|
687
|
+
|
|
688
|
+
if (!process.env.VERBIO_CLIENT_ID || !process.env.VERBIO_CLIENT_SECRET) {
|
|
689
|
+
t.pass('skipping Verbio Synthesize test since no Verbio Keys provided');
|
|
690
|
+
t.end();
|
|
691
|
+
client.quit();
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const text = 'Hi there and welcome to jambones!';
|
|
696
|
+
try {
|
|
697
|
+
let opts = await synthAudio(stats, {
|
|
698
|
+
vendor: 'verbio',
|
|
699
|
+
credentials: {
|
|
700
|
+
client_id: process.env.VERBIO_CLIENT_ID,
|
|
701
|
+
client_secret: process.env.VERBIO_CLIENT_SECRET
|
|
702
|
+
},
|
|
703
|
+
language: 'en-US',
|
|
704
|
+
voice: 'tommy_en-us',
|
|
705
|
+
text,
|
|
706
|
+
renderForCaching: true
|
|
673
707
|
});
|
|
674
708
|
t.ok(!opts.servedFromCache, `successfully synthesized whisper audio to ${opts.filePath}`);
|
|
675
709
|
|