mirror of
https://github.com/jambonz/speech-utils.git
synced 2026-10-03 23:33:59 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f3e930004 | ||
|
|
3f4c444d82 | ||
|
|
fd7d8b8bcd | ||
|
|
46f833c7fa | ||
|
|
4eabfbe4b7 | ||
|
|
f3ab2baa6a | ||
|
|
fb412e2ddf | ||
|
|
dbfabeaddf | ||
|
|
c3188e40bb | ||
|
|
d0dfd07204 | ||
|
|
04a2466f54 | ||
|
|
0f9a9edc4d | ||
|
|
ced1a0ef0d | ||
|
|
1609d0b205 | ||
|
|
ef8ada2793 | ||
|
|
444ad2522f | ||
|
|
3cf9894b44 |
+32
-14
@@ -77,7 +77,7 @@ const trimTrailingSilence = (buffer) => {
|
|||||||
*/
|
*/
|
||||||
async function synthAudio(client, logger, stats, { account_sid,
|
async function synthAudio(client, logger, stats, { account_sid,
|
||||||
vendor, language, voice, gender, text, engine, salt, model, credentials, deploymentId,
|
vendor, language, voice, gender, text, engine, salt, model, credentials, deploymentId,
|
||||||
disableTtsCache, renderForCaching, options
|
disableTtsCache, renderForCaching, disableTtsStreaming, options
|
||||||
}) {
|
}) {
|
||||||
let audioBuffer;
|
let audioBuffer;
|
||||||
let servedFromCache = false;
|
let servedFromCache = false;
|
||||||
@@ -200,17 +200,14 @@ async function synthAudio(client, logger, stats, { account_sid,
|
|||||||
break;
|
break;
|
||||||
case 'elevenlabs':
|
case 'elevenlabs':
|
||||||
audioBuffer = await synthElevenlabs(logger, {
|
audioBuffer = await synthElevenlabs(logger, {
|
||||||
credentials, options, stats, language, voice, text, renderForCaching, filePath
|
credentials, options, stats, language, voice, text, renderForCaching, disableTtsStreaming, filePath
|
||||||
});
|
});
|
||||||
if (typeof audioBuffer === 'object' && audioBuffer.filePath) {
|
if (audioBuffer?.filePath) return audioBuffer;
|
||||||
return audioBuffer;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
audioBuffer = await synthElevenlabs(logger, {credentials, options, stats, language, voice, text, filePath});
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 'whisper':
|
case 'whisper':
|
||||||
audioBuffer = await synthWhisper(logger, {credentials, stats, voice, text});
|
audioBuffer = await synthWhisper(logger, {
|
||||||
|
credentials, stats, voice, text, renderForCaching, disableTtsStreaming});
|
||||||
|
if (audioBuffer?.filePath) return audioBuffer;
|
||||||
break;
|
break;
|
||||||
case 'deepgram':
|
case 'deepgram':
|
||||||
audioBuffer = await synthDeepgram(logger, {credentials, stats, model, text});
|
audioBuffer = await synthDeepgram(logger, {credentials, stats, model, text});
|
||||||
@@ -607,14 +604,18 @@ const synthCustomVendor = async(logger, {credentials, stats, language, voice, te
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const synthElevenlabs = async(logger, {credentials, options, stats, language, voice, text, renderForCaching}) => {
|
const synthElevenlabs = async(logger, {
|
||||||
|
credentials, options, stats, voice, text, renderForCaching, disableTtsStreaming
|
||||||
|
}) => {
|
||||||
const {api_key, model_id, options: credOpts} = credentials;
|
const {api_key, model_id, options: credOpts} = credentials;
|
||||||
const opts = !!options && Object.keys(options).length !== 0 ? options : JSON.parse(credOpts || '{}');
|
const opts = !!options && Object.keys(options).length !== 0 ? options : JSON.parse(credOpts || '{}');
|
||||||
|
|
||||||
/* default to using the streaming interface, unless disabled by env var OR we want just a cache file */
|
/* default to using the streaming interface, unless disabled by env var OR we want just a cache file */
|
||||||
if (!process.env.JAMBONES_DISABLE_TTS_STREAMING && !renderForCaching) {
|
if (!process.env.JAMBONES_DISABLE_TTS_STREAMING && !renderForCaching && !disableTtsStreaming) {
|
||||||
let params = '';
|
let params = '';
|
||||||
params += `{api_key=${api_key}`;
|
params += `{api_key=${api_key}`;
|
||||||
|
params += ',vendor=elevenlabs';
|
||||||
|
params += `,voice=${voice}`;
|
||||||
params += `,model_id=${model_id}`;
|
params += `,model_id=${model_id}`;
|
||||||
params += `,optimize_streaming_latency=${opts.optimize_streaming_latency || 2}`;
|
params += `,optimize_streaming_latency=${opts.optimize_streaming_latency || 2}`;
|
||||||
params += ',write_cache_file=1';
|
params += ',write_cache_file=1';
|
||||||
@@ -656,8 +657,25 @@ const synthElevenlabs = async(logger, {credentials, options, stats, language, vo
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const synthWhisper = async(logger, {credentials, stats, voice, text}) => {
|
const synthWhisper = async(logger, {credentials, stats, voice, text, renderForCaching, disableTtsStreaming}) => {
|
||||||
const {api_key, model_id, baseURL, timeout} = credentials;
|
const {api_key, model_id, baseURL, timeout, speed} = credentials;
|
||||||
|
/* if the env is set to stream then bag out, unless we are specifically rendering to generate a cache file */
|
||||||
|
if (!process.env.JAMBONES_DISABLE_TTS_STREAMING && !renderForCaching && !disableTtsStreaming) {
|
||||||
|
let params = '';
|
||||||
|
params += `{api_key=${api_key}`;
|
||||||
|
params += `,model_id=${model_id}`;
|
||||||
|
params += ',vendor=whisper';
|
||||||
|
params += `,voice=${voice}`;
|
||||||
|
params += ',write_cache_file=1';
|
||||||
|
if (speed) params += `,speed=${speed}`;
|
||||||
|
params += '}';
|
||||||
|
|
||||||
|
return {
|
||||||
|
filePath: `say:${params}${text.replace(/\n/g, ' ')}`,
|
||||||
|
servedFromCache: false,
|
||||||
|
rtt: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const openai = new OpenAI.OpenAI({
|
const openai = new OpenAI.OpenAI({
|
||||||
apiKey: api_key,
|
apiKey: api_key,
|
||||||
@@ -682,7 +700,7 @@ const synthWhisper = async(logger, {credentials, stats, voice, text}) => {
|
|||||||
const synthDeepgram = async(logger, {credentials, stats, model, text}) => {
|
const synthDeepgram = async(logger, {credentials, stats, model, text}) => {
|
||||||
const {api_key} = credentials;
|
const {api_key} = credentials;
|
||||||
try {
|
try {
|
||||||
const post = bent('https://api.beta.deepgram.com', 'POST', 'buffer', {
|
const post = bent('https://api.deepgram.com', 'POST', 'buffer', {
|
||||||
'Authorization': `Token ${api_key}`,
|
'Authorization': `Token ${api_key}`,
|
||||||
'Accept': 'audio/mpeg',
|
'Accept': 'audio/mpeg',
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@jambonz/speech-utils",
|
"name": "@jambonz/speech-utils",
|
||||||
"version": "0.0.41",
|
"version": "0.0.44",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@jambonz/speech-utils",
|
"name": "@jambonz/speech-utils",
|
||||||
"version": "0.0.41",
|
"version": "0.0.44",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-polly": "^3.496.0",
|
"@aws-sdk/client-polly": "^3.496.0",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@jambonz/speech-utils",
|
"name": "@jambonz/speech-utils",
|
||||||
"version": "0.0.41",
|
"version": "0.0.44",
|
||||||
"description": "TTS-related speech utilities for jambonz",
|
"description": "TTS-related speech utilities for jambonz",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"author": "Dave Horton",
|
"author": "Dave Horton",
|
||||||
|
|||||||
+51
-1
@@ -212,6 +212,56 @@ test('Azure speech synth tests', async(t) => {
|
|||||||
client.quit();
|
client.quit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Azure SSML tests', async(t) => {
|
||||||
|
const fn = require('..');
|
||||||
|
const {synthAudio, client} = fn(opts, logger);
|
||||||
|
|
||||||
|
if (!process.env.MICROSOFT_API_KEY || !process.env.MICROSOFT_REGION) {
|
||||||
|
t.pass('skipping Microsoft speech synth tests since MICROSOFT_API_KEY or MICROSOFT_REGION not provided');
|
||||||
|
return t.end();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const text = `<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="https://www.w3.org/2001/mstts" xml:lang="en-US">
|
||||||
|
<voice name="en-US-JennyMultilingualNeural">
|
||||||
|
<mstts:express-as style="cheerful" styledegree="2">That'd be just amazing!
|
||||||
|
</mstts:express-as>
|
||||||
|
</voice>
|
||||||
|
</speak>`;
|
||||||
|
|
||||||
|
let opts = await synthAudio(stats, {
|
||||||
|
vendor: 'microsoft',
|
||||||
|
credentials: {
|
||||||
|
api_key: process.env.MICROSOFT_API_KEY,
|
||||||
|
region: process.env.MICROSOFT_REGION,
|
||||||
|
},
|
||||||
|
language: 'en-US',
|
||||||
|
voice: 'en-US-ChristopherNeural',
|
||||||
|
text,
|
||||||
|
});
|
||||||
|
t.ok(!opts.servedFromCache, `successfully synthesized microsoft audio to ${opts.filePath}`);
|
||||||
|
if (process.env.JAMBONES_HTTP_PROXY_IP && process.env.JAMBONES_HTTP_PROXY_PORT) {
|
||||||
|
t.pass('successfully used proxy to reach microsoft tts service');
|
||||||
|
}
|
||||||
|
|
||||||
|
opts = await synthAudio(stats, {
|
||||||
|
vendor: 'microsoft',
|
||||||
|
credentials: {
|
||||||
|
api_key: process.env.MICROSOFT_API_KEY,
|
||||||
|
region: process.env.MICROSOFT_REGION,
|
||||||
|
},
|
||||||
|
language: 'en-US',
|
||||||
|
voice: 'en-US-ChristopherNeural',
|
||||||
|
text,
|
||||||
|
});
|
||||||
|
t.ok(opts.servedFromCache, `successfully retrieved microsoft audio from cache ${opts.filePath}`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
t.end(err);
|
||||||
|
}
|
||||||
|
client.quit();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
test('Azure custom voice speech synth tests', async(t) => {
|
test('Azure custom voice speech synth tests', async(t) => {
|
||||||
const fn = require('..');
|
const fn = require('..');
|
||||||
const {synthAudio, client} = fn(opts, logger);
|
const {synthAudio, client} = fn(opts, logger);
|
||||||
@@ -537,7 +587,7 @@ test('Deepgram speech synth tests', async(t) => {
|
|||||||
credentials: {
|
credentials: {
|
||||||
api_key: process.env.DEEPGRAM_API_KEY
|
api_key: process.env.DEEPGRAM_API_KEY
|
||||||
},
|
},
|
||||||
model: 'alpha-asteria-en-v2',
|
model: 'aura-asteria-en',
|
||||||
text,
|
text,
|
||||||
});
|
});
|
||||||
t.ok(!opts.servedFromCache, `successfully synthesized deepgram audio to ${opts.filePath}`);
|
t.ok(!opts.servedFromCache, `successfully synthesized deepgram audio to ${opts.filePath}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user