@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.
@@ -14,7 +14,13 @@ const {
14
14
  CancellationDetails,
15
15
  SpeechSynthesisOutputFormat
16
16
  } = sdk;
17
- const {makeSynthKey, createNuanceClient, createKryptonClient, createRivaClient, noopLogger} = require('./utils');
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
- if (!content.startsWith('<speak')) content = `<speak>${text}</speak>`;
380
+ content = `<speak>${text}</speak>`;
324
381
  }
325
382
  speechConfig.speechSynthesisOutputFormat = trimSilence ?
326
383
  SpeechSynthesisOutputFormat.Raw8Khz16BitMonoPcm :
package/lib/utils.js CHANGED
@@ -106,7 +106,6 @@ const createRivaClient = async(rivaUri) => {
106
106
  return client;
107
107
  };
108
108
 
109
-
110
109
  module.exports = {
111
110
  makeSynthKey,
112
111
  makeNuanceKey,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jambonz/speech-utils",
3
- "version": "0.0.18",
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.26.0",
35
+ "microsoft-cognitiveservices-speech-sdk": "^1.31.0",
36
36
  "ioredis": "^5.3.2",
37
37
  "undici": "^5.21.0"
38
38
  },