@jambonz/speech-utils 0.0.44 → 0.0.46

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,47 @@ 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
+ params += ',write_cache_file=1';
416
+ if (region) params += `,region=${region}`;
417
+ if (custom_tts_endpoint) params += `,endpointId=${custom_tts_endpoint}`;
418
+ if (process.env.JAMBONES_HTTP_PROXY_IP) params += `,http_proxy_ip=${process.env.JAMBONES_HTTP_PROXY_IP}`;
419
+ if (process.env.JAMBONES_HTTP_PROXY_PORT) params += `,http_proxy_port=${process.env.JAMBONES_HTTP_PROXY_PORT}`;
420
+ params += '}';
421
+ return {
422
+ filePath: `say:${params}${content.replace(/\n/g, ' ')}`,
423
+ servedFromCache: false,
424
+ rtt: 0
425
+ };
426
+ }
388
427
  if (use_custom_tts && custom_tts_endpoint_url) {
389
428
  return await _synthOnPremMicrosoft(logger, {
390
429
  credentials,
@@ -396,20 +435,12 @@ const synthMicrosoft = async(logger, {
396
435
  });
397
436
  }
398
437
  const trimSilence = filePath.endsWith('.r8');
399
- let content = text;
400
438
  const speechConfig = SpeechConfig.fromSubscription(apiKey, region);
401
439
  speechConfig.speechSynthesisLanguage = language;
402
440
  speechConfig.speechSynthesisVoiceName = voice;
403
441
  if (use_custom_tts && custom_tts_endpoint) {
404
442
  speechConfig.endpointId = custom_tts_endpoint;
405
443
  }
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
444
  speechConfig.speechSynthesisOutputFormat = trimSilence ?
414
445
  SpeechSynthesisOutputFormat.Raw8Khz16BitMonoPcm :
415
446
  SpeechSynthesisOutputFormat.Audio16Khz32KBitRateMonoMp3;
@@ -421,14 +452,6 @@ const synthMicrosoft = async(logger, {
421
452
  }
422
453
  const synthesizer = new SpeechSynthesizer(speechConfig);
423
454
 
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
455
  return new Promise((resolve, reject) => {
433
456
  const speakAsync = content.startsWith('<speak') ?
434
457
  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.46",
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) {