@jambonz/speech-utils 0.2.15 → 0.2.16
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/lib/synth-audio.js +63 -1
- package/package.json +1 -1
- package/test/synth.js +29 -0
package/lib/synth-audio.js
CHANGED
|
@@ -98,7 +98,7 @@ async function synthAudio(client, createHash, retrieveHash, logger, stats, { acc
|
|
|
98
98
|
logger = logger || noopLogger;
|
|
99
99
|
|
|
100
100
|
assert.ok(['google', 'aws', 'polly', 'microsoft', 'wellsaid', 'nuance', 'nvidia', 'ibm', 'elevenlabs',
|
|
101
|
-
'whisper', 'deepgram', 'playht', 'rimelabs', 'verbio', 'cartesia', 'inworld'].includes(vendor) ||
|
|
101
|
+
'whisper', 'deepgram', 'playht', 'rimelabs', 'verbio', 'cartesia', 'inworld', 'resemble'].includes(vendor) ||
|
|
102
102
|
vendor.startsWith('custom'),
|
|
103
103
|
`synthAudio supported vendors are google, aws, microsoft, nuance, nvidia and wellsaid ..etc, not ${vendor}`);
|
|
104
104
|
if ('google' === vendor) {
|
|
@@ -166,6 +166,9 @@ async function synthAudio(client, createHash, retrieveHash, logger, stats, { acc
|
|
|
166
166
|
} else if ('cartesia' === vendor) {
|
|
167
167
|
assert.ok(credentials.api_key, 'synthAudio requires api_key when cartesia is used');
|
|
168
168
|
assert.ok(credentials.model_id, 'synthAudio requires model_id when cartesia is used');
|
|
169
|
+
} else if (vendor === 'resemble') {
|
|
170
|
+
assert.ok(voice, 'synthAudio requires voice when resemble is used');
|
|
171
|
+
assert.ok(credentials.api_key, 'synthAudio requires api_key when resemble is used');
|
|
169
172
|
}
|
|
170
173
|
|
|
171
174
|
const key = makeSynthKey({
|
|
@@ -263,6 +266,10 @@ async function synthAudio(client, createHash, retrieveHash, logger, stats, { acc
|
|
|
263
266
|
audioData = await synthDeepgram(logger, {credentials, stats, model, text,
|
|
264
267
|
renderForCaching, disableTtsStreaming});
|
|
265
268
|
break;
|
|
269
|
+
case 'resemble':
|
|
270
|
+
audioData = await synthResemble(logger, {
|
|
271
|
+
credentials, stats, voice, text, options, renderForCaching, disableTtsStreaming});
|
|
272
|
+
break;
|
|
266
273
|
case vendor.startsWith('custom') ? vendor : 'cant_match_value':
|
|
267
274
|
audioData = await synthCustomVendor(logger,
|
|
268
275
|
{credentials, stats, language, voice, text});
|
|
@@ -1285,6 +1292,61 @@ const synthCartesia = async(logger, {
|
|
|
1285
1292
|
|
|
1286
1293
|
};
|
|
1287
1294
|
|
|
1295
|
+
const synthResemble = async(logger, {
|
|
1296
|
+
credentials, options, stats, voice, text, renderForCaching, disableTtsStreaming
|
|
1297
|
+
}) => {
|
|
1298
|
+
const {api_key, resemble_tts_uri} = credentials;
|
|
1299
|
+
const {project_uuid, use_hd} = options || {};
|
|
1300
|
+
|
|
1301
|
+
/* default to using the streaming interface, unless disabled by env var OR we want just a cache file */
|
|
1302
|
+
if (!JAMBONES_DISABLE_TTS_STREAMING && !renderForCaching && !disableTtsStreaming) {
|
|
1303
|
+
let params = '';
|
|
1304
|
+
params += `{api_key=${api_key}`;
|
|
1305
|
+
params += ',vendor=resemble';
|
|
1306
|
+
params += `,voice=${voice}`;
|
|
1307
|
+
params += ',write_cache_file=1';
|
|
1308
|
+
if (project_uuid) params += `,project_uuid=${project_uuid}`;
|
|
1309
|
+
if (use_hd) params += `,use_hd=${use_hd}`;
|
|
1310
|
+
if (resemble_tts_uri) params += `,endpoint=${resemble_tts_uri}`;
|
|
1311
|
+
|
|
1312
|
+
params += '}';
|
|
1313
|
+
|
|
1314
|
+
return {
|
|
1315
|
+
filePath: `say:${params}${text.replace(/\n/g, ' ').replace(/\r/g, ' ')}`,
|
|
1316
|
+
servedFromCache: false,
|
|
1317
|
+
rtt: 0
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
try {
|
|
1322
|
+
const baseUrl = resemble_tts_uri || 'https://f.cluster.resemble.ai';
|
|
1323
|
+
const post = bent(baseUrl, 'POST', 'buffer', {
|
|
1324
|
+
'Authorization': `Bearer ${api_key}`,
|
|
1325
|
+
'Content-Type': 'application/json'
|
|
1326
|
+
});
|
|
1327
|
+
const response = await post('/synthesize', {
|
|
1328
|
+
voice_uuid: voice,
|
|
1329
|
+
data: text,
|
|
1330
|
+
sample_rate: 8000,
|
|
1331
|
+
output_format: 'mp3',
|
|
1332
|
+
...(project_uuid && {project_uuid}),
|
|
1333
|
+
...(use_hd && {use_hd}),
|
|
1334
|
+
});
|
|
1335
|
+
|
|
1336
|
+
const json = JSON.parse(response.toString('utf8'));
|
|
1337
|
+
const audioContent = Buffer.from(json.audio_content, 'base64');
|
|
1338
|
+
return {
|
|
1339
|
+
audioContent,
|
|
1340
|
+
extension: 'mp3',
|
|
1341
|
+
sampleRate: 8000
|
|
1342
|
+
};
|
|
1343
|
+
} catch (err) {
|
|
1344
|
+
logger.info({err}, 'synth Elevenlabs returned error');
|
|
1345
|
+
stats.increment('tts.count', ['vendor:elevenlabs', 'accepted:no']);
|
|
1346
|
+
throw err;
|
|
1347
|
+
}
|
|
1348
|
+
};
|
|
1349
|
+
|
|
1288
1350
|
const getFileExtFromMime = (mime) => {
|
|
1289
1351
|
switch (mime) {
|
|
1290
1352
|
case 'audio/wav':
|
package/package.json
CHANGED
package/test/synth.js
CHANGED
|
@@ -750,6 +750,35 @@ test('inworld speech synth', async(t) => {
|
|
|
750
750
|
client.quit();
|
|
751
751
|
});
|
|
752
752
|
|
|
753
|
+
test('resemble speech synth', async(t) => {
|
|
754
|
+
const fn = require('..');
|
|
755
|
+
const {synthAudio, client} = fn(opts, logger);
|
|
756
|
+
|
|
757
|
+
if (!process.env.RESEMBLE_API_KEY) {
|
|
758
|
+
t.pass('skipping resemble speech synth tests since RESEMBLE_API_KEY is not provided');
|
|
759
|
+
return t.end();
|
|
760
|
+
}
|
|
761
|
+
const text = '<speak prompt="Speak in an excited, upbeat tone">Hello from Resemble!</speak>';
|
|
762
|
+
try {
|
|
763
|
+
const opts = await synthAudio(stats, {
|
|
764
|
+
vendor: 'resemble',
|
|
765
|
+
credentials: {
|
|
766
|
+
api_key: process.env.RESEMBLE_API_KEY,
|
|
767
|
+
},
|
|
768
|
+
language: 'en',
|
|
769
|
+
voice: '3f5fb9f1',
|
|
770
|
+
text,
|
|
771
|
+
renderForCaching: true
|
|
772
|
+
});
|
|
773
|
+
t.ok(!opts.servedFromCache, `successfully synthesized resemble audio to ${opts.filePath}`);
|
|
774
|
+
|
|
775
|
+
} catch (err) {
|
|
776
|
+
console.error(JSON.stringify(err));
|
|
777
|
+
t.end(err);
|
|
778
|
+
}
|
|
779
|
+
client.quit();
|
|
780
|
+
});
|
|
781
|
+
|
|
753
782
|
test('rimelabs speech synth tests mist', async(t) => {
|
|
754
783
|
const fn = require('..');
|
|
755
784
|
const {synthAudio, client} = fn(opts, logger);
|