@jambonz/speech-utils 1.0.10 → 1.0.11
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 +66 -1
- package/package.json +1 -1
- package/test/synth.js +30 -0
package/lib/synth-audio.js
CHANGED
|
@@ -80,7 +80,8 @@ async function synthAudio(client, createHash, retrieveHash, logger, stats, { acc
|
|
|
80
80
|
logger = logger || noopLogger;
|
|
81
81
|
|
|
82
82
|
assert.ok(['google', 'aws', 'polly', 'microsoft', 'wellsaid', 'nvidia', 'elevenlabs',
|
|
83
|
-
'whisper', 'deepgram', 'deepgramflux', 'rimelabs', 'cartesia', '
|
|
83
|
+
'whisper', 'deepgram', 'deepgramflux', 'rimelabs', 'cartesia', 'nineninesix', 'inworld', 'resemble',
|
|
84
|
+
'murf', 'xai']
|
|
84
85
|
.includes(vendor) ||
|
|
85
86
|
vendor.startsWith('custom'),
|
|
86
87
|
`synthAudio supported vendors are google, aws, microsoft, nvidia and wellsaid ..etc, not ${vendor}`);
|
|
@@ -135,6 +136,10 @@ async function synthAudio(client, createHash, retrieveHash, logger, stats, { acc
|
|
|
135
136
|
} else if ('cartesia' === vendor) {
|
|
136
137
|
assert.ok(credentials.api_key, 'synthAudio requires api_key when cartesia is used');
|
|
137
138
|
assert.ok(credentials.model_id, 'synthAudio requires model_id when cartesia is used');
|
|
139
|
+
} else if ('nineninesix' === vendor) {
|
|
140
|
+
assert.ok(voice, 'synthAudio requires voice when nineninesix is used');
|
|
141
|
+
assert.ok(credentials.api_key, 'synthAudio requires api_key when nineninesix is used');
|
|
142
|
+
assert.ok(credentials.model_id, 'synthAudio requires model_id when nineninesix is used');
|
|
138
143
|
} else if ('murf' === vendor) {
|
|
139
144
|
assert.ok(voice, 'synthAudio requires voice when murf is used');
|
|
140
145
|
assert.ok(credentials.api_key, 'synthAudio requires api_key when murf is used');
|
|
@@ -212,6 +217,11 @@ async function synthAudio(client, createHash, retrieveHash, logger, stats, { acc
|
|
|
212
217
|
credentials, options, stats, language, voice, key, text, renderForCaching, disableTtsStreaming,
|
|
213
218
|
disableTtsCache});
|
|
214
219
|
break;
|
|
220
|
+
case 'nineninesix':
|
|
221
|
+
audioData = await synthNineninesix(logger, {
|
|
222
|
+
credentials, stats, language, voice, key, text, renderForCaching, disableTtsStreaming,
|
|
223
|
+
disableTtsCache});
|
|
224
|
+
break;
|
|
215
225
|
case 'inworld':
|
|
216
226
|
audioData = await synthInworld(logger, {
|
|
217
227
|
credentials, options, stats, language, voice, key, text, renderForCaching, disableTtsStreaming,
|
|
@@ -1423,6 +1433,61 @@ const synthCartesia = async(logger, {
|
|
|
1423
1433
|
|
|
1424
1434
|
};
|
|
1425
1435
|
|
|
1436
|
+
/* nineninesix.ai — a Cartesia-compatible API, but only raw/wav come back
|
|
1437
|
+
(mp3 is rejected), so the cache render asks for wav rather than mp3. */
|
|
1438
|
+
const synthNineninesix = async(logger, {
|
|
1439
|
+
credentials, stats, voice, language, key, text, renderForCaching, disableTtsStreaming, disableTtsCache
|
|
1440
|
+
}) => {
|
|
1441
|
+
const {api_key, model_id} = credentials;
|
|
1442
|
+
|
|
1443
|
+
/* default to using the streaming interface, unless disabled by env var OR we want just a cache file */
|
|
1444
|
+
if (!JAMBONES_DISABLE_TTS_STREAMING && !renderForCaching && !disableTtsStreaming) {
|
|
1445
|
+
let params = '{';
|
|
1446
|
+
params += `api_key=${api_key}`;
|
|
1447
|
+
params += `,playback_id=${key}`;
|
|
1448
|
+
params += `,model_id=${model_id}`;
|
|
1449
|
+
params += ',vendor=nineninesix';
|
|
1450
|
+
params += `,voice=${voice}`;
|
|
1451
|
+
params += `,write_cache_file=${disableTtsCache ? 0 : 1}`;
|
|
1452
|
+
if (language) params += `,language=${language}`;
|
|
1453
|
+
params += '}';
|
|
1454
|
+
|
|
1455
|
+
return {
|
|
1456
|
+
filePath: `say:${params}${text.replace(/\n/g, ' ').replace(/\r/g, ' ')}`,
|
|
1457
|
+
servedFromCache: false,
|
|
1458
|
+
rtt: 0
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
try {
|
|
1463
|
+
const sampleRate = 8000;
|
|
1464
|
+
const post = bent('https://api.nineninesix.ai', 'POST', 'buffer', {
|
|
1465
|
+
'Authorization': `Bearer ${api_key}`,
|
|
1466
|
+
'Content-Type': 'application/json'
|
|
1467
|
+
});
|
|
1468
|
+
const audioContent = await post('/tts/bytes', {
|
|
1469
|
+
model_id,
|
|
1470
|
+
transcript: text,
|
|
1471
|
+
voice: {mode: 'id', id: voice},
|
|
1472
|
+
...(language && {language}),
|
|
1473
|
+
output_format: {
|
|
1474
|
+
container: 'wav',
|
|
1475
|
+
encoding: 'pcm_s16le',
|
|
1476
|
+
sample_rate: sampleRate
|
|
1477
|
+
}
|
|
1478
|
+
});
|
|
1479
|
+
return {
|
|
1480
|
+
audioContent,
|
|
1481
|
+
extension: 'wav',
|
|
1482
|
+
sampleRate
|
|
1483
|
+
};
|
|
1484
|
+
} catch (err) {
|
|
1485
|
+
logger.info({err}, 'synth nineninesix returned error');
|
|
1486
|
+
stats.increment('tts.count', ['vendor:nineninesix', 'accepted:no']);
|
|
1487
|
+
throw err;
|
|
1488
|
+
}
|
|
1489
|
+
};
|
|
1490
|
+
|
|
1426
1491
|
const synthResemble = async(logger, {
|
|
1427
1492
|
credentials, options, stats, voice, key, text, renderForCaching, disableTtsStreaming, disableTtsCache
|
|
1428
1493
|
}) => {
|
package/package.json
CHANGED
package/test/synth.js
CHANGED
|
@@ -1058,6 +1058,36 @@ test('Cartesia speech synth tests', async(t) => {
|
|
|
1058
1058
|
client.quit();
|
|
1059
1059
|
});
|
|
1060
1060
|
|
|
1061
|
+
test('nineninesix speech synth tests', async(t) => {
|
|
1062
|
+
const fn = require('..');
|
|
1063
|
+
const {synthAudio, client} = fn(opts, logger);
|
|
1064
|
+
|
|
1065
|
+
if (!process.env.NINENINESIX_API_KEY) {
|
|
1066
|
+
t.pass('skipping nineninesix speech synth tests since NINENINESIX_API_KEY is not provided');
|
|
1067
|
+
return t.end();
|
|
1068
|
+
}
|
|
1069
|
+
const text = 'Hi there and welcome to jambones! ' + Date.now();
|
|
1070
|
+
try {
|
|
1071
|
+
const opts = await synthAudio(stats, {
|
|
1072
|
+
vendor: 'nineninesix',
|
|
1073
|
+
credentials: {
|
|
1074
|
+
api_key: process.env.NINENINESIX_API_KEY,
|
|
1075
|
+
model_id: 'gepard-1.0'
|
|
1076
|
+
},
|
|
1077
|
+
language: 'en',
|
|
1078
|
+
voice: '3ad7a827-7fd1-4954-bf35-47d4cc33d9ed',
|
|
1079
|
+
text,
|
|
1080
|
+
renderForCaching: true
|
|
1081
|
+
});
|
|
1082
|
+
t.ok(!opts.servedFromCache, `successfully synthed nineninesix audio to ${opts.filePath}`);
|
|
1083
|
+
|
|
1084
|
+
} catch (err) {
|
|
1085
|
+
console.error(JSON.stringify(err));
|
|
1086
|
+
t.end(err);
|
|
1087
|
+
}
|
|
1088
|
+
client.quit();
|
|
1089
|
+
});
|
|
1090
|
+
|
|
1061
1091
|
test('inworld speech synth', async(t) => {
|
|
1062
1092
|
const fn = require('..');
|
|
1063
1093
|
const {synthAudio, client} = fn(opts, logger);
|