@jambonz/speech-utils 0.0.1 → 0.0.3

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.
@@ -11,7 +11,8 @@ const debug = require('debug')('jambonz:realtimedb-helpers');
11
11
  * @param {string} opts.text - text or ssml to synthesize
12
12
  * @returns {object} result - {error, purgedCount}
13
13
  */
14
- async function purgeTtsCache(client, logger, {all, vendor, language, voice, deploymentId, engine, text} = {all: true}) {
14
+ async function purgeTtsCache(client, logger, {all, account_sid, vendor,
15
+ language, voice, deploymentId, engine, text} = {all: true}) {
15
16
  logger = logger || noopLogger;
16
17
 
17
18
  let purgedCount = 0, error;
@@ -21,8 +22,13 @@ async function purgeTtsCache(client, logger, {all, vendor, language, voice, depl
21
22
  const keys = await client.keysAsync('tts:*');
22
23
  purgedCount = await client.delAsync(keys);
23
24
 
24
- } else {
25
+ } else if (account_sid && !vendor && !language && !voice && !engine && !text) {
26
+ const keys = await client.keysAsync(`tts:${account_sid}:*`);
27
+ purgedCount = await client.delAsync(keys);
28
+ }
29
+ else {
25
30
  const key = makeSynthKey({
31
+ account_sid,
26
32
  vendor,
27
33
  language: language || '',
28
34
  voice: voice || deploymentId,
@@ -32,7 +32,7 @@ const {
32
32
  const {SynthesizeSpeechRequest} = require('../stubs/riva/proto/riva_tts_pb');
33
33
  const {AudioEncoding} = require('../stubs/riva/proto/riva_audio_pb');
34
34
  const debug = require('debug')('jambonz:realtimedb-helpers');
35
- const EXPIRES = 3600 * 24; // cache tts for 24 hours
35
+ const EXPIRES = (process.env.JAMBONES_TTS_CACHE_DURATION_MINS || 24 * 60) * 60; // cache tts for 24 hours
36
36
  const TMP_FOLDER = '/tmp';
37
37
 
38
38
  /**
@@ -52,7 +52,7 @@ const TMP_FOLDER = '/tmp';
52
52
  * @returns object containing filepath to an mp3 file in the /tmp folder containing
53
53
  * the synthesized audio, and a variable indicating whether it was served from cache
54
54
  */
55
- async function synthAudio(client, logger, stats, {
55
+ async function synthAudio(client, logger, stats, { account_sid,
56
56
  vendor, language, voice, gender, text, engine, salt, model, credentials, deploymentId, disableTtsCache
57
57
  }) {
58
58
  let audioBuffer;
@@ -95,8 +95,8 @@ async function synthAudio(client, logger, stats, {
95
95
  } else if (vendor.startsWith('custom')) {
96
96
  assert.ok(credentials.custom_tts_url, `synthAudio requires custom_tts_url in credentials when ${vendor} is used`);
97
97
  }
98
-
99
98
  const key = makeSynthKey({
99
+ account_sid,
100
100
  vendor,
101
101
  language: language || '',
102
102
  voice: voice || deploymentId,
@@ -155,7 +155,8 @@ async function synthAudio(client, logger, stats, {
155
155
  audioBuffer = await synthWellSaid(logger, {credentials, stats, language, voice, text, filePath});
156
156
  break;
157
157
  case vendor.startsWith('custom') ? vendor : 'cant_match_value':
158
- audioBuffer = await synthCustomVendor(logger, {credentials, stats, language, voice, text});
158
+ ({ audioBuffer, filePath } = await synthCustomVendor(logger,
159
+ {credentials, stats, language, voice, text, filePath}));
159
160
  break;
160
161
  default:
161
162
  assert(`synthAudio: unsupported speech vendor ${vendor}`);
@@ -440,30 +441,56 @@ const synthNvidia = async(client, logger, {credentials, stats, language, voice,
440
441
  };
441
442
 
442
443
 
443
- // CustomVendor accept only mp3
444
- const synthCustomVendor = async(logger, {credentials, stats, language, voice, text}) => {
444
+ const synthCustomVendor = async(logger, {credentials, stats, language, voice, text, filePath}) => {
445
445
  const {vendor, auth_token, custom_tts_url} = credentials;
446
446
 
447
447
  try {
448
- const post = bent('POST', 'buffer', {
448
+ const post = bent('POST', {
449
449
  'Authorization': `Bearer ${auth_token}`,
450
- 'Accept': 'audio/mpeg',
451
450
  'Content-Type': 'application/json'
452
451
  });
453
452
 
454
- const mp3 = await post(custom_tts_url, {
453
+ const response = await post(custom_tts_url, {
455
454
  language,
456
- format: 'audio/mpeg',
457
455
  voice,
458
456
  type: text.startsWith('<speak>') ? 'ssml' : 'text',
459
457
  text
460
458
  });
461
459
 
462
- return mp3;
460
+ const regex = /\.[^\.]*$/g;
461
+ const mime = response.headers['content-type'];
462
+ const buffer = await response.arrayBuffer();
463
+ return {
464
+ audioBuffer: buffer,
465
+ filePath: filePath.replace(regex, getFileExtFromMime(mime))
466
+ };
463
467
  } catch (err) {
464
468
  logger.info({err}, `Vendor ${vendor} returned error`);
465
469
  throw err;
466
470
  }
467
471
  };
468
472
 
473
+ const getFileExtFromMime = (mime) => {
474
+ switch (mime) {
475
+ case 'audio/wav':
476
+ case 'audio/x-wav':
477
+ return '.wav';
478
+ case /audio\/l16.*rate=8000/.test(mime) ? mime : 'cant match value':
479
+ return '.r8';
480
+ case /audio\/l16.*rate=16000/.test(mime) ? mime : 'cant match value':
481
+ return '.r16';
482
+ case /audio\/l16.*rate=24000/.test(mime) ? mime : 'cant match value':
483
+ return '.r24';
484
+ case /audio\/l16.*rate=32000/.test(mime) ? mime : 'cant match value':
485
+ return '.r32';
486
+ case /audio\/l16.*rate=48000/.test(mime) ? mime : 'cant match value':
487
+ return '.r48';
488
+ case 'audio/mpeg':
489
+ case 'audio/mp3':
490
+ return '.mp3';
491
+ default:
492
+ return '.wav';
493
+ }
494
+ };
495
+
469
496
  module.exports = synthAudio;
package/lib/utils.js CHANGED
@@ -16,10 +16,10 @@ const debug = require('debug')('jambonz:realtimedb-helpers');
16
16
  */
17
17
  //const nuanceClientMap = new Map();
18
18
 
19
- function makeSynthKey({vendor, language, voice, engine = '', text}) {
19
+ function makeSynthKey({account_sid = '', vendor, language, voice, engine = '', text}) {
20
20
  const hash = crypto.createHash('sha1');
21
21
  hash.update(`${language}:${vendor}:${voice}:${engine}:${text}`);
22
- return `tts:${hash.digest('hex')}`;
22
+ return `tts${account_sid ? (':' + account_sid) : ''}:${hash.digest('hex')}`;
23
23
  }
24
24
 
25
25
  const noopLogger = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jambonz/speech-utils",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "TTS-related speech utilities for jambonz",
5
5
  "main": "index.js",
6
6
  "author": "Dave Horton",
package/test/synth.js CHANGED
@@ -348,7 +348,6 @@ test('Custom Vendor speech synth tests', async(t) => {
348
348
  let obj = await getJSON(`http://127.0.0.1:3100/lastRequest/somethingnew`);
349
349
  t.ok(obj.headers.Authorization == 'Bearer some_jwt_token', 'Custom Vendor Authentication Header is correct');
350
350
  t.ok(obj.body.language == 'en-US', 'Custom Vendor Language is correct');
351
- t.ok(obj.body.format == 'audio/mpeg', 'Custom Vendor format is correct');
352
351
  t.ok(obj.body.voice == 'English-US.Female-1', 'Custom Vendor voice is correct');
353
352
  t.ok(obj.body.type == 'text', 'Custom Vendor type is correct');
354
353
  t.ok(obj.body.text == 'This is a test. This is only a test', 'Custom Vendor text is correct');
@@ -427,5 +426,35 @@ test('TTS Cache tests', async(t) => {
427
426
  t.end(err);
428
427
  }
429
428
 
429
+ try {
430
+ // clear cache
431
+ await purgeTtsCache();
432
+ // save some random tts keys to cache
433
+ const minRecords = 8;
434
+ const account_sid = "12412512_cabc_5aff"
435
+ const account_sid2 = "22412512_cabc_5aff"
436
+ for (const i in Array(minRecords).fill(0)) {
437
+ await client.setAsync(makeSynthKey({account_sid, vendor: i, language: i, voice: i, engine: i, text: i}), i);
438
+ }
439
+ for (const i in Array(minRecords).fill(0)) {
440
+ await client.setAsync(makeSynthKey({account_sid: account_sid2, vendor: i, language: i, voice: i, engine: i, text: i}), i);
441
+ }
442
+ const {purgedCount} = await purgeTtsCache({account_sid});
443
+ t.equal(purgedCount, minRecords, `successfully purged at least ${minRecords} tts records from cache for account_sid:${account_sid}`);
444
+
445
+ let cached = (await client.keysAsync('tts:*')).length;
446
+ t.equal(cached, minRecords, `successfully purged all tts records from cache for account_sid:${account_sid}`);
447
+
448
+ const {purgedCount: purgedCount2} = await purgeTtsCache({account_sid: account_sid2});
449
+ t.equal(purgedCount2, minRecords, `successfully purged at least ${minRecords} tts records from cache for account_sid:${account_sid2}`);
450
+
451
+ cached = (await client.keysAsync('tts:*')).length;
452
+ t.equal(cached, 0, `successfully purged all tts records from cache`);
453
+
454
+ } catch (err) {
455
+ console.error(JSON.stringify(err));
456
+ t.end(err);
457
+ }
458
+
430
459
  client.quit();
431
460
  });