@jambonz/speech-utils 0.0.44 → 0.0.45

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.
@@ -183,7 +183,9 @@ async function synthAudio(client, logger, stats, { account_sid,
183
183
  case 'azure':
184
184
  case 'microsoft':
185
185
  vendorLabel = 'microsoft';
186
- audioBuffer = await synthMicrosoft(logger, {credentials, stats, language, voice, text, deploymentId, filePath});
186
+ audioBuffer = await synthMicrosoft(logger, {credentials, stats, language, voice, text, deploymentId,
187
+ filePath, renderForCaching, disableTtsStreaming});
188
+ if (audioBuffer?.filePath) return audioBuffer;
187
189
  break;
188
190
  case 'nuance':
189
191
  model = model || 'enhanced';
@@ -381,10 +383,46 @@ const synthMicrosoft = async(logger, {
381
383
  language,
382
384
  voice,
383
385
  text,
384
- filePath
386
+ filePath,
387
+ renderForCaching,
388
+ disableTtsStreaming
385
389
  }) => {
386
390
  try {
387
391
  const {api_key: apiKey, region, use_custom_tts, custom_tts_endpoint, custom_tts_endpoint_url} = credentials;
392
+ // let clean up the text
393
+ let content = text;
394
+ if (use_custom_tts && !content.startsWith('<speak')) {
395
+ /**
396
+ * Note: it seems that to use custom voice ssml is required with the voice attribute
397
+ * Otherwise sending plain text we get "Voice does not match"
398
+ */
399
+ content = `<speak>${text}</speak>`;
400
+ }
401
+
402
+ if (content.startsWith('<speak>')) {
403
+ /* microsoft enforces some properties and uses voice xml element so if the user did not supply do it for them */
404
+ const words = content.slice(7, -8).trim().replace(/(\r\n|\n|\r)/gm, ' ');
405
+ // eslint-disable-next-line max-len
406
+ 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>`;
407
+ logger.info({content}, 'synthMicrosoft');
408
+ }
409
+ if (!process.env.JAMBONES_DISABLE_TTS_STREAMING && !renderForCaching && !disableTtsStreaming) {
410
+ let params = '';
411
+ params += `{api_key=${apiKey}`;
412
+ params += `,language=${language}`;
413
+ params += ',vendor=microsoft';
414
+ params += `,voice=${voice}`;
415
+ if (region) params += `,region=${region}`;
416
+ if (custom_tts_endpoint) params += `,endpointId=${custom_tts_endpoint}`;
417
+ if (process.env.JAMBONES_HTTP_PROXY_IP) params += `,http_proxy_ip=${process.env.JAMBONES_HTTP_PROXY_IP}`;
418
+ if (process.env.JAMBONES_HTTP_PROXY_PORT) params += `,http_proxy_port=${process.env.JAMBONES_HTTP_PROXY_PORT}`;
419
+ params += '}';
420
+ return {
421
+ filePath: `say:${params}${content.replace(/\n/g, ' ')}`,
422
+ servedFromCache: false,
423
+ rtt: 0
424
+ };
425
+ }
388
426
  if (use_custom_tts && custom_tts_endpoint_url) {
389
427
  return await _synthOnPremMicrosoft(logger, {
390
428
  credentials,
@@ -396,20 +434,12 @@ const synthMicrosoft = async(logger, {
396
434
  });
397
435
  }
398
436
  const trimSilence = filePath.endsWith('.r8');
399
- let content = text;
400
437
  const speechConfig = SpeechConfig.fromSubscription(apiKey, region);
401
438
  speechConfig.speechSynthesisLanguage = language;
402
439
  speechConfig.speechSynthesisVoiceName = voice;
403
440
  if (use_custom_tts && custom_tts_endpoint) {
404
441
  speechConfig.endpointId = custom_tts_endpoint;
405
442
  }
406
- if (use_custom_tts && !content.startsWith('<speak')) {
407
- /**
408
- * Note: it seems that to use custom voice ssml is required with the voice attribute
409
- * Otherwise sending plain text we get "Voice does not match"
410
- */
411
- content = `<speak>${text}</speak>`;
412
- }
413
443
  speechConfig.speechSynthesisOutputFormat = trimSilence ?
414
444
  SpeechSynthesisOutputFormat.Raw8Khz16BitMonoPcm :
415
445
  SpeechSynthesisOutputFormat.Audio16Khz32KBitRateMonoMp3;
@@ -421,14 +451,6 @@ const synthMicrosoft = async(logger, {
421
451
  }
422
452
  const synthesizer = new SpeechSynthesizer(speechConfig);
423
453
 
424
- if (content.startsWith('<speak>')) {
425
- /* microsoft enforces some properties and uses voice xml element so if the user did not supply do it for them */
426
- const words = content.slice(7, -8).trim().replace(/(\r\n|\n|\r)/gm, ' ');
427
- // eslint-disable-next-line max-len
428
- 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>`;
429
- logger.info({content}, 'synthMicrosoft');
430
- }
431
-
432
454
  return new Promise((resolve, reject) => {
433
455
  const speakAsync = content.startsWith('<speak') ?
434
456
  synthesizer.speakSsmlAsync.bind(synthesizer) :
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jambonz/speech-utils",
3
- "version": "0.0.44",
3
+ "version": "0.0.45",
4
4
  "description": "TTS-related speech utilities for jambonz",
5
5
  "main": "index.js",
6
6
  "author": "Dave Horton",
package/test/synth.js CHANGED
@@ -188,6 +188,7 @@ test('Azure speech synth tests', async(t) => {
188
188
  language: 'en-US',
189
189
  voice: 'en-US-ChristopherNeural',
190
190
  text: longText,
191
+ renderForCaching: true
191
192
  });
192
193
  t.ok(!opts.servedFromCache, `successfully synthesized microsoft audio to ${opts.filePath}`);
193
194
  if (process.env.JAMBONES_HTTP_PROXY_IP && process.env.JAMBONES_HTTP_PROXY_PORT) {
@@ -203,6 +204,7 @@ test('Azure speech synth tests', async(t) => {
203
204
  language: 'en-US',
204
205
  voice: 'en-US-ChristopherNeural',
205
206
  text: longText,
207
+ renderForCaching: true
206
208
  });
207
209
  t.ok(opts.servedFromCache, `successfully retrieved microsoft audio from cache ${opts.filePath}`);
208
210
  } catch (err) {
@@ -237,6 +239,7 @@ test('Azure SSML tests', async(t) => {
237
239
  language: 'en-US',
238
240
  voice: 'en-US-ChristopherNeural',
239
241
  text,
242
+ renderForCaching: true
240
243
  });
241
244
  t.ok(!opts.servedFromCache, `successfully synthesized microsoft audio to ${opts.filePath}`);
242
245
  if (process.env.JAMBONES_HTTP_PROXY_IP && process.env.JAMBONES_HTTP_PROXY_PORT) {
@@ -252,6 +255,7 @@ test('Azure SSML tests', async(t) => {
252
255
  language: 'en-US',
253
256
  voice: 'en-US-ChristopherNeural',
254
257
  text,
258
+ renderForCaching: true
255
259
  });
256
260
  t.ok(opts.servedFromCache, `successfully retrieved microsoft audio from cache ${opts.filePath}`);
257
261
  } catch (err) {
@@ -283,6 +287,7 @@ test('Azure custom voice speech synth tests', async(t) => {
283
287
  language: 'en-US',
284
288
  voice: process.env.MICROSOFT_CUSTOM_VOICE,
285
289
  text,
290
+ renderForCaching: true
286
291
  });
287
292
  t.ok(!opts.servedFromCache, `successfully synthesized microsoft audio to ${opts.filePath}`);
288
293
 
@@ -297,6 +302,7 @@ test('Azure custom voice speech synth tests', async(t) => {
297
302
  language: 'en-US',
298
303
  voice: process.env.MICROSOFT_CUSTOM_VOICE,
299
304
  text,
305
+ renderForCaching: true
300
306
  });
301
307
  t.ok(opts.servedFromCache, `successfully retrieved microsoft custom voice audio from cache ${opts.filePath}`);
302
308
  } catch (err) {