@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.
- package/.github/workflows/ci.yml +4 -2
- package/lib/synth-audio.js +61 -5
- package/package.json +1 -1
- package/test/synth.js +36 -2
package/.github/workflows/ci.yml
CHANGED
|
@@ -8,8 +8,8 @@ jobs:
|
|
|
8
8
|
build:
|
|
9
9
|
runs-on: ubuntu-latest
|
|
10
10
|
steps:
|
|
11
|
-
- uses: actions/checkout@
|
|
12
|
-
- uses: actions/setup-node@
|
|
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
|
package/lib/synth-audio.js
CHANGED
|
@@ -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
|
-
'
|
|
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
|
-
}
|
|
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
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
|
|
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('..');
|