Compare commits

...

19 Commits

Author SHA1 Message Date
Dave Horton
5c7bac91a8 update to latest speech-utils 2024-10-18 12:29:05 -04:00
Hoan Luu Huu
de250c8d58 support playht3.0 languages (#357)
* support playht3.0 languages

* update speech utils version
2024-10-16 07:34:02 -04:00
Dave Horton
84d83a0a48 playht test failing due to use of incorrect language name en-US (#356) 2024-10-14 20:58:23 -04:00
Hoan Luu Huu
b5bede7a08 add support for speechmatics languages and voices (#355) 2024-10-11 19:54:22 -04:00
Hoan Luu Huu
6e779f6744 support stt speechmatics (#353)
* support stt speechmatics

* support speechmatics region authentication

* update testcase for speechmatics_stt_uri
2024-10-11 09:17:40 -04:00
Hoan Luu Huu
77b9ca4cba update speech version 0.1.18 (#354) 2024-10-11 08:42:13 -04:00
Hoan Luu Huu
0451b6982c Merge pull request #350 from jambonz/feat/playht30
support playht3.0
2024-10-10 10:40:57 +07:00
Hoan Luu Huu
71adc577e9 Merge branch 'main' into feat/playht30 2024-10-10 10:38:13 +07:00
Hoan Luu Huu
e8b32103fe update speech version (#352) 2024-10-09 19:44:43 -04:00
Hoan Luu Huu
57d8d0a02c allow system information contains log level and account has enable_debug_log (#351)
* allow system information contains log level and account has enable_debug_log

* update upgrade db script
2024-10-07 09:52:11 -04:00
Quan HL
a41760fa9f PlayHT version 3.0 support PlayHt2.0 voices 2024-10-03 13:00:42 +07:00
Quan HL
c6bae80a03 support playht3.0 2024-09-27 11:13:59 +07:00
Dave Horton
4cddbd83a1 update to version of realtime-db with fix for expires (#349) 2024-09-18 08:24:47 -04:00
Dave Horton
6275aac341 bump version 2024-09-04 13:34:52 +01:00
Hoan Luu Huu
52de41c9bc support configuration to limit minimum value sipgatewa netmask can be used (#344) 2024-08-19 21:44:15 -04:00
Dave Horton
ed71abd675 added private_newtwork_cidr to system_information table (#341)
* added private_newtwork_cidr to system_information table

* db schema upgrade to add system_information.private_network_cidr in 0.9.2

* increase size of system_information.private_network_cidr to varchar(8192)
2024-08-18 12:49:06 -04:00
Hoan Luu Huu
2d2b98dab5 Feat/deepgram tts onprem (#338)
* support deepgram onpremise

* wip

* update speech utils version

* install docker in ci
2024-08-07 07:24:58 -04:00
Hoan Luu Huu
7553e2b617 update mysql2 version (#339) 2024-08-06 11:42:33 -04:00
Hoan Luu Huu
b921cab867 Support elevenlabs 2.5 (#336)
* update elevenlab model

* wip

* wip

* wip
2024-07-24 13:04:12 -04:00
19 changed files with 2410 additions and 1239 deletions

View File

@@ -7,11 +7,21 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Docker Compose
run: |
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose --version
- uses: actions/setup-node@v3
with:
node-version: lts/*
- run: npm install
- run: npm run jslint
- name: Install Docker Compose
run: |
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose --version
- run: npm test

View File

@@ -358,7 +358,9 @@ CREATE TABLE system_information
(
domain_name VARCHAR(255),
sip_domain_name VARCHAR(255),
monitoring_domain_name VARCHAR(255)
monitoring_domain_name VARCHAR(255),
private_network_cidr VARCHAR(8192),
log_level ENUM('info', 'debug') NOT NULL DEFAULT 'info'
);
CREATE TABLE users
@@ -552,6 +554,7 @@ siprec_hook_sid CHAR(36),
record_all_calls BOOLEAN NOT NULL DEFAULT false,
record_format VARCHAR(16) NOT NULL DEFAULT 'mp3',
bucket_credential VARCHAR(8192) COMMENT 'credential used to authenticate with storage service',
enable_debug_log BOOLEAN NOT NULL DEFAULT false,
PRIMARY KEY (account_sid)
) COMMENT='An enterprise that uses the platform for comm services';

File diff suppressed because one or more lines are too long

View File

@@ -197,6 +197,11 @@ const sql = {
'ALTER TABLE applications MODIFY COLUMN speech_synthesis_voice VARCHAR(256)',
'ALTER TABLE applications MODIFY COLUMN fallback_speech_synthesis_voice VARCHAR(256)',
'ALTER TABLE sip_gateways ADD COLUMN use_sips_scheme BOOLEAN NOT NULL DEFAULT 0',
],
9002: [
'ALTER TABLE system_information ADD COLUMN private_network_cidr VARCHAR(8192)',
'ALTER TABLE system_information ADD COLUMN log_level ENUM(\'info\', \'debug\') NOT NULL DEFAULT \'info\'',
'ALTER TABLE accounts ADD COLUMN enable_debug_log BOOLEAN NOT NULL DEFAULT false',
]
};
@@ -230,6 +235,7 @@ const doIt = async() => {
if (val < 8004) upgrades.push(...sql['8004']);
if (val < 8005) upgrades.push(...sql['8005']);
if (val < 9000) upgrades.push(...sql['9000']);
if (val < 9002) upgrades.push(...sql['9002']);
// perform all upgrades
logger.info({upgrades}, 'applying schema upgrades..');

View File

@@ -23,10 +23,11 @@ class SpeechCredential extends Model {
static async getSpeechCredentialsByVendorAndLabel(service_provider_sid, account_sid, vendor, label) {
let sql;
if (account_sid) {
sql = `SELECT * FROM speech_credentials WHERE account_sid = ? AND vendor = ? ${label ? 'AND label = ?' : ''}`;
sql = `SELECT * FROM speech_credentials WHERE account_sid = ? AND vendor = ?
AND label ${label ? '= ?' : 'is NULL'}`;
} else {
sql = `SELECT * FROM speech_credentials WHERE service_provider_sid = ? AND vendor = ?
${label ? 'AND label = ?' : ''}`;
AND label ${label ? '= ?' : 'is NULL'}`;
}
const [rows] = await promisePool.query(sql, [account_sid ? account_sid : service_provider_sid, vendor, label]);
return rows;

View File

@@ -33,6 +33,10 @@ SystemInformation.fields = [
name: 'monitoring_domain_name',
type: 'string',
},
{
name: 'private_network_cidr',
type: 'string',
},
];
module.exports = SystemInformation;

View File

@@ -41,6 +41,7 @@ const checkUserScope = async(req, voip_carrier_sid) => {
const validate = async(req, sid) => {
const {lookupSipGatewayBySid} = req.app.locals;
const {netmask} = req.body;
let voip_carrier_sid;
if (sid) {
@@ -52,6 +53,12 @@ const validate = async(req, sid) => {
voip_carrier_sid = req.body.voip_carrier_sid;
if (!voip_carrier_sid) throw new DbErrorBadRequest('missing voip_carrier_sid');
}
if (netmask &&
process.env.JAMBONZ_MIN_GATEWAY_NETMASK &&
parseInt(netmask) < process.env.JAMBONZ_MIN_GATEWAY_NETMASK) {
throw new DbErrorBadRequest(
`netmask required to have value equal or greater than ${process.env.JAMBONZ_MIN_GATEWAY_NETMASK}`);
}
await checkUserScope(req, voip_carrier_sid);
};

View File

@@ -10,7 +10,8 @@ const {decryptCredential, testWhisper, testDeepgramTTS,
testPlayHT,
testRimelabs,
testVerbioTts,
testVerbioStt} = require('../../utils/speech-utils');
testVerbioStt,
testSpeechmaticsStt} = require('../../utils/speech-utils');
const {DbErrorUnprocessableRequest, DbErrorForbidden, DbErrorBadRequest} = require('../../utils/errors');
const {
testGoogleTts,
@@ -122,8 +123,10 @@ const encryptCredential = (obj) => {
secret,
nuance_tts_uri,
nuance_stt_uri,
speechmatics_stt_uri,
deepgram_stt_uri,
deepgram_stt_use_tls,
deepgram_tts_uri,
use_custom_tts,
custom_tts_endpoint,
custom_tts_endpoint_url,
@@ -204,10 +207,10 @@ const encryptCredential = (obj) => {
case 'deepgram':
// API key is optional if onprem
if (!deepgram_stt_uri) {
if (!deepgram_stt_uri || !deepgram_tts_uri) {
assert(api_key, 'invalid deepgram speech credential: api_key is required');
}
const deepgramData = JSON.stringify({api_key, deepgram_stt_uri, deepgram_stt_use_tls});
const deepgramData = JSON.stringify({api_key, deepgram_stt_uri, deepgram_stt_use_tls, deepgram_tts_uri});
return encrypt(deepgramData);
case 'ibm':
@@ -235,6 +238,12 @@ const encryptCredential = (obj) => {
const elevenlabsData = JSON.stringify({api_key, model_id, options});
return encrypt(elevenlabsData);
case 'speechmatics':
assert(api_key, 'invalid speechmatics speech credential: api_key is required');
assert(speechmatics_stt_uri, 'invalid speechmatics speech credential: speechmatics_stt_uri is required');
const speechmaticsData = JSON.stringify({api_key, speechmatics_stt_uri, options});
return encrypt(speechmaticsData);
case 'playht':
assert(api_key, 'invalid playht speech credential: api_key is required');
assert(user_id, 'invalid playht speech credential: user_id is required');
@@ -458,6 +467,7 @@ router.put('/:sid', async(req, res) => {
options,
deepgram_stt_uri,
deepgram_stt_use_tls,
deepgram_tts_uri,
engine_version
} = req.body;
@@ -485,6 +495,7 @@ router.put('/:sid', async(req, res) => {
options,
deepgram_stt_uri,
deepgram_stt_use_tls,
deepgram_tts_uri,
engine_version
};
logger.info({o, newCred}, 'updating speech credential with this new credential');
@@ -765,6 +776,18 @@ router.get('/:sid/test', async(req, res) => {
SpeechCredential.ttsTestResult(sid, false);
}
}
} else if (cred.vendor === 'speechmatics') {
const {api_key} = credential;
if (cred.use_for_stt) {
try {
await testSpeechmaticsStt(logger, {api_key});
results.stt.status = 'ok';
SpeechCredential.ttsTestResult(sid, true);
} catch (err) {
results.stt = {status: 'fail', reason: err.message};
SpeechCredential.ttsTestResult(sid, false);
}
}
} else if (cred.vendor === 'playht') {
if (cred.use_for_tts) {
try {
@@ -852,7 +875,7 @@ router.get('/:sid/test', async(req, res) => {
router.get('/speech/supportedLanguagesAndVoices', async(req, res) => {
const {logger, getTtsVoices} = req.app.locals;
try {
const {vendor, label} = req.query;
const {vendor, label, create_new} = req.query;
if (!vendor) {
throw new DbErrorBadRequest('vendor is required');
}
@@ -860,7 +883,7 @@ router.get('/speech/supportedLanguagesAndVoices', async(req, res) => {
const service_provider_sid = req.user.service_provider_sid ||
req.body.service_provider_sid || parseServiceProviderSid(req);
const credentials = await SpeechCredential.getSpeechCredentialsByVendorAndLabel(
const credentials = create_new ? null : await SpeechCredential.getSpeechCredentialsByVendorAndLabel(
service_provider_sid, account_sid, vendor, label);
const tmp = credentials && credentials.length > 0 ? credentials[0] : null;
const cred = tmp ? JSON.parse(decrypt(tmp.credential)) : null;

View File

@@ -0,0 +1,218 @@
module.exports = [
{
name: 'Automatic',
value: 'auto',
},
{
name: 'Arabic',
value: 'ar',
},
{
name: 'Bashkir',
value: 'ba',
},
{
name: 'Basque',
value: 'eu',
},
{
name: 'Belarusian',
value: 'be',
},
{
name: 'Bulgarian',
value: 'bg',
},
{
name: 'Cantonese',
value: 'yue',
},
{
name: 'Catalan',
value: 'ca',
},
{
name: 'Croatian',
value: 'hr',
},
{
name: 'Czech',
value: 'cs',
},
{
name: 'Danish',
value: 'da',
},
{
name: 'Dutch',
value: 'nl',
},
{
name: 'English',
value: 'en',
},
{
name: 'Esperanto',
value: 'eo',
},
{
name: 'Estonian',
value: 'et',
},
{
name: 'Finnish',
value: 'fi',
},
{
name: 'French',
value: 'fr',
},
{
name: 'Galician',
value: 'gl',
},
{
name: 'German',
value: 'de',
},
{
name: 'Greek',
value: 'el',
},
{
name: 'Hebrew',
value: 'he',
},
{
name: 'Hindi',
value: 'hi',
},
{
name: 'Hungarian',
value: 'hu',
},
{
name: 'Irish',
value: 'ga',
},
{
name: 'Interlingua',
value: 'ia',
},
{
name: 'Italian',
value: 'it',
},
{
name: 'Indonesian',
value: 'id',
},
{
name: 'Japanese',
value: 'ja',
},
{
name: 'Korean',
value: 'ko',
},
{
name: 'Latvian',
value: 'lv',
},
{
name: 'Lithuanian',
value: 'lt',
},
{
name: 'Maltese',
value: 'mt',
},
{
name: 'Malay',
value: 'ms',
},
{
name: 'Mandarin',
value: 'cmn',
},
{
name: 'Marathi',
value: 'mr',
},
{
name: 'Mongolian',
value: 'mn',
},
{
name: 'Norwegian',
value: 'no',
},
{
name: 'Persian',
value: 'fa',
},
{
name: 'Polish',
value: 'pl',
},
{
name: 'Portuguese',
value: 'pt',
},
{
name: 'Romanian',
value: 'ro',
},
{
name: 'Russian',
value: 'ru',
},
{
name: 'Slovakian',
value: 'sk',
},
{
name: 'Slovenian',
value: 'sl',
},
{
name: 'Spanish',
value: 'es',
},
{
name: 'Spanish & English bilingual',
value: 'es',
},
{
name: 'Swedish',
value: 'sv',
},
{
name: 'Tamil',
value: 'ta',
},
{
name: 'Thai',
value: 'th',
},
{
name: 'Turkish',
value: 'tr',
},
{
name: 'Uyghur',
value: 'ug',
},
{
name: 'Ukrainian',
value: 'uk',
},
{
name: 'Vietnamese',
value: 'vi',
},
{
name: 'Welsh',
value: 'cy',
},
];

View File

@@ -0,0 +1,152 @@
// languages.js
module.exports = [
{
name: 'English',
value: 'english'
},
{
name: 'Mandarin',
value: 'mandarin'
},
{
name: 'Hindi',
value: 'hindi'
},
{
name: 'Japanese',
value: 'japanese'
},
{
name: 'Korean',
value: 'korean'
},
{
name: 'Arabic',
value: 'arabic'
},
{
name: 'Spanish',
value: 'spanish'
},
{
name: 'French',
value: 'french'
},
{
name: 'Italian',
value: 'italian'
},
{
name: 'Portuguese',
value: 'portuguese'
},
{
name: 'German',
value: 'german'
},
{
name: 'Dutch',
value: 'dutch'
},
{
name: 'Swedish',
value: 'swedish'
},
{
name: 'Czech',
value: 'czech'
},
{
name: 'Polish',
value: 'polish'
},
{
name: 'Russian',
value: 'russian'
},
{
name: 'Bulgarian',
value: 'bulgarian'
},
{
name: 'Hebrew',
value: 'hebrew'
},
{
name: 'Greek',
value: 'greek'
},
{
name: 'Turkish',
value: 'turkish'
},
{
name: 'Afrikaans',
value: 'afrikaans'
},
{
name: 'Xhosa',
value: 'xhosa'
},
{
name: 'Tagalog',
value: 'tagalog'
},
{
name: 'Malay',
value: 'malay'
},
{
name: 'Indonesian',
value: 'indonesian'
},
{
name: 'Bengali',
value: 'bengali'
},
{
name: 'Serbian',
value: 'serbian'
},
{
name: 'Thai',
value: 'thai'
},
{
name: 'Urdu',
value: 'urdu'
},
{
name: 'Croatian',
value: 'croatian'
},
{
name: 'Hungarian',
value: 'hungarian'
},
{
name: 'Danish',
value: 'danish'
},
{
name: 'Amharic',
value: 'amharic'
},
{
name: 'Albanian',
value: 'albanian'
},
{
name: 'Catalan',
value: 'catalan'
},
{
name: 'Ukrainian',
value: 'ukrainian'
},
{
name: 'Galician',
value: 'galician'
}
];

View File

@@ -1,5 +1,6 @@
module.exports = [
{ name: 'Turbo v2', value: 'eleven_turbo_v2' },
{ name: 'Turbo v2.5', value: 'eleven_turbo_v2_5' },
{ name: 'Multilingual v2', value: 'eleven_multilingual_v2' },
{ name: 'Multilingual v1', value: 'eleven_multilingual_v1' },
{ name: 'English v1', value: 'eleven_monolingual_v1' },

View File

@@ -1,4 +1,5 @@
module.exports = [
{ name: 'Play3.0', value: 'Play3.0' },
{ name: 'PlayHT2.0-turbo', value: 'PlayHT2.0-turbo' },
{ name: 'PlayHT2.0', value: 'PlayHT2.0' },
{ name: 'PlayHT1.0', value: 'PlayHT1.0' },

View File

@@ -7,6 +7,7 @@ const bent = require('bent');
const fs = require('fs');
const { AssemblyAI } = require('assemblyai');
const {decrypt, obscureKey} = require('./encrypt-decrypt');
const { RealtimeSession } = require('speechmatics');
const TtsGoogleLanguagesVoices = require('./speech-data/tts-google');
const TtsAwsLanguagesVoices = require('./speech-data/tts-aws');
@@ -24,6 +25,7 @@ const TtsModelDeepgram = require('./speech-data/tts-model-deepgram');
const TtsModelElevenLabs = require('./speech-data/tts-model-elevenlabs');
const TtsModelWhisper = require('./speech-data/tts-model-whisper');
const TtsModelPlayHT = require('./speech-data/tts-model-playht');
const ttsLanguagesPlayHt = require('./speech-data/tts-languages-playht');
const TtsModelRimelabs = require('./speech-data/tts-model-rimelabs');
const SttGoogleLanguagesVoices = require('./speech-data/stt-google');
@@ -35,9 +37,11 @@ const SttIbmLanguagesVoices = require('./speech-data/stt-ibm');
const SttNvidiaLanguagesVoices = require('./speech-data/stt-nvidia');
const SttCobaltLanguagesVoices = require('./speech-data/stt-cobalt');
const SttSonioxLanguagesVoices = require('./speech-data/stt-soniox');
const SttSpeechmaticsLanguagesVoices = require('./speech-data/stt-speechmatics');
const SttAssemblyaiLanguagesVoices = require('./speech-data/stt-assemblyai');
const SttVerbioLanguagesVoices = require('./speech-data/stt-verbio');
const testSonioxStt = async(logger, credentials) => {
const api_key = credentials;
const soniox = new SpeechClient(api_key);
@@ -54,6 +58,61 @@ const testSonioxStt = async(logger, credentials) => {
});
};
const testSpeechmaticsStt = async(logger, credentials) => {
const {api_key, speechmatics_stt_uri} = credentials;
return new Promise(async(resolve, reject) => {
try {
const session = new RealtimeSession({ apiKey: api_key, realtimeUrl: speechmatics_stt_uri });
let transcription = '';
session.addListener('Error', (error) => {
reject(error);
});
session.addListener('AddTranscript', (message) => {
transcription += message.metadata.transcript;
});
session.addListener('EndOfTranscript', () => {
resolve(transcription);
});
session
.start({
transcription_config: {
language: 'en',
operating_point: 'enhanced',
enable_partials: true,
max_delay: 2,
},
audio_format: { type: 'file' },
})
.then(() => {
//prepare file stream
const fileStream = fs.createReadStream(`${__dirname}/../../data/test_audio.wav`);
//send it
fileStream.on('data', (sample) => {
session.sendAudio(sample);
});
//end the session
fileStream.on('end', () => {
session.stop();
});
return;
})
.catch((error) => {
reject(error);
});
} catch (error) {
logger.info({error}, 'failed to get speechmatics transcript');
reject(error);
}
});
};
const testNuanceTts = async(logger, getTtsVoices, credentials) => {
const voices = await getTtsVoices({vendor: 'nuance', credentials});
return voices;
@@ -92,8 +151,8 @@ const testGoogleStt = async(logger, credentials) => {
};
const testDeepgramStt = async(logger, credentials) => {
const {api_key} = credentials;
const deepgram = new Deepgram(api_key);
const {api_key, deepgram_stt_uri, deepgram_stt_use_tls} = credentials;
const deepgram = new Deepgram(api_key, deepgram_stt_uri, deepgram_stt_uri && deepgram_stt_use_tls);
const mimetype = 'audio/wav';
const source = {
@@ -270,9 +329,10 @@ const testPlayHT = async(logger, synthAudio, credentials) => {
{
vendor: 'playht',
credentials,
language: 'en-US',
language: 'english',
voice: 's3://voice-cloning-zero-shot/d9ff78ba-d016-47f6-b0ef-dd630f59414e/female-cs/manifest.json',
text: 'Hi there and welcome to jambones!'
text: 'Hi there and welcome to jambones!',
renderForCaching: true
}
);
// Test if playHT can fetch voices
@@ -295,7 +355,8 @@ const testRimelabs = async(logger, synthAudio, credentials) => {
credentials,
language: 'en-US',
voice: 'amber',
text: 'Hi there and welcome to jambones!'
text: 'Hi there and welcome to jambones!',
renderForCaching: true
}
);
} catch (err) {
@@ -312,7 +373,8 @@ const testWhisper = async(logger, synthAudio, credentials) => {
credentials,
language: 'en-US',
voice: 'alloy',
text: 'Hi there and welcome to jambones!'
text: 'Hi there and welcome to jambones!',
renderForCaching: true
}
);
} catch (err) {
@@ -328,7 +390,8 @@ const testDeepgramTTS = async(logger, synthAudio, credentials) => {
vendor: 'deepgram',
credentials,
model: 'aura-asteria-en',
text: 'Hi there and welcome to jambones!'
text: 'Hi there and welcome to jambones!',
renderForCaching: true
}
);
} catch (err) {
@@ -383,7 +446,8 @@ const testVerbioTts = async(logger, synthAudio, credentials) => {
credentials,
language: 'en-US',
voice: 'tommy_en-us',
text: 'Hi there and welcome to jambones!'
text: 'Hi there and welcome to jambones!',
renderForCaching: true
}
);
} catch (err) {
@@ -509,6 +573,7 @@ function decryptCredential(obj, credential, logger, isObscureKey = true) {
obj.api_key = isObscureKey ? obscureKey(o.api_key) : o.api_key;
obj.deepgram_stt_uri = o.deepgram_stt_uri;
obj.deepgram_stt_use_tls = o.deepgram_stt_use_tls;
obj.deepgram_tts_uri = o.deepgram_tts_uri;
}
else if ('ibm' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
@@ -526,6 +591,10 @@ function decryptCredential(obj, credential, logger, isObscureKey = true) {
} else if ('soniox' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.api_key = isObscureKey ? obscureKey(o.api_key) : o.api_key;
} else if ('speechmatics' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.api_key = isObscureKey ? obscureKey(o.api_key) : o.api_key;
obj.speechmatics_stt_uri = o.speechmatics_stt_uri;
} else if ('elevenlabs' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.api_key = isObscureKey ? obscureKey(o.api_key) : o.api_key;
@@ -617,6 +686,8 @@ async function getLanguagesAndVoicesForVendor(logger, vendor, credential, getTts
return await getLanguagesVoicesForWhisper(credential, getTtsVoices, logger);
case 'verbio':
return await getLanguagesVoicesForVerbio(credential, getTtsVoices, logger);
case 'speechmatics':
return await getLanguagesVoicesForSpeechmatics(credential, getTtsVoices, logger);
default:
logger.info(`invalid vendor ${vendor}, return empty result`);
throw new Error(`Invalid vendor ${vendor}`);
@@ -729,6 +800,10 @@ async function getLanguagesVoicesForSoniox(credential) {
return tranform(undefined, SttSonioxLanguagesVoices);
}
async function getLanguagesVoicesForSpeechmatics(credential) {
return tranform(undefined, SttSpeechmaticsLanguagesVoices);
}
async function getLanguagesVoicesForElevenlabs(credential) {
if (credential) {
const get = bent('https://api.elevenlabs.io', 'GET', 'json', {
@@ -796,6 +871,7 @@ const fetchLayHTVoices = async(credential) => {
async function getLanguagesVoicesForPlayHT(credential) {
if (credential) {
const {voice_engine} = credential;
const [cloned_voice, voices] = await fetchLayHTVoices(credential);
const list_voices = [...cloned_voice, ...voices];
@@ -803,35 +879,57 @@ async function getLanguagesVoicesForPlayHT(credential) {
let name = `${d.name} -${concat(d.accent)}${concat(d.age)}${concat(d.gender)}${concat(d.loudness)}` +
`${concat(d.style)}${concat(d.tempo)}${concat(d.texture)}` ;
name = name.endsWith(',') ? name.trim().slice(0, -1) : name;
name += !d.language_code ? ' - Custom Voice' : '';
return {
value: `${d.id}`,
name
};
};
const ttsVoices = list_voices.reduce((acc, voice) => {
if (!credential.voice_engine.includes(voice.voice_engine)) {
const buildPlay30Payload = () => {
// PlayHT3.0 can play different languages with differrent voice.
// all voices will be added to english language by default and orther langauges will get voices from english.
const ttsVoices = ttsLanguagesPlayHt.map((l) => ({
...l,
voices: l.value === 'english' ? list_voices.map((v) => buildVoice(v)) : []
}));
return tranform(ttsVoices, undefined, TtsModelPlayHT);
};
const buildPayload = () => {
const ttsVoices = list_voices.reduce((acc, voice) => {
if (!voice_engine.includes(voice.voice_engine)) {
return acc;
}
const languageCode = voice.language_code;
// custom voice does not have language code
if (!languageCode) {
voice.language_code = 'en';
voice.language = 'Custom-English';
}
const existingLanguage = acc.find((lang) => lang.value === languageCode);
if (existingLanguage) {
existingLanguage.voices.push(buildVoice(voice));
} else {
acc.push({
value: voice.language_code,
name: voice.language,
voices: [buildVoice(voice)]
});
}
return acc;
}
const languageCode = voice.language_code;
// custom voice does not have language code
if (!languageCode) {
voice.language_code = 'en';
voice.language = 'Custom-English';
}
const existingLanguage = acc.find((lang) => lang.value === languageCode);
if (existingLanguage) {
existingLanguage.voices.push(buildVoice(voice));
} else {
acc.push({
value: voice.language_code,
name: voice.language,
voices: [buildVoice(voice)]
});
}
return acc;
}, []);
return tranform(ttsVoices, undefined, TtsModelPlayHT);
}, []);
return tranform(ttsVoices, undefined, TtsModelPlayHT);
};
switch (voice_engine) {
case 'Play3.0':
return buildPlay30Payload();
default:
return buildPayload();
}
}
return tranform(TtsPlayHtLanguagesVoices, undefined, TtsModelPlayHT);
}
@@ -1057,5 +1155,6 @@ module.exports = {
testWhisper,
testVerbioTts,
testVerbioStt,
getLanguagesAndVoicesForVendor
getLanguagesAndVoicesForVendor,
testSpeechmaticsStt
};

2890
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "jambonz-api-server",
"version": "0.9.0",
"version": "0.9.2",
"description": "",
"main": "app.js",
"scripts": {
@@ -28,8 +28,8 @@
"@jambonz/db-helpers": "^0.9.3",
"@jambonz/lamejs": "^1.2.2",
"@jambonz/mw-registrar": "^0.2.7",
"@jambonz/realtimedb-helpers": "^0.8.9",
"@jambonz/speech-utils": "^0.1.11",
"@jambonz/realtimedb-helpers": "^0.8.10",
"@jambonz/speech-utils": "^0.1.20",
"@jambonz/time-series": "^0.2.8",
"@jambonz/verb-specifications": "^0.0.72",
"@soniox/soniox-node": "^1.2.2",
@@ -46,12 +46,13 @@
"jsonwebtoken": "^9.0.2",
"mailgun.js": "^10.2.1",
"microsoft-cognitiveservices-speech-sdk": "1.36.0",
"mysql2": "^3.9.3",
"mysql2": "^3.11.0",
"nocache": "4.0.0",
"passport": "^0.7.0",
"passport-http-bearer": "^1.0.1",
"pino": "^8.20.0",
"short-uuid": "^4.2.2",
"speechmatics": "^4.0.0",
"stream-buffers": "^3.0.2",
"stripe": "^14.24.0",
"swagger-ui-express": "^5.0.0",

View File

@@ -152,7 +152,9 @@ test('account tests', async(t) => {
auth: authAdmin,
json: true,
});
console.log(result);
t.ok(result.name === 'daveh' , 'successfully retrieved account by sid');
t.ok(result.enable_debug_log === 0 , 'enable_debug_log default value ok');
/* update account with account level token */
result = await request.put(`/Accounts/${sid}`, {
@@ -177,8 +179,8 @@ test('account tests', async(t) => {
name: 'recordings',
access_key_id: 'access_key_id',
secret_access_key: 'secret access key'
}
},
enable_debug_log: true
}
});
t.ok(result.statusCode === 204, 'successfully updated account using account level token');
@@ -194,6 +196,7 @@ test('account tests', async(t) => {
t.ok(result.bucket_credential.access_key_id === 'access_key_id', 'bucket_access_key_id was updated');
t.ok(result.record_all_calls === 1, 'record_all_calls was updated');
t.ok(result.record_format === 'wav', 'record_format was updated');
t.ok(result.enable_debug_log, 'enable_debug_log was updated');
/* verify that account level api key last_used was updated*/
result = await request.get(`/Accounts/${sid}/ApiKeys`, {

View File

@@ -17,6 +17,53 @@ test('sip gateway tests', async(t) => {
let result;
const voip_carrier_sid = await createVoipCarrier(request);
/* add a invalid sip gateway */
const STORED_JAMBONZ_MIN_GATEWAY_NETMASK = process.env.JAMBONZ_MIN_GATEWAY_NETMASK;
process.env.JAMBONZ_MIN_GATEWAY_NETMASK = 24;
result = await request.post('/SipGateways', {
resolveWithFullResponse: true,
auth: authAdmin,
json: true,
simple: false,
body: {
voip_carrier_sid,
ipv4: '1.2.3.4',
netmask: 1,
inbound: true,
outbound: true,
protocol: 'tcp'
}
});
t.ok(result.statusCode === 400, 'successfully created sip gateway ');
result = await request.post('/SipGateways', {
resolveWithFullResponse: true,
auth: authAdmin,
json: true,
body: {
voip_carrier_sid,
ipv4: '1.2.3.4',
netmask: 24,
inbound: true,
outbound: true,
protocol: 'tcp'
}
});
t.ok(result.statusCode === 201, 'successfully created sip gateway ');
process.env.JAMBONZ_MIN_GATEWAY_NETMASK = STORED_JAMBONZ_MIN_GATEWAY_NETMASK;
/* delete sip gateways */
result = await request.delete(`/SipGateways/${result.body.sid}`, {
resolveWithFullResponse: true,
simple: false,
json: true,
auth: authAdmin
});
//console.log(`result: ${JSON.stringify(result)}`);
t.ok(result.statusCode === 204, 'successfully deleted sip gateway');
/* add a sip gateway */
result = await request.post('/SipGateways', {
resolveWithFullResponse: true,

View File

@@ -371,7 +371,8 @@ test('speech credentials tests', async(t) => {
vendor: 'deepgram',
use_for_stt: true,
deepgram_stt_uri: "127.0.0.1:50002",
deepgram_stt_use_tls: true
deepgram_stt_use_tls: true,
deepgram_tts_uri: 'https://server.com'
}
});
t.ok(result.statusCode === 201, 'successfully added speech credential for deepgram');
@@ -386,6 +387,7 @@ test('speech credentials tests', async(t) => {
t.ok(result.statusCode === 200, 'successfully get speech credential for deepgram');
t.ok(result.body.deepgram_stt_uri === '127.0.0.1:50002', "deepgram_stt_uri is correct for deepgram");
t.ok(result.body.deepgram_stt_use_tls === true, "deepgram_stt_use_tls is correct for deepgram");
t.ok(result.body.deepgram_tts_uri === 'https://server.com', "deepgram_tts_uri is correct for deepgram")
result = await request.put(`/Accounts/${account_sid}/SpeechCredentials/${dg_sid}`, {
resolveWithFullResponse: true,
@@ -395,7 +397,8 @@ test('speech credentials tests', async(t) => {
vendor: 'deepgram',
use_for_stt: true,
deepgram_stt_uri: "127.0.0.2:50002",
deepgram_stt_use_tls: false
deepgram_stt_use_tls: false,
deepgram_tts_uri: 'https://server2.com'
}
});
t.ok(result.statusCode === 204, 'successfully updated speech credential for deepgram onprem');
@@ -409,6 +412,7 @@ test('speech credentials tests', async(t) => {
t.ok(result.statusCode === 200, 'successfully get speech credential for deepgram onprem');
t.ok(result.body.deepgram_stt_uri === '127.0.0.2:50002', "deepgram_stt_uri is correct for deepgram onprem");
t.ok(result.body.deepgram_stt_use_tls === false, "deepgram_stt_use_tls is correct for deepgram onprem");
t.ok(result.body.deepgram_tts_uri === 'https://server2.com', "deepgram_tts_uri is correct for deepgram onprem");
result = await request.delete(`/Accounts/${account_sid}/SpeechCredentials/${dg_sid}`, {
auth: authUser,
@@ -528,6 +532,39 @@ test('speech credentials tests', async(t) => {
t.ok(result.statusCode === 204, 'successfully deleted speech credential');
}
/* add a credential for Speechmatics */
if (process.env.SPEECHMATICS_API_KEY) {
result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
body: {
vendor: 'speechmatics',
use_for_stt: true,
api_key: process.env.SPEECHMATICS_API_KEY,
speechmatics_stt_uri: 'eu2.rt.speechmatics.com'
}
});
t.ok(result.statusCode === 201, 'successfully added speech credential for speechmatics');
const ms_sid = result.body.sid;
/* test the speech credential */
result = await request.get(`/Accounts/${account_sid}/SpeechCredentials/${ms_sid}/test`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
});
console.log(JSON.stringify(result));
t.ok(result.statusCode === 200 && result.body.stt.status === 'ok', 'successfully tested speech credential for speechmatics');
/* delete the credential */
result = await request.delete(`/Accounts/${account_sid}/SpeechCredentials/${ms_sid}`, {
auth: authUser,
resolveWithFullResponse: true,
});
t.ok(result.statusCode === 204, 'successfully deleted speech credential');
}
/* add a credential for nvidia */
result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, {
resolveWithFullResponse: true,

View File

@@ -16,7 +16,9 @@ test('system information test', async(t) => {
body: {
domain_name: 'test.com',
sip_domain_name: 'sip.test.com',
monitoring_domain_name: 'monitor.test.com'
monitoring_domain_name: 'monitor.test.com',
private_network_cidr: '192.168.1.0/24, 10.10.100.1',
log_level: 'info'
}
});
t.ok(result.statusCode === 201, 'successfully created system information ');
@@ -24,6 +26,8 @@ test('system information test', async(t) => {
t.ok(body.domain_name === 'test.com', 'added domain_name ok');
t.ok(body.sip_domain_name === 'sip.test.com', 'added sip_domain_name ok');
t.ok(body.monitoring_domain_name === 'monitor.test.com', 'added monitoring_domain_name ok');
t.ok(body.private_network_cidr === '192.168.1.0/24, 10.10.100.1', 'added private_network_cidr ok');
t.ok(body.log_level === 'info', 'added log_level ok');
result = await request.get('/SystemInformation', {
auth: authAdmin,
@@ -32,6 +36,8 @@ test('system information test', async(t) => {
t.ok(result.domain_name === 'test.com', 'get domain_name ok');
t.ok(result.sip_domain_name === 'sip.test.com', 'get sip_domain_name ok');
t.ok(result.monitoring_domain_name === 'monitor.test.com', 'get monitoring_domain_name ok');
t.ok(result.private_network_cidr === '192.168.1.0/24, 10.10.100.1', 'get private_network_cidr ok');
t.ok(result.log_level === 'info', 'added log_level ok');
result = await request.post('/SystemInformation', {
resolveWithFullResponse: true,
@@ -40,7 +46,9 @@ test('system information test', async(t) => {
body: {
domain_name: 'test1.com',
sip_domain_name: 'sip1.test.com',
monitoring_domain_name: 'monitor1.test.com'
monitoring_domain_name: 'monitor1.test.com',
private_network_cidr: '',
log_level: 'debug'
}
});
t.ok(result.statusCode === 201, 'successfully updated system information ');
@@ -48,6 +56,8 @@ test('system information test', async(t) => {
t.ok(body.domain_name === 'test1.com', 'updated domain_name ok');
t.ok(body.sip_domain_name === 'sip1.test.com', 'updated sip_domain_name ok');
t.ok(body.monitoring_domain_name === 'monitor1.test.com', 'updated monitoring_domain_name ok');
t.ok(body.private_network_cidr === '', 'updated private_network_cidr ok');
t.ok(body.log_level === 'debug', 'updated log_level ok');
result = await request.get('/SystemInformation', {
auth: authAdmin,
@@ -56,6 +66,7 @@ test('system information test', async(t) => {
t.ok(result.domain_name === 'test1.com', 'get domain_name ok');
t.ok(result.sip_domain_name === 'sip1.test.com', 'get sip_domain_name ok');
t.ok(result.monitoring_domain_name === 'monitor1.test.com', 'get monitoring_domain_name ok');
t.ok(result.log_level === 'debug', 'updated log_level ok');
} catch(err) {
console.error(err);