@jambonz/speech-utils 1.0.3 → 1.0.4
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/.github/workflows/publish.yml +2 -2
- package/lib/synth-audio.js +43 -5
- package/package.json +1 -1
package/lib/synth-audio.js
CHANGED
|
@@ -1343,6 +1343,18 @@ const synthCartesia = async(logger, {
|
|
|
1343
1343
|
try {
|
|
1344
1344
|
const client = new CartesiaClient({ apiKey: api_key });
|
|
1345
1345
|
const sampleRate = 48000;
|
|
1346
|
+
|
|
1347
|
+
// omit a control unless explicitly provided (0 is a valid value, so test for nullish only)
|
|
1348
|
+
const has = (v) => v !== null && v !== undefined;
|
|
1349
|
+
/* Voice controls are model-family specific:
|
|
1350
|
+
- sonic-2 takes `experimentalControls` (emotion is an array, no volume).
|
|
1351
|
+
- sonic-3 family (sonic-3, sonic-3.5, pinned sonic-3.x snapshots) takes
|
|
1352
|
+
`generationConfig` (emotion is a string, volume supported). Match the same
|
|
1353
|
+
"starts with sonic-3" predicate the freeswitch streaming module uses
|
|
1354
|
+
(mod_cartesia_tts_streaming: strncmp(model_id, "sonic-3", ...)), so cached
|
|
1355
|
+
and streamed synthesis behave identically.
|
|
1356
|
+
Older models (sonic, sonic-english, sonic-multilingual, sonic-2024-*) take neither. */
|
|
1357
|
+
const isSonic3 = model_id?.startsWith('sonic-3');
|
|
1346
1358
|
const mp3Stream = await client.tts.bytes({
|
|
1347
1359
|
modelId: model_id,
|
|
1348
1360
|
transcript: text,
|
|
@@ -1358,16 +1370,16 @@ const synthCartesia = async(logger, {
|
|
|
1358
1370
|
),
|
|
1359
1371
|
...(model_id === 'sonic-2' && (opts.speed || opts.emotion) && {
|
|
1360
1372
|
experimentalControls: {
|
|
1361
|
-
...(opts.speed
|
|
1373
|
+
...(has(opts.speed) && {speed: opts.speed}),
|
|
1362
1374
|
...(opts.emotion && {emotion: [opts.emotion]}),
|
|
1363
1375
|
}
|
|
1364
1376
|
}),
|
|
1365
1377
|
},
|
|
1366
|
-
...(
|
|
1378
|
+
...(isSonic3 && (has(opts.speed) || has(opts.emotion) || has(opts.volume)) && {
|
|
1367
1379
|
generationConfig: {
|
|
1368
|
-
...(opts.volume
|
|
1369
|
-
...(opts.speed
|
|
1370
|
-
...(opts.emotion
|
|
1380
|
+
...(has(opts.volume) && {volume: opts.volume}),
|
|
1381
|
+
...(has(opts.speed) && {speed: opts.speed}),
|
|
1382
|
+
...(has(opts.emotion) && {emotion: opts.emotion}),
|
|
1371
1383
|
}
|
|
1372
1384
|
}),
|
|
1373
1385
|
language: language,
|
|
@@ -1391,6 +1403,32 @@ const synthCartesia = async(logger, {
|
|
|
1391
1403
|
sampleRate
|
|
1392
1404
|
};
|
|
1393
1405
|
} catch (err) {
|
|
1406
|
+
/* Cartesia's tts.bytes() uses a streaming response, so on an HTTP error the SDK
|
|
1407
|
+
throws a CartesiaError whose `body` is an unconsumed stream-wrapper object
|
|
1408
|
+
(async-iterable, yielding Uint8Array chunks) rather than the parsed error
|
|
1409
|
+
JSON. Read it so callers get a meaningful message instead of a serialized
|
|
1410
|
+
stream object. */
|
|
1411
|
+
if (err && err.body && typeof err.body !== 'string' && typeof err.body[Symbol.asyncIterator] === 'function') {
|
|
1412
|
+
try {
|
|
1413
|
+
const chunks = [];
|
|
1414
|
+
for await (const chunk of err.body) {
|
|
1415
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
1416
|
+
}
|
|
1417
|
+
const text = Buffer.concat(chunks).toString('utf8');
|
|
1418
|
+
if (text) {
|
|
1419
|
+
let parsed;
|
|
1420
|
+
try {
|
|
1421
|
+
parsed = JSON.parse(text);
|
|
1422
|
+
} catch {
|
|
1423
|
+
parsed = null;
|
|
1424
|
+
}
|
|
1425
|
+
err.message = (parsed && (parsed.error || parsed.message)) || text;
|
|
1426
|
+
err.body = parsed || text;
|
|
1427
|
+
}
|
|
1428
|
+
} catch (readErr) {
|
|
1429
|
+
logger.info({readErr}, 'synth Cartesia: failed to read error response body');
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1394
1432
|
logger.info({err}, 'synth Cartesia returned error');
|
|
1395
1433
|
stats.increment('tts.count', ['vendor:cartesia', 'accepted:no']);
|
|
1396
1434
|
throw err;
|