@jambonz/speech-utils 0.0.50 → 0.0.51

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.
@@ -8,8 +8,8 @@ jobs:
8
8
  build:
9
9
  runs-on: ubuntu-latest
10
10
  steps:
11
- - uses: actions/checkout@v3
12
- - uses: actions/setup-node@v3
11
+ - uses: actions/checkout@v4
12
+ - uses: actions/setup-node@v4
13
13
  with:
14
14
  node-version: lts/*
15
15
  - run: npm install
@@ -32,5 +32,7 @@ jobs:
32
32
  ELEVENLABS_API_KEY: ${{ secrets.ELEVENLABS_API_KEY }}
33
33
  ELEVENLABS_VOICE_ID: ${{ secrets.ELEVENLABS_VOICE_ID }}
34
34
  ELEVENLABS_MODEL_ID: ${{ secrets.ELEVENLABS_MODEL_ID }}
35
+ PLAYHT_USER_ID: ${{ secrets.PLAYHT_USER_ID }}
36
+ PLAYHT_API_KEY: ${{ secrets.PLAYHT_API_KEY }}
35
37
  JAMBONES_HTTP_PROXY_IP: 127.0.0.1
36
38
  JAMBONES_HTTP_PROXY_PORT: 3128
@@ -84,10 +84,10 @@ async function synthAudio(client, logger, stats, { account_sid,
84
84
  let rtt;
85
85
  logger = logger || noopLogger;
86
86
 
87
- assert.ok(['google', 'aws', 'polly', 'microsoft',
88
- 'wellsaid', 'nuance', 'nvidia', 'ibm', 'elevenlabs', 'whisper', 'deepgram', 'playht'].includes(vendor) ||
87
+ assert.ok(['google', 'aws', 'polly', 'microsoft', 'wellsaid', 'nuance', 'nvidia', 'ibm', 'elevenlabs',
88
+ 'whisper', 'deepgram', 'playht', 'rimelabs'].includes(vendor) ||
89
89
  vendor.startsWith('custom'),
90
- `synthAudio supported vendors are google, aws, microsoft, nuance, nvidia and wellsaid, not ${vendor}`);
90
+ `synthAudio supported vendors are google, aws, microsoft, nuance, nvidia and wellsaid ..etc, not ${vendor}`);
91
91
  if ('google' === vendor) {
92
92
  assert.ok(language, 'synthAudio requires language when google is used');
93
93
  }
@@ -123,11 +123,15 @@ async function synthAudio(client, logger, stats, { account_sid,
123
123
  assert.ok(voice, 'synthAudio requires voice when elevenlabs is used');
124
124
  assert.ok(credentials.api_key, 'synthAudio requires api_key when elevenlabs is used');
125
125
  assert.ok(credentials.model_id, 'synthAudio requires model_id when elevenlabs is used');
126
- } else if ('playht' === vendor) {
126
+ } else if ('playht' === vendor) {
127
127
  assert.ok(voice, 'synthAudio requires voice when playht is used');
128
128
  assert.ok(credentials.api_key, 'synthAudio requires api_key when playht is used');
129
129
  assert.ok(credentials.user_id, 'synthAudio requires user_id when playht is used');
130
130
  assert.ok(credentials.voice_engine, 'synthAudio requires voice_engine when playht is used');
131
+ } else if ('rimelabs' === vendor) {
132
+ assert.ok(voice, 'synthAudio requires voice when rimelabs is used');
133
+ assert.ok(credentials.api_key, 'synthAudio requires api_key when rimelabs is used');
134
+ assert.ok(credentials.model_id, 'synthAudio requires model_id when rimelabs is used');
131
135
  } else if ('whisper' === vendor) {
132
136
  assert.ok(voice, 'synthAudio requires voice when whisper is used');
133
137
  assert.ok(credentials.model_id, 'synthAudio requires model when whisper is used');
@@ -151,7 +155,7 @@ async function synthAudio(client, logger, stats, { account_sid,
151
155
  ) ||
152
156
  (
153
157
  !process.env.JAMBONES_DISABLE_TTS_STREAMING &&
154
- ['elevenlabs', 'deepgram'].includes(vendor)
158
+ ['elevenlabs', 'deepgram', 'rimelabs'].includes(vendor)
155
159
  )
156
160
  ) {
157
161
  filePath = `${TMP_FOLDER}/${key.replace('tts:', `tts-${salt || ''}`)}.r8`;
@@ -217,6 +221,12 @@ async function synthAudio(client, logger, stats, { account_sid,
217
221
  });
218
222
  if (audioBuffer?.filePath) return audioBuffer;
219
223
  break;
224
+ case 'rimelabs':
225
+ audioBuffer = await synthRimelabs(logger, {
226
+ credentials, options, stats, language, voice, text, renderForCaching, disableTtsStreaming, filePath
227
+ });
228
+ if (audioBuffer?.filePath) return audioBuffer;
229
+ break;
220
230
  case 'whisper':
221
231
  audioBuffer = await synthWhisper(logger, {
222
232
  credentials, stats, voice, text, renderForCaching, disableTtsStreaming});
@@ -748,6 +758,52 @@ const synthPlayHT = async(logger, {
748
758
  }
749
759
  };
750
760
 
761
+ const synthRimelabs = async(logger, {
762
+ credentials, options, stats, voice, text, renderForCaching, disableTtsStreaming
763
+ }) => {
764
+ const {api_key, model_id, options: credOpts} = credentials;
765
+ const opts = !!options && Object.keys(options).length !== 0 ? options : JSON.parse(credOpts || '{}');
766
+
767
+ /* default to using the streaming interface, unless disabled by env var OR we want just a cache file */
768
+ if (!process.env.JAMBONES_DISABLE_TTS_STREAMING && !renderForCaching && !disableTtsStreaming) {
769
+ let params = '';
770
+ params += `{api_key=${api_key}`;
771
+ params += `,model_id=${model_id}`;
772
+ params += ',vendor=rimelabs';
773
+ params += `,voice=${voice}`;
774
+ params += ',write_cache_file=1';
775
+ if (opts.speedAlpha) params += `,speed_alpha=${opts.speedAlpha}`;
776
+ if (opts.reduceLatency) params += `,reduce_latency=${opts.reduceLatency}`;
777
+ params += '}';
778
+
779
+ return {
780
+ filePath: `say:${params}${text.replace(/\n/g, ' ').replace(/\r/g, ' ')}`,
781
+ servedFromCache: false,
782
+ rtt: 0
783
+ };
784
+ }
785
+
786
+ try {
787
+ const post = bent('https://users.rime.ai', 'POST', 'buffer', {
788
+ 'Authorization': `Bearer ${api_key}`,
789
+ 'Accept': 'audio/mp3',
790
+ 'Content-Type': 'application/json'
791
+ });
792
+ const mp3 = await post('/v1/rime-tts', {
793
+ speaker: voice,
794
+ text,
795
+ modelId: model_id,
796
+ samplingRate: 8000,
797
+ ...opts
798
+ });
799
+ return mp3;
800
+ } catch (err) {
801
+ logger.info({err}, 'synth rimelabs returned error');
802
+ stats.increment('tts.count', ['vendor:rimelabs', 'accepted:no']);
803
+ throw err;
804
+ }
805
+ };
806
+
751
807
  const synthWhisper = async(logger, {credentials, stats, voice, text, renderForCaching, disableTtsStreaming}) => {
752
808
  const {api_key, model_id, baseURL, timeout, speed} = credentials;
753
809
  /* if the env is set to stream then bag out, unless we are specifically rendering to generate a cache file */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jambonz/speech-utils",
3
- "version": "0.0.50",
3
+ "version": "0.0.51",
4
4
  "description": "TTS-related speech utilities for jambonz",
5
5
  "main": "index.js",
6
6
  "author": "Dave Horton",
package/test/synth.js CHANGED
@@ -581,14 +581,48 @@ test('PlayHT speech synth tests', async(t) => {
581
581
  text,
582
582
  renderForCaching: true
583
583
  });
584
- t.ok(!opts.servedFromCache, `successfully synthesized eleven audio to ${opts.filePath}`);
584
+ t.ok(!opts.servedFromCache, `successfully playht eleven audio to ${opts.filePath}`);
585
585
 
586
586
  } catch (err) {
587
587
  console.error(JSON.stringify(err));
588
588
  t.end(err);
589
589
  }
590
590
  client.quit();
591
- })
591
+ });
592
+
593
+ test('rimelabs speech synth tests', async(t) => {
594
+ const fn = require('..');
595
+ const {synthAudio, client} = fn(opts, logger);
596
+
597
+ if (!process.env.RIMELABS_API_KEY) {
598
+ t.pass('skipping rimelabs speech synth tests since RIMELABS_API_KEY is not provided');
599
+ return t.end();
600
+ }
601
+ const text = 'Hi there and welcome to jambones!';
602
+ try {
603
+ let opts = await synthAudio(stats, {
604
+ vendor: 'rimelabs',
605
+ credentials: {
606
+ api_key: process.env.RIMELABS_API_KEY,
607
+ model_id: 'mist',
608
+ options: JSON.stringify({
609
+ speedAlpha: 1.0,
610
+ reduceLatency: false
611
+ })
612
+ },
613
+ language: 'en-US',
614
+ voice: 'amber',
615
+ text,
616
+ renderForCaching: true
617
+ });
618
+ t.ok(!opts.servedFromCache, `successfully synthesized rimelabs audio to ${opts.filePath}`);
619
+
620
+ } catch (err) {
621
+ console.error(JSON.stringify(err));
622
+ t.end(err);
623
+ }
624
+ client.quit();
625
+ });
592
626
 
593
627
  test('whisper speech synth tests', async(t) => {
594
628
  const fn = require('..');