mirror of
https://github.com/jambonz/speech-utils.git
synced 2026-10-03 23:33:59 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
043642ea5f | ||
|
|
91ea9820f9 | ||
|
|
fad0f57b13 | ||
|
|
2608e149a8 | ||
|
|
d21289acd6 | ||
|
|
a7b19e1d8a | ||
|
|
09c7aa9a5f | ||
|
|
f311c21327 | ||
|
|
535d0191da | ||
|
|
c00d4f9be4 | ||
|
|
db135ee5ad | ||
|
|
5eac4c2ad8 | ||
|
|
e23a1a6d09 | ||
|
|
3a78300a08 | ||
|
|
1d7390e7ae | ||
|
|
3c0940f657 | ||
|
|
6fb6195b16 | ||
|
|
2ff8587601 | ||
|
|
925bd26a70 | ||
|
|
ddea485f5f | ||
|
|
49de25feb8 | ||
|
|
08b55b8d79 | ||
|
|
59bca302b9 | ||
|
|
0d98f73c43 | ||
|
|
36670e0080 | ||
|
|
61672f9868 | ||
|
|
ddeca8eb99 | ||
|
|
6e8271b2f6 | ||
|
|
74a4938eb6 | ||
|
|
cb50f603cd | ||
|
|
9a524f00bc | ||
|
|
545df0b770 | ||
|
|
9409405769 | ||
|
|
7dc3bbdb01 | ||
|
|
f8f9de2645 | ||
|
|
467d7ede26 | ||
|
|
fc211ab2e7 | ||
|
|
536f8aab31 | ||
|
|
69b3fdffbe | ||
|
|
53fe72d89e | ||
|
|
e79c15c5da | ||
|
|
a4a427e174 | ||
|
|
b697bc5268 | ||
|
|
d35d7f0aec | ||
|
|
ed1c564fa2 | ||
|
|
7189d471c1 | ||
|
|
04080cc5ec | ||
|
|
3e5ab4af27 | ||
|
|
f06dddd2f7 | ||
|
|
7b4a71f55d | ||
|
|
d552b65618 | ||
|
|
f701b50244 |
@@ -25,7 +25,7 @@ function getExtensionAndSampleRate(path) {
|
||||
}
|
||||
|
||||
async function addFileToCache(client, logger, path,
|
||||
{account_sid, vendor, language, voice, deploymentId, engine, model, text}) {
|
||||
{account_sid, vendor, language, voice, deploymentId, engine, model, text, instructions}) {
|
||||
let key;
|
||||
logger = logger || noopLogger;
|
||||
|
||||
@@ -38,6 +38,7 @@ async function addFileToCache(client, logger, path,
|
||||
engine,
|
||||
model,
|
||||
text,
|
||||
instructions
|
||||
});
|
||||
const [extension, sampleRate] = getExtensionAndSampleRate(path);
|
||||
const audioBuffer = await fs.readFile(path);
|
||||
|
||||
+4
-2
@@ -2,13 +2,14 @@ const JAMBONES_TTS_TRIM_SILENCE = process.env.JAMBONES_TTS_TRIM_SILENCE;
|
||||
const JAMBONES_DISABLE_TTS_STREAMING = process.env.JAMBONES_DISABLE_TTS_STREAMING;
|
||||
const JAMBONES_DISABLE_AZURE_TTS_STREAMING = process.env.JAMBONES_DISABLE_AZURE_TTS_STREAMING;
|
||||
const JAMBONES_EAGERLY_PRE_CACHE_AUDIO = process.env.JAMBONES_EAGERLY_PRE_CACHE_AUDIO;
|
||||
const JAMBONES_AZURE_ENABLE_SSML = process.env.JAMBONES_AZURE_ENABLE_SSML;
|
||||
|
||||
const JAMBONES_HTTP_PROXY_IP = process.env.JAMBONES_HTTP_PROXY_IP;
|
||||
const JAMBONES_HTTP_PROXY_PORT = process.env.JAMBONES_HTTP_PROXY_PORT;
|
||||
const JAMBONES_TTS_CACHE_DURATION_MINS =
|
||||
(parseInt(process.env.JAMBONES_TTS_CACHE_DURATION_MINS) || 4 * 60) * 60; // cache tts for 4 hours
|
||||
|
||||
const TMP_FOLDER = '/tmp';
|
||||
const TMP_FOLDER = process.env.JAMBONES_TMP_FOLDER || '/tmp';
|
||||
|
||||
const HTTP_TIMEOUT = 5000;
|
||||
|
||||
@@ -21,5 +22,6 @@ module.exports = {
|
||||
JAMBONES_TTS_CACHE_DURATION_MINS,
|
||||
JAMBONES_EAGERLY_PRE_CACHE_AUDIO,
|
||||
TMP_FOLDER,
|
||||
HTTP_TIMEOUT
|
||||
HTTP_TIMEOUT,
|
||||
JAMBONES_AZURE_ENABLE_SSML
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ const debug = require('debug')('jambonz:realtimedb-helpers');
|
||||
* @returns {object} result - {error, purgedCount}
|
||||
*/
|
||||
async function purgeTtsCache(client, logger, {all, account_sid, vendor,
|
||||
language, voice, deploymentId, engine, model, text} = {all: true}) {
|
||||
language, voice, deploymentId, engine, model, text, instructions} = {all: true}) {
|
||||
logger = logger || noopLogger;
|
||||
|
||||
let purgedCount = 0, error;
|
||||
@@ -35,6 +35,7 @@ async function purgeTtsCache(client, logger, {all, account_sid, vendor,
|
||||
engine,
|
||||
model,
|
||||
text,
|
||||
instructions
|
||||
});
|
||||
purgedCount = await client.del(key);
|
||||
if (purgedCount === 0) error = 'Specified item not found';
|
||||
|
||||
+128
-16
@@ -47,6 +47,7 @@ const {
|
||||
JAMBONES_HTTP_PROXY_PORT,
|
||||
JAMBONES_TTS_CACHE_DURATION_MINS,
|
||||
JAMBONES_TTS_TRIM_SILENCE,
|
||||
JAMBONES_AZURE_ENABLE_SSML
|
||||
} = require('./config');
|
||||
const EXPIRES = JAMBONES_TTS_CACHE_DURATION_MINS;
|
||||
const OpenAI = require('openai');
|
||||
@@ -89,7 +90,7 @@ const trimTrailingSilence = (buffer) => {
|
||||
*/
|
||||
async function synthAudio(client, createHash, retrieveHash, logger, stats, { account_sid,
|
||||
vendor, language, voice, gender, text, engine, salt, model, credentials, deploymentId,
|
||||
disableTtsCache, renderForCaching = false, disableTtsStreaming, options
|
||||
disableTtsCache, renderForCaching = false, disableTtsStreaming, options, instructions
|
||||
}) {
|
||||
let audioData;
|
||||
let servedFromCache = false;
|
||||
@@ -97,7 +98,7 @@ async function synthAudio(client, createHash, retrieveHash, logger, stats, { acc
|
||||
logger = logger || noopLogger;
|
||||
|
||||
assert.ok(['google', 'aws', 'polly', 'microsoft', 'wellsaid', 'nuance', 'nvidia', 'ibm', 'elevenlabs',
|
||||
'whisper', 'deepgram', 'playht', 'rimelabs', 'verbio', 'cartesia'].includes(vendor) ||
|
||||
'whisper', 'deepgram', 'playht', 'rimelabs', 'verbio', 'cartesia', 'inworld'].includes(vendor) ||
|
||||
vendor.startsWith('custom'),
|
||||
`synthAudio supported vendors are google, aws, microsoft, nuance, nvidia and wellsaid ..etc, not ${vendor}`);
|
||||
if ('google' === vendor) {
|
||||
@@ -140,6 +141,10 @@ async function synthAudio(client, createHash, retrieveHash, logger, stats, { acc
|
||||
assert.ok(credentials.api_key, 'synthAudio requires api_key when playht is used');
|
||||
assert.ok(credentials.user_id, 'synthAudio requires user_id when playht is used');
|
||||
assert.ok(credentials.voice_engine, 'synthAudio requires voice_engine when playht is used');
|
||||
} else if ('inworld' === vendor) {
|
||||
assert.ok(voice, 'synthAudio requires voice when inworld is used');
|
||||
assert.ok(credentials.api_key, 'synthAudio requires api_key when inworld is used');
|
||||
assert.ok(credentials.model_id, 'synthAudio requires model_id when inworld is used');
|
||||
} else if ('rimelabs' === vendor) {
|
||||
assert.ok(voice, 'synthAudio requires voice when rimelabs is used');
|
||||
assert.ok(credentials.api_key, 'synthAudio requires api_key when rimelabs is used');
|
||||
@@ -171,7 +176,8 @@ async function synthAudio(client, createHash, retrieveHash, logger, stats, { acc
|
||||
engine,
|
||||
// model or model_id is used to identify the tts cache.
|
||||
model: model || credentials.model_id,
|
||||
text
|
||||
text,
|
||||
instructions
|
||||
});
|
||||
|
||||
debug(`synth key is ${key}`);
|
||||
@@ -215,7 +221,8 @@ async function synthAudio(client, createHash, retrieveHash, logger, stats, { acc
|
||||
audioData = await synthNuance(client, logger, {credentials, stats, voice, model, text});
|
||||
break;
|
||||
case 'nvidia':
|
||||
audioData = await synthNvidia(client, logger, {credentials, stats, language, voice, model, text});
|
||||
audioData = await synthNvidia(client, logger, {credentials, stats, language, voice, model, text,
|
||||
renderForCaching, disableTtsStreaming});
|
||||
break;
|
||||
case 'ibm':
|
||||
audioData = await synthIbm(logger, {credentials, stats, voice, text});
|
||||
@@ -235,13 +242,17 @@ async function synthAudio(client, createHash, retrieveHash, logger, stats, { acc
|
||||
audioData = await synthCartesia(logger, {
|
||||
credentials, options, stats, language, voice, text, renderForCaching, disableTtsStreaming});
|
||||
break;
|
||||
case 'inworld':
|
||||
audioData = await synthInworld(logger, {
|
||||
credentials, options, stats, language, voice, text, renderForCaching, disableTtsStreaming});
|
||||
break;
|
||||
case 'rimelabs':
|
||||
audioData = await synthRimelabs(logger, {
|
||||
credentials, options, stats, language, voice, text, renderForCaching, disableTtsStreaming});
|
||||
break;
|
||||
case 'whisper':
|
||||
audioData = await synthWhisper(logger, {
|
||||
credentials, stats, voice, text, renderForCaching, disableTtsStreaming});
|
||||
credentials, stats, voice, text, instructions, renderForCaching, disableTtsStreaming});
|
||||
break;
|
||||
case 'verbio':
|
||||
audioData = await synthVerbio(client, logger, {
|
||||
@@ -464,7 +475,6 @@ async function _synthOnPremMicrosoft(logger, {
|
||||
}) {
|
||||
const {use_custom_tts, custom_tts_endpoint_url, api_key} = credentials;
|
||||
let content = text;
|
||||
|
||||
if (use_custom_tts && !content.startsWith('<speak')) {
|
||||
/**
|
||||
* Note: it seems that to use custom voice ssml is required with the voice attribute
|
||||
@@ -480,6 +490,10 @@ async function _synthOnPremMicrosoft(logger, {
|
||||
content = `<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="https://www.w3.org/2001/mstts" xml:lang="${language}"><voice name="${voice}">${words}</voice></speak>`;
|
||||
logger.info({content}, 'synthMicrosoft');
|
||||
}
|
||||
else if (JAMBONES_AZURE_ENABLE_SSML && !content.startsWith('<speak')) {
|
||||
// eslint-disable-next-line max-len
|
||||
content = `<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="https://www.w3.org/2001/mstts" xml:lang="${language}"><voice name="${voice}"><lang xml:lang="${language}">${text}</lang></voice></speak>`;
|
||||
}
|
||||
|
||||
try {
|
||||
const trimSilence = JAMBONES_TTS_TRIM_SILENCE;
|
||||
@@ -516,19 +530,23 @@ const synthMicrosoft = async(logger, {
|
||||
let content = text;
|
||||
if (use_custom_tts && !content.startsWith('<speak')) {
|
||||
/**
|
||||
* Note: it seems that to use custom voice ssml is required with the voice attribute
|
||||
* Otherwise sending plain text we get "Voice does not match"
|
||||
*/
|
||||
* Note: it seems that to use custom voice ssml is required with the voice attribute
|
||||
* Otherwise sending plain text we get "Voice does not match"
|
||||
*/
|
||||
content = `<speak>${text}</speak>`;
|
||||
}
|
||||
|
||||
if (content.startsWith('<speak>')) {
|
||||
/* microsoft enforces some properties and uses voice xml element so if the user did not supply do it for them */
|
||||
/* microsoft enforces some properties and uses voice xml element so if the user did not supply do it for them */
|
||||
const words = content.slice(7, -8).trim().replace(/(\r\n|\n|\r)/gm, ' ');
|
||||
// eslint-disable-next-line max-len
|
||||
content = `<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="https://www.w3.org/2001/mstts" xml:lang="${language}"><voice name="${voice}">${words}</voice></speak>`;
|
||||
logger.info({content}, 'synthMicrosoft');
|
||||
}
|
||||
else if (JAMBONES_AZURE_ENABLE_SSML && !content.startsWith('<speak')) {
|
||||
// eslint-disable-next-line max-len
|
||||
content = `<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="https://www.w3.org/2001/mstts" xml:lang="${language}"><voice name="${voice}"><lang xml:lang="${language}">${text}</lang></voice></speak>`;
|
||||
}
|
||||
if (!JAMBONES_DISABLE_TTS_STREAMING && !JAMBONES_DISABLE_AZURE_TTS_STREAMING &&
|
||||
!renderForCaching && !disableTtsStreaming) {
|
||||
let params = '';
|
||||
@@ -708,8 +726,24 @@ const synthNuance = async(client, logger, {credentials, stats, voice, model, tex
|
||||
});
|
||||
};
|
||||
|
||||
const synthNvidia = async(client, logger, {credentials, stats, language, voice, model, text}) => {
|
||||
const synthNvidia = async(client, logger, {
|
||||
credentials, stats, language, voice, model, text, renderForCaching, disableTtsStreaming
|
||||
}) => {
|
||||
const {riva_server_uri} = credentials;
|
||||
if (!JAMBONES_DISABLE_TTS_STREAMING && !renderForCaching && !disableTtsStreaming) {
|
||||
let params = '';
|
||||
params += `{riva_server_uri=${riva_server_uri}`;
|
||||
params += `,voice=${voice}`;
|
||||
params += `,language=${language}`;
|
||||
params += ',write_cache_file=1';
|
||||
params += '}';
|
||||
|
||||
return {
|
||||
filePath: `say:${params}${text.replace(/\n/g, ' ')}`,
|
||||
servedFromCache: false,
|
||||
rtt: 0
|
||||
};
|
||||
}
|
||||
let rivaClient, request;
|
||||
const sampleRate = 8000;
|
||||
try {
|
||||
@@ -789,9 +823,13 @@ const synthElevenlabs = async(logger, {
|
||||
if (opts.voice_settings?.similarity_boost) params += `,similarity_boost=${opts.voice_settings.similarity_boost}`;
|
||||
if (opts.voice_settings?.stability) params += `,stability=${opts.voice_settings.stability}`;
|
||||
if (opts.voice_settings?.style) params += `,style=${opts.voice_settings.style}`;
|
||||
if (opts.voice_settings?.speed !== null && opts.voice_settings?.speed !== undefined)
|
||||
params += `,speed=${opts.voice_settings.speed}`;
|
||||
if (opts.voice_settings?.use_speaker_boost === false) params += ',use_speaker_boost=false';
|
||||
if (opts.previous_text) params += `,previous_text=${opts.previous_text}`;
|
||||
if (opts.next_text) params += `,next_text=${opts.next_text}`;
|
||||
if (opts.pronunciation_dictionary_locators && Array.isArray(opts.pronunciation_dictionary_locators))
|
||||
params += `,pronunciation_dictionary_locators=${JSON.stringify(opts.pronunciation_dictionary_locators)}`;
|
||||
params += '}';
|
||||
|
||||
return {
|
||||
@@ -833,11 +871,10 @@ const synthElevenlabs = async(logger, {
|
||||
const synthPlayHT = async(client, logger, {
|
||||
credentials, options, stats, voice, language, text, renderForCaching, disableTtsStreaming
|
||||
}) => {
|
||||
const {api_key, user_id, voice_engine, options: credOpts} = credentials;
|
||||
const {api_key, user_id, voice_engine, playht_tts_uri, options: credOpts} = credentials;
|
||||
const opts = !!options && Object.keys(options).length !== 0 ? options : JSON.parse(credOpts || '{}');
|
||||
|
||||
let synthesizeUrl = 'https://api.play.ht/api/v2/tts/stream';
|
||||
|
||||
let synthesizeUrl = playht_tts_uri ? `${playht_tts_uri}/api/v2/tts/stream` : 'https://api.play.ht/api/v2/tts/stream';
|
||||
// If model is play3.0, the synthesizeUrl is got from authentication endpoint
|
||||
if (voice_engine === 'Play3.0') {
|
||||
try {
|
||||
@@ -925,22 +962,92 @@ const synthPlayHT = async(client, logger, {
|
||||
}
|
||||
};
|
||||
|
||||
const synthRimelabs = async(logger, {
|
||||
const synthInworld = async(logger, {
|
||||
credentials, options, stats, voice, text, renderForCaching, disableTtsStreaming
|
||||
}) => {
|
||||
const {api_key, model_id, options: credOpts} = credentials;
|
||||
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 */
|
||||
if (!JAMBONES_DISABLE_TTS_STREAMING && !renderForCaching && !disableTtsStreaming) {
|
||||
let params = '';
|
||||
params += `{api_key=${api_key}`;
|
||||
params += `,model_id=${model_id}`;
|
||||
params += ',vendor=inworld';
|
||||
params += `,voice=${voice}`;
|
||||
params += ',write_cache_file=1';
|
||||
if (opts.temperature) params += `,temperature=${opts.temperature}`;
|
||||
if (opts.audioConfig?.pitch) params += `,pitch=${opts.pitch}`;
|
||||
if (opts.audioConfig?.speakingRate) params += `,speakingRate=${opts.speakingRate}`;
|
||||
params += '}';
|
||||
|
||||
return {
|
||||
filePath: `say:${params}${text.replace(/\n/g, ' ').replace(/\r/g, ' ')}`,
|
||||
servedFromCache: false,
|
||||
rtt: 0
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const url = 'https://api.inworld.ai/tts/v1/voice';
|
||||
const sampleRate = 8000;
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Basic ${api_key}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
text,
|
||||
voiceId: voice,
|
||||
modelId: model_id,
|
||||
audioConfig: {
|
||||
...(opts.audioConfig || {}),
|
||||
audioEncoding: 'MP3',
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
const response = await fetch(url, options);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(await response.text());
|
||||
}
|
||||
const json = await response.json();
|
||||
return {
|
||||
audioContent: Buffer.from(json.audioContent, 'base64'),
|
||||
extension: 'mp3',
|
||||
sampleRate
|
||||
};
|
||||
} catch (err) {
|
||||
logger.info({err}, 'synth inworld returned error');
|
||||
stats.increment('tts.count', ['vendor:inworld', 'accepted:no']);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const synthRimelabs = async(logger, {
|
||||
credentials, options, stats, language, voice, text, renderForCaching, disableTtsStreaming
|
||||
}) => {
|
||||
const {api_key, model_id, options: credOpts} = credentials;
|
||||
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 */
|
||||
if (!JAMBONES_DISABLE_TTS_STREAMING && !renderForCaching && !disableTtsStreaming) {
|
||||
let params = '';
|
||||
params += `{api_key=${api_key}`;
|
||||
params += `,model_id=${model_id}`;
|
||||
params += ',vendor=rimelabs';
|
||||
params += `,language=${language}`;
|
||||
params += `,voice=${voice}`;
|
||||
params += ',write_cache_file=1';
|
||||
if (opts.speedAlpha) params += `,speed_alpha=${opts.speedAlpha}`;
|
||||
if (opts.reduceLatency) params += `,reduce_latency=${opts.reduceLatency}`;
|
||||
// Arcana model parameters
|
||||
if (opts.temperature) params += `,temperature=${opts.temperature}`;
|
||||
if (opts.repetition_penalty) params += `,repetition_penalty=${opts.repetition_penalty}`;
|
||||
if (opts.top_p) params += `,top_p=${opts.top_p}`;
|
||||
if (opts.max_tokens) params += `,max_tokens=${opts.max_tokens}`;
|
||||
params += '}';
|
||||
|
||||
return {
|
||||
@@ -962,6 +1069,7 @@ const synthRimelabs = async(logger, {
|
||||
text,
|
||||
modelId: model_id,
|
||||
samplingRate: sampleRate,
|
||||
lang: language,
|
||||
...opts
|
||||
});
|
||||
return {
|
||||
@@ -1020,7 +1128,8 @@ const synthVerbio = async(client, logger, {credentials, stats, voice, text, rend
|
||||
}
|
||||
};
|
||||
|
||||
const synthWhisper = async(logger, {credentials, stats, voice, text, renderForCaching, disableTtsStreaming}) => {
|
||||
const synthWhisper = async(logger, {credentials, stats, voice, text, instructions,
|
||||
renderForCaching, disableTtsStreaming}) => {
|
||||
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 (!JAMBONES_DISABLE_TTS_STREAMING && !renderForCaching && !disableTtsStreaming) {
|
||||
@@ -1031,6 +1140,8 @@ const synthWhisper = async(logger, {credentials, stats, voice, text, renderForCa
|
||||
params += `,voice=${voice}`;
|
||||
params += ',write_cache_file=1';
|
||||
if (speed) params += `,speed=${speed}`;
|
||||
// comma is used to separated parameters in freeswitch tts module
|
||||
if (instructions) params += `,instructions=${instructions.replace(/\n/g, ' ').replace(/,/g, ';')}`;
|
||||
params += '}';
|
||||
|
||||
return {
|
||||
@@ -1050,6 +1161,7 @@ const synthWhisper = async(logger, {credentials, stats, voice, text, renderForCa
|
||||
model: model_id,
|
||||
voice,
|
||||
input: text,
|
||||
...(instructions && {instructions}),
|
||||
response_format: 'mp3'
|
||||
});
|
||||
return {
|
||||
|
||||
+3
-2
@@ -23,10 +23,11 @@ function makeSynthKey({
|
||||
voice,
|
||||
engine = '',
|
||||
model = '',
|
||||
text
|
||||
text,
|
||||
instructions = '',
|
||||
}) {
|
||||
const hash = crypto.createHash('sha1');
|
||||
hash.update(`${language}:${vendor}:${voice}:${engine}:${model}:${text}`);
|
||||
hash.update(`${language}:${vendor}:${voice}:${engine}:${model}:${text}:${instructions}`);
|
||||
const hexHashKey = hash.digest('hex');
|
||||
const accountKey = account_sid ? `:${account_sid}` : '';
|
||||
const key = `tts${accountKey}:${hexHashKey}`;
|
||||
|
||||
Generated
+525
-1162
File diff suppressed because it is too large
Load Diff
+5
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@jambonz/speech-utils",
|
||||
"version": "0.2.2",
|
||||
"version": "0.2.15",
|
||||
"description": "TTS-related speech utilities for jambonz",
|
||||
"main": "index.js",
|
||||
"author": "Dave Horton",
|
||||
@@ -26,6 +26,7 @@
|
||||
},
|
||||
"homepage": "https://github.com/jambonz/speech-utils#readme",
|
||||
"dependencies": {
|
||||
"23": "^0.0.0",
|
||||
"@aws-sdk/client-polly": "^3.496.0",
|
||||
"@aws-sdk/client-sts": "^3.496.0",
|
||||
"@cartesia/cartesia-js": "^2.1.0",
|
||||
@@ -36,10 +37,10 @@
|
||||
"debug": "^4.3.4",
|
||||
"form-urlencoded": "^6.1.4",
|
||||
"google-protobuf": "^3.21.2",
|
||||
"ibm-watson": "^8.0.0",
|
||||
"ibm-watson": "^11.0.0",
|
||||
"microsoft-cognitiveservices-speech-sdk": "1.38.0",
|
||||
"openai": "^4.25.0",
|
||||
"undici": "^6.4.0"
|
||||
"openai": "^4.98.0",
|
||||
"undici": "^7.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"config": "^3.3.11",
|
||||
|
||||
+72
-7
@@ -720,7 +720,37 @@ test('Cartesia speech synth tests', async(t) => {
|
||||
client.quit();
|
||||
});
|
||||
|
||||
test('rimelabs speech synth tests', async(t) => {
|
||||
test('inworld speech synth', async(t) => {
|
||||
const fn = require('..');
|
||||
const {synthAudio, client} = fn(opts, logger);
|
||||
|
||||
if (!process.env.INWORLD_API_KEY) {
|
||||
t.pass('skipping inworld speech synth tests since INWORLD_API_KEY is not provided');
|
||||
return t.end();
|
||||
}
|
||||
const text = 'Hi there and welcome to jambones!';
|
||||
try {
|
||||
const opts = await synthAudio(stats, {
|
||||
vendor: 'inworld',
|
||||
credentials: {
|
||||
api_key: process.env.INWORLD_API_KEY,
|
||||
model_id: 'inworld-tts-1'
|
||||
},
|
||||
language: 'en',
|
||||
voice: 'Ashley',
|
||||
text,
|
||||
renderForCaching: true
|
||||
});
|
||||
t.ok(!opts.servedFromCache, `successfully synthesized inworld audio to ${opts.filePath}`);
|
||||
|
||||
} catch (err) {
|
||||
console.error(JSON.stringify(err));
|
||||
t.end(err);
|
||||
}
|
||||
client.quit();
|
||||
});
|
||||
|
||||
test('rimelabs speech synth tests mist', async(t) => {
|
||||
const fn = require('..');
|
||||
const {synthAudio, client} = fn(opts, logger);
|
||||
|
||||
@@ -730,7 +760,7 @@ test('rimelabs speech synth tests', async(t) => {
|
||||
}
|
||||
const text = 'Hi there and welcome to jambones!';
|
||||
try {
|
||||
let opts = await synthAudio(stats, {
|
||||
const opts = await synthAudio(stats, {
|
||||
vendor: 'rimelabs',
|
||||
credentials: {
|
||||
api_key: process.env.RIMELABS_API_KEY,
|
||||
@@ -740,7 +770,7 @@ test('rimelabs speech synth tests', async(t) => {
|
||||
reduceLatency: false
|
||||
})
|
||||
},
|
||||
language: 'en-US',
|
||||
language: 'eng',
|
||||
voice: 'amber',
|
||||
text,
|
||||
renderForCaching: true
|
||||
@@ -754,6 +784,40 @@ test('rimelabs speech synth tests', async(t) => {
|
||||
client.quit();
|
||||
});
|
||||
|
||||
test('rimelabs speech synth tests mistv2', async(t) => {
|
||||
const fn = require('..');
|
||||
const {synthAudio, client} = fn(opts, logger);
|
||||
|
||||
if (!process.env.RIMELABS_API_KEY) {
|
||||
t.pass('skipping rimelabs speech synth tests since RIMELABS_API_KEY is not provided');
|
||||
return t.end();
|
||||
}
|
||||
const text = 'Hi there and welcome to jambones!';
|
||||
try {
|
||||
const opts = await synthAudio(stats, {
|
||||
vendor: 'rimelabs',
|
||||
credentials: {
|
||||
api_key: process.env.RIMELABS_API_KEY,
|
||||
model_id: 'mistv2',
|
||||
options: JSON.stringify({
|
||||
speedAlpha: 1.0,
|
||||
reduceLatency: false
|
||||
})
|
||||
},
|
||||
language: 'spa',
|
||||
voice: 'pablo',
|
||||
text,
|
||||
renderForCaching: true
|
||||
});
|
||||
t.ok(!opts.servedFromCache, `successfully synthesized rimelabs mistv2 audio to ${opts.filePath}`);
|
||||
|
||||
} catch (err) {
|
||||
console.error(JSON.stringify(err));
|
||||
t.end(err);
|
||||
}
|
||||
client.quit();
|
||||
});
|
||||
|
||||
test('whisper speech synth tests', async(t) => {
|
||||
const fn = require('..');
|
||||
const {synthAudio, client} = fn(opts, logger);
|
||||
@@ -853,7 +917,8 @@ test('TTS Cache tests', async(t) => {
|
||||
// save some random tts keys to cache
|
||||
const minRecords = 8;
|
||||
for (const i in Array(minRecords).fill(0)) {
|
||||
await client.set(makeSynthKey({vendor: i, language: i, voice: i, engine: i, model: i, text: i}), i);
|
||||
await client.set(makeSynthKey({vendor: i, language: i, voice: i, engine: i, model: i, text: i,
|
||||
instructions: i}), i);
|
||||
}
|
||||
const count = await getTtsSize();
|
||||
t.ok(count >= minRecords, 'getTtsSize worked.');
|
||||
@@ -872,7 +937,7 @@ test('TTS Cache tests', async(t) => {
|
||||
try {
|
||||
// save some random tts keys to cache
|
||||
for (const i in Array(10).fill(0)) {
|
||||
await client.set(makeSynthKey({vendor: i, language: i, voice: i, engine: i, text: i}), i);
|
||||
await client.set(makeSynthKey({vendor: i, language: i, voice: i, engine: i, text: i, instructions: i}), i);
|
||||
}
|
||||
// save a specific key to tts cache
|
||||
const opts = {vendor: 'aws', language: 'en-US', voice: 'MALE', engine: 'Engine', text: 'Hello World!'};
|
||||
@@ -910,10 +975,10 @@ test('TTS Cache tests', async(t) => {
|
||||
const account_sid = "12412512_cabc_5aff"
|
||||
const account_sid2 = "22412512_cabc_5aff"
|
||||
for (const i in Array(minRecords).fill(0)) {
|
||||
await client.set(makeSynthKey({account_sid, vendor: i, language: i, voice: i, engine: i, text: i}), i);
|
||||
await client.set(makeSynthKey({account_sid, vendor: i, language: i, voice: i, engine: i, text: i, instructions: i}), i);
|
||||
}
|
||||
for (const i in Array(minRecords).fill(0)) {
|
||||
await client.set(makeSynthKey({account_sid: account_sid2, vendor: i, language: i, voice: i, engine: i, text: i}), i);
|
||||
await client.set(makeSynthKey({account_sid: account_sid2, vendor: i, language: i, voice: i, engine: i, text: i, instructions: i}), i);
|
||||
}
|
||||
const {purgedCount} = await purgeTtsCache({account_sid});
|
||||
t.equal(purgedCount, minRecords, `successfully purged at least ${minRecords} tts records from cache for account_sid:${account_sid}`);
|
||||
|
||||
Reference in New Issue
Block a user