@jambonz/speech-utils 0.0.32 → 0.0.34

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.
@@ -76,7 +76,8 @@ const trimTrailingSilence = (buffer) => {
76
76
  * the synthesized audio, and a variable indicating whether it was served from cache
77
77
  */
78
78
  async function synthAudio(client, logger, stats, { account_sid,
79
- vendor, language, voice, gender, text, engine, salt, model, credentials, deploymentId, disableTtsCache, options
79
+ vendor, language, voice, gender, text, engine, salt, model, credentials, deploymentId,
80
+ disableTtsCache, renderForCaching, options
80
81
  }) {
81
82
  let audioBuffer;
82
83
  let servedFromCache = false;
@@ -84,7 +85,7 @@ async function synthAudio(client, logger, stats, { account_sid,
84
85
  logger = logger || noopLogger;
85
86
 
86
87
  assert.ok(['google', 'aws', 'polly', 'microsoft',
87
- 'wellsaid', 'nuance', 'nvidia', 'ibm', 'elevenlabs', 'whisper'].includes(vendor) ||
88
+ 'wellsaid', 'nuance', 'nvidia', 'ibm', 'elevenlabs', 'whisper', 'deepgram'].includes(vendor) ||
88
89
  vendor.startsWith('custom'),
89
90
  `synthAudio supported vendors are google, aws, microsoft, nuance, nvidia and wellsaid, not ${vendor}`);
90
91
  if ('google' === vendor) {
@@ -194,11 +195,22 @@ async function synthAudio(client, logger, stats, { account_sid,
194
195
  audioBuffer = await synthWellSaid(logger, {credentials, stats, language, voice, text, filePath});
195
196
  break;
196
197
  case 'elevenlabs':
197
- audioBuffer = await synthElevenlabs(logger, {credentials, options, stats, language, voice, text, filePath});
198
+ audioBuffer = await synthElevenlabs(logger, {
199
+ credentials, options, stats, language, voice, text, renderForCaching, filePath
200
+ });
201
+ if (typeof audioBuffer === 'object' && audioBuffer.filePath) {
202
+ return audioBuffer;
203
+ }
204
+ else {
205
+ audioBuffer = await synthElevenlabs(logger, {credentials, options, stats, language, voice, text, filePath});
206
+ }
198
207
  break;
199
208
  case 'whisper':
200
209
  audioBuffer = await synthWhisper(logger, {credentials, stats, voice, text});
201
210
  break;
211
+ case 'deepgram':
212
+ audioBuffer = await synthDeepgram(logger, {credentials, stats, model, text});
213
+ break;
202
214
  case vendor.startsWith('custom') ? vendor : 'cant_match_value':
203
215
  ({ audioBuffer, filePath } = await synthCustomVendor(logger,
204
216
  {credentials, stats, language, voice, text, filePath}));
@@ -591,9 +603,29 @@ const synthCustomVendor = async(logger, {credentials, stats, language, voice, te
591
603
  }
592
604
  };
593
605
 
594
- const synthElevenlabs = async(logger, {credentials, options, stats, language, voice, text}) => {
606
+ const synthElevenlabs = async(logger, {credentials, options, stats, language, voice, text, renderForCaching}) => {
595
607
  const {api_key, model_id, options: credOpts} = credentials;
596
608
  const opts = !!options && Object.keys(options).length !== 0 ? options : JSON.parse(credOpts || '{}');
609
+
610
+ /* if the env is set to stream then bag out, unless we are specifically rendering to generate a cache file */
611
+ if (process.env.JAMBONES_ELEVENLABS_STREAMING && !renderForCaching) {
612
+ let params = '';
613
+ params += `{api_key=${api_key}`;
614
+ params += `,model_id=${model_id}`;
615
+ params += `,optimize_streaming_latency=${opts.optimize_streaming_latency || 2}`;
616
+ if (opts.voice_settings?.similarity_boost) params += `,similarity_boost=${opts.voice_settings.similarity_boost}`;
617
+ if (opts.voice_settings?.stability) params += `,stability=${opts.voice_settings.stability}`;
618
+ if (opts.voice_settings?.style) params += `,style=${opts.voice_settings.style}`;
619
+ if (opts.voice_settings?.use_speaker_boost === false) params += ',use_speaker_boost=false';
620
+ params += '}';
621
+
622
+ return {
623
+ filePath: `say:${params}${text.replace(/\n/g, ' ')}`,
624
+ servedFromCache: false,
625
+ rtt: 0
626
+ };
627
+ }
628
+
597
629
  const optimize_streaming_latency = opts.optimize_streaming_latency ?
598
630
  `?optimize_streaming_latency=${opts.optimize_streaming_latency}` : '';
599
631
  try {
@@ -640,8 +672,27 @@ const synthWhisper = async(logger, {credentials, stats, voice, text}) => {
640
672
  stats.increment('tts.count', ['vendor:openai', 'accepted:no']);
641
673
  throw err;
642
674
  }
643
- }
644
- ;
675
+ };
676
+
677
+ const synthDeepgram = async(logger, {credentials, stats, model, text}) => {
678
+ const {api_key} = credentials;
679
+ try {
680
+ const post = bent('https://api.beta.deepgram.com', 'POST', 'buffer', {
681
+ 'Authorization': `Token ${api_key}`,
682
+ 'Accept': 'audio/mpeg',
683
+ 'Content-Type': 'application/json'
684
+ });
685
+ const mp3 = await post(`/v1/speak?model=${model}`, {
686
+ text
687
+ });
688
+ return mp3;
689
+ } catch (err) {
690
+ logger.info({err}, 'synth Deepgram returned error');
691
+ stats.increment('tts.count', ['vendor:deepgram', 'accepted:no']);
692
+ throw err;
693
+ }
694
+ };
695
+
645
696
  const getFileExtFromMime = (mime) => {
646
697
  switch (mime) {
647
698
  case 'audio/wav':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jambonz/speech-utils",
3
- "version": "0.0.32",
3
+ "version": "0.0.34",
4
4
  "description": "TTS-related speech utilities for jambonz",
5
5
  "main": "index.js",
6
6
  "author": "Dave Horton",
@@ -24,26 +24,26 @@
24
24
  },
25
25
  "homepage": "https://github.com/jambonz/speech-utils#readme",
26
26
  "dependencies": {
27
- "@aws-sdk/client-polly": "^3.359.0",
28
- "@aws-sdk/client-sts": "^3.458.0",
29
- "@google-cloud/text-to-speech": "^4.2.1",
30
- "@grpc/grpc-js": "^1.8.13",
27
+ "@aws-sdk/client-polly": "^3.496.0",
28
+ "@aws-sdk/client-sts": "^3.496.0",
29
+ "@google-cloud/text-to-speech": "^5.0.2",
30
+ "@grpc/grpc-js": "^1.9.14",
31
31
  "@jambonz/realtimedb-helpers": "^0.8.7",
32
32
  "bent": "^7.3.12",
33
33
  "debug": "^4.3.4",
34
- "form-urlencoded": "^6.1.0",
34
+ "form-urlencoded": "^6.1.4",
35
35
  "google-protobuf": "^3.21.2",
36
36
  "ibm-watson": "^8.0.0",
37
- "microsoft-cognitiveservices-speech-sdk": "1.32.0",
38
- "openai": "^4.16.2",
39
- "undici": "^5.21.0"
37
+ "microsoft-cognitiveservices-speech-sdk": "1.34.0",
38
+ "openai": "^4.25.0",
39
+ "undici": "^6.4.0"
40
40
  },
41
41
  "devDependencies": {
42
- "config": "^3.3.9",
43
- "eslint": "^8.33.0",
42
+ "config": "^3.3.10",
43
+ "eslint": "^8.56.0",
44
44
  "eslint-plugin-promise": "^6.1.1",
45
45
  "nyc": "^15.1.0",
46
- "pino": "^7.2.0",
47
- "tape": "^5.1.1"
46
+ "pino": "^8.17.0",
47
+ "tape": "^5.7.3"
48
48
  }
49
49
  }
package/test/synth.js CHANGED
@@ -514,6 +514,33 @@ test('whisper speech synth tests', async(t) => {
514
514
  client.quit();
515
515
  })
516
516
 
517
+ test('Deepgram speech synth tests', async(t) => {
518
+ const fn = require('..');
519
+ const {synthAudio, client} = fn(opts, logger);
520
+
521
+ if (!process.env.DEEPGRAM_API_KEY) {
522
+ t.pass('skipping Deepgram speech synth tests since DEEPGRAM_API_KEY');
523
+ return t.end();
524
+ }
525
+ const text = 'Hi there and welcome to jambones!';
526
+ try {
527
+ let opts = await synthAudio(stats, {
528
+ vendor: 'deepgram',
529
+ credentials: {
530
+ api_key: process.env.DEEPGRAM_API_KEY
531
+ },
532
+ model: 'alpha-asteria-en-v2',
533
+ text,
534
+ });
535
+ t.ok(!opts.servedFromCache, `successfully synthesized deepgram audio to ${opts.filePath}`);
536
+
537
+ } catch (err) {
538
+ console.error(JSON.stringify(err));
539
+ t.end(err);
540
+ }
541
+ client.quit();
542
+ })
543
+
517
544
  test('TTS Cache tests', async(t) => {
518
545
  const fn = require('..');
519
546
  const {purgeTtsCache, getTtsSize, client} = fn(opts, logger);