@jambonz/speech-utils 0.0.18 → 0.0.20
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 -6
- package/lib/utils.js +0 -1
- package/package.json +2 -2
package/lib/synth-audio.js
CHANGED
|
@@ -14,7 +14,13 @@ const {
|
|
|
14
14
|
CancellationDetails,
|
|
15
15
|
SpeechSynthesisOutputFormat
|
|
16
16
|
} = sdk;
|
|
17
|
-
const {
|
|
17
|
+
const {
|
|
18
|
+
makeSynthKey,
|
|
19
|
+
createNuanceClient,
|
|
20
|
+
createKryptonClient,
|
|
21
|
+
createRivaClient,
|
|
22
|
+
noopLogger
|
|
23
|
+
} = require('./utils');
|
|
18
24
|
const getNuanceAccessToken = require('./get-nuance-access-token');
|
|
19
25
|
const {
|
|
20
26
|
SynthesisRequest,
|
|
@@ -125,8 +131,7 @@ async function synthAudio(client, logger, stats, { account_sid,
|
|
|
125
131
|
if (['nuance', 'nvidia'].includes(vendor) ||
|
|
126
132
|
(
|
|
127
133
|
process.env.JAMBONES_TTS_TRIM_SILENCE &&
|
|
128
|
-
['microsoft', 'azure'].includes(vendor)
|
|
129
|
-
!text.startsWith('<speak')
|
|
134
|
+
['microsoft', 'azure'].includes(vendor)
|
|
130
135
|
)
|
|
131
136
|
) {
|
|
132
137
|
filePath = `${TMP_FOLDER}/${key.replace('tts:', `tts-${salt || ''}`)}.r8`;
|
|
@@ -298,6 +303,47 @@ const synthIbm = async(logger, {credentials, stats, voice, text}) => {
|
|
|
298
303
|
}
|
|
299
304
|
};
|
|
300
305
|
|
|
306
|
+
async function _synthOnPremMicrosoft(logger, {
|
|
307
|
+
credentials,
|
|
308
|
+
stats,
|
|
309
|
+
language,
|
|
310
|
+
voice,
|
|
311
|
+
text,
|
|
312
|
+
filePath
|
|
313
|
+
}) {
|
|
314
|
+
const {use_custom_tts, custom_tts_endpoint_url} = credentials;
|
|
315
|
+
let content = text;
|
|
316
|
+
|
|
317
|
+
if (use_custom_tts && !content.startsWith('<speak')) {
|
|
318
|
+
/**
|
|
319
|
+
* Note: it seems that to use custom voice ssml is required with the voice attribute
|
|
320
|
+
* Otherwise sending plain text we get "Voice does not match"
|
|
321
|
+
*/
|
|
322
|
+
content = `<speak>${text}</speak>`;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (content.startsWith('<speak>')) {
|
|
326
|
+
/* microsoft enforces some properties and uses voice xml element so if the user did not supply do it for them */
|
|
327
|
+
const words = content.slice(7, -8).trim().replace(/(\r\n|\n|\r)/gm, ' ');
|
|
328
|
+
// eslint-disable-next-line max-len
|
|
329
|
+
content = `<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="https://www.w3.org/2001/mstts" xml:lang="${language}"><voice name="${voice}">${words}</voice></speak>`;
|
|
330
|
+
logger.info({content}, 'synthMicrosoft');
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
try {
|
|
334
|
+
const post = bent('POST', 'buffer', {
|
|
335
|
+
'X-Microsoft-OutputFormat': 'audio-16khz-128kbitrate-mono-mp3',
|
|
336
|
+
'Content-Type': 'application/ssml+xml',
|
|
337
|
+
'User-Agent': 'Jambonz'
|
|
338
|
+
});
|
|
339
|
+
const mp3 = await post(custom_tts_endpoint_url, content);
|
|
340
|
+
return mp3;
|
|
341
|
+
} catch (err) {
|
|
342
|
+
logger.info({err}, '_synthMicrosoftByHttp returned error');
|
|
343
|
+
throw err;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
301
347
|
const synthMicrosoft = async(logger, {
|
|
302
348
|
credentials,
|
|
303
349
|
stats,
|
|
@@ -307,7 +353,17 @@ const synthMicrosoft = async(logger, {
|
|
|
307
353
|
filePath
|
|
308
354
|
}) => {
|
|
309
355
|
try {
|
|
310
|
-
const {api_key: apiKey, region, use_custom_tts, custom_tts_endpoint} = credentials;
|
|
356
|
+
const {api_key: apiKey, region, use_custom_tts, custom_tts_endpoint, custom_tts_endpoint_url} = credentials;
|
|
357
|
+
if (use_custom_tts && custom_tts_endpoint_url) {
|
|
358
|
+
return await _synthOnPremMicrosoft(logger, {
|
|
359
|
+
credentials,
|
|
360
|
+
stats,
|
|
361
|
+
language,
|
|
362
|
+
voice,
|
|
363
|
+
text,
|
|
364
|
+
filePath
|
|
365
|
+
});
|
|
366
|
+
}
|
|
311
367
|
const trimSilence = filePath.endsWith('.r8');
|
|
312
368
|
let content = text;
|
|
313
369
|
const speechConfig = SpeechConfig.fromSubscription(apiKey, region);
|
|
@@ -315,12 +371,13 @@ const synthMicrosoft = async(logger, {
|
|
|
315
371
|
speechConfig.speechSynthesisVoiceName = voice;
|
|
316
372
|
if (use_custom_tts && custom_tts_endpoint) {
|
|
317
373
|
speechConfig.endpointId = custom_tts_endpoint;
|
|
318
|
-
|
|
374
|
+
}
|
|
375
|
+
if (use_custom_tts && !content.startsWith('<speak')) {
|
|
319
376
|
/**
|
|
320
377
|
* Note: it seems that to use custom voice ssml is required with the voice attribute
|
|
321
378
|
* Otherwise sending plain text we get "Voice does not match"
|
|
322
379
|
*/
|
|
323
|
-
|
|
380
|
+
content = `<speak>${text}</speak>`;
|
|
324
381
|
}
|
|
325
382
|
speechConfig.speechSynthesisOutputFormat = trimSilence ?
|
|
326
383
|
SpeechSynthesisOutputFormat.Raw8Khz16BitMonoPcm :
|
package/lib/utils.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jambonz/speech-utils",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.20",
|
|
4
4
|
"description": "TTS-related speech utilities for jambonz",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"author": "Dave Horton",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"form-urlencoded": "^6.1.0",
|
|
33
33
|
"google-protobuf": "^3.21.2",
|
|
34
34
|
"ibm-watson": "^8.0.0",
|
|
35
|
-
"microsoft-cognitiveservices-speech-sdk": "^1.
|
|
35
|
+
"microsoft-cognitiveservices-speech-sdk": "^1.31.0",
|
|
36
36
|
"ioredis": "^5.3.2",
|
|
37
37
|
"undici": "^5.21.0"
|
|
38
38
|
},
|