Compare commits

..

11 Commits

Author SHA1 Message Date
Quan HL
7f904930ae fix azure cannot download mp3 if encodingMp3 = true 2024-05-03 19:43:45 +07:00
Hoan Luu Huu
c0fab2880b fix cannot send multipart to aws due to min size (#319) 2024-05-03 07:37:38 -04:00
Hoan Luu Huu
ce2fa392a4 support aws speech by roleArn (#313)
* support aws speech by roleArn

* support 3 types of aws  credentials

* wip

* wip

* update speech util version
2024-05-02 07:57:22 -04:00
Hoan Luu Huu
3b47162d13 Feat/record with pipeline (#318)
* use pipeline for nodejs streams

* use pipeline for nodejs streams
2024-04-30 07:39:24 -04:00
Hoan Luu Huu
b765232d4f api server cannot synthesize text after upgrade latest speech-utils (#317)
* api server cannot synthesize text after upgrade latest speech-utils

* wip

* add testcase for synthesize text

* fix synthesize testcase
2024-04-29 19:48:34 -04:00
Dave Horton
2436bea6ea add support for LCC updateCall with conferenceParticipantState (#296)
* add support for LCC updateCall with conferenceParticipantState

* wip

* wip
2024-04-22 11:06:08 -04:00
Dave Horton
f67abddbd4 bug: attempting to add duplicate dns records on hosted system (#312) 2024-04-19 18:13:27 -04:00
Hoan Luu Huu
39fcb17dec support mod_rimelabs_tts (#310)
* support mod_rimelabs_tts

* update speech utils 0.0.51
2024-04-12 07:25:04 -04:00
Hoan Luu Huu
80418aa7e5 check playht can fetch voices when adding new speech credential (#309)
* check playht can fetch voices when adding new speech credential

* wip

* wip

* wip

* wip
2024-04-12 07:01:13 -04:00
Hoan Luu Huu
b21d10eb3e fetch playht custom voice (#307) 2024-04-09 08:48:18 -04:00
Hoan Luu Huu
7875eb51b9 playht should return list of voice match voice engine configured at speech credentials (#306) 2024-04-09 06:53:17 -04:00
14 changed files with 1070 additions and 53 deletions

2
app.js
View File

@@ -52,6 +52,7 @@ const {
getTtsVoices,
getTtsSize,
purgeTtsCache,
getAwsAuthToken,
synthAudio
} = require('@jambonz/speech-utils')({}, logger);
const {
@@ -95,6 +96,7 @@ app.locals = {
deleteKey,
getTtsVoices,
getTtsSize,
getAwsAuthToken,
purgeTtsCache,
synthAudio,
lookupAppBySid,

View File

@@ -16,7 +16,7 @@ class S3MultipartUploadStream extends Writable {
this.partNumber = 1;
this.multipartETags = [];
this.buffer = Buffer.alloc(0);
this.minPartSize = 2 * 1024 * 1024; // 5 MB
this.minPartSize = 5 * 1024 * 1024; // 5 MB
this.s3 = new S3Client(opts.bucketCredential);
this.metadata = opts.metadata;
}

View File

@@ -3,6 +3,7 @@ const Websocket = require('ws');
const PCMToMP3Encoder = require('./encoder');
const wav = require('wav');
const { getUploader } = require('./utils');
const { pipeline } = require('stream');
async function upload(logger, socket) {
socket._recvInitialMetadata = false;
@@ -60,22 +61,19 @@ async function upload(logger, socket) {
bitrate: 128
}, logger);
}
const handleError = (err, streamType) => {
logger.error(
{ err },
`Error while streaming for vendor: ${obj.vendor}, pipe: ${streamType}: ${err.message}`
);
};
/* start streaming data */
const duplex = Websocket.createWebSocketStream(socket);
duplex
.on('error', (err) => handleError(err, 'duplex'))
.pipe(encoder)
.on('error', (err) => handleError(err, 'encoder'))
.pipe(uploadStream)
.on('error', (err) => handleError(err, 'uploadStream'));
pipeline(
Websocket.createWebSocketStream(socket),
encoder,
uploadStream,
(error) => {
if (error) {
logger.error({ error }, 'pipeline error, cannot upload data to storage');
socket.close();
}
}
);
} else {
logger.info(`account ${accountSid} does not have any bucket credential, close the socket`);
socket.close();

View File

@@ -265,7 +265,8 @@ function validateUpdateCall(opts) {
'sip_request',
'record',
'tag',
'dtmf'
'dtmf',
'conferenceParticipantAction'
]
.reduce((acc, prop) => (opts[prop] ? ++acc : acc), 0);
@@ -316,6 +317,19 @@ function validateUpdateCall(opts) {
if (opts.tag && (typeof opts.tag !== 'object' || Array.isArray(opts.tag) || opts.tag === null)) {
throw new DbErrorBadRequest('invalid tag data');
}
if (opts.conferenceParticipantAction) {
if (!['tag', 'untag', 'coach', 'uncoach', 'mute', 'unmute', 'hold', 'unhold']
.includes(opts.conferenceParticipantAction.action)) {
throw new DbErrorBadRequest(
`conferenceParticipantAction invalid action property ${opts.conferenceParticipantAction.action}`);
}
if ('tag' == opts.conferenceParticipantAction.action && !opts.tag) {
throw new DbErrorBadRequest('conferenceParticipantAction requires tag property when action is \'tag\'');
}
if ('coach' == opts.conferenceParticipantAction.action && !opts.tag) {
throw new DbErrorBadRequest('conferenceParticipantAction requires tag property when action is \'coach\'');
}
}
}
function validateTo(to) {

View File

@@ -31,6 +31,7 @@ router.post('/:sip_realm', async(req, res) => {
const [sbcs] = await promisePool.query('SELECT ipv4 from sbc_addresses');
if (sbcs.length === 0) throw new Error('no SBC addresses provisioned in the database!');
const ips = sbcs.map((s) => s.ipv4);
const uniqueIps = [...new Set(ips)];
/* retrieve existing dns records */
const [old_recs] = await promisePool.query('SELECT record_id from dns_records WHERE account_sid = ?',
@@ -48,7 +49,7 @@ router.post('/:sip_realm', async(req, res) => {
}
/* add the dns records */
const records = await createDnsRecords(logger, domain, subdomain, ips);
const records = await createDnsRecords(logger, domain, subdomain, uniqueIps);
if (!records) throw new Error(`failure updating dns records for ${sip_realm}`);
const values = records.map((r) => {
return `('${uuid()}', '${account_sid}', '${r.type}', ${r.id})`;

View File

@@ -7,7 +7,8 @@ const {decrypt, encrypt} = require('../../utils/encrypt-decrypt');
const {parseAccountSid, parseServiceProviderSid, parseSpeechCredentialSid} = require('./utils');
const {decryptCredential, testWhisper, testDeepgramTTS,
getLanguagesAndVoicesForVendor,
testPlayHT} = require('../../utils/speech-utils');
testPlayHT,
testRimelabs} = require('../../utils/speech-utils');
const {DbErrorUnprocessableRequest, DbErrorForbidden, DbErrorBadRequest} = require('../../utils/errors');
const {
testGoogleTts,
@@ -112,6 +113,7 @@ const encryptCredential = (obj) => {
secret_access_key,
aws_region,
api_key,
role_arn,
region,
client_id,
secret,
@@ -154,10 +156,17 @@ const encryptCredential = (obj) => {
return encrypt(service_key);
case 'aws':
assert(access_key_id, 'invalid aws speech credential: access_key_id is required');
assert(secret_access_key, 'invalid aws speech credential: secret_access_key is required');
assert(aws_region, 'invalid aws speech credential: aws_region is required');
const awsData = JSON.stringify({aws_region, access_key_id, secret_access_key});
// AWS polly can work for 3 types of credentials:
// 1/ access_key_id and secret_access_key
// 2/ RoleArn Assume role
// 3/ RoleArn assigned to instance profile where will run this application
const awsData = JSON.stringify(
{
aws_region,
...(access_key_id && {access_key_id}),
...(secret_access_key && {secret_access_key}),
...(role_arn && {role_arn}),
});
return encrypt(awsData);
case 'microsoft':
@@ -229,6 +238,12 @@ const encryptCredential = (obj) => {
const playhtData = JSON.stringify({api_key, user_id, voice_engine, options});
return encrypt(playhtData);
case 'rimelabs':
assert(api_key, 'invalid rimelabs speech credential: api_key is required');
assert(model_id, 'invalid rimelabs speech credential: model_id is required');
const rimelabsData = JSON.stringify({api_key, model_id, options});
return encrypt(rimelabsData);
case 'assemblyai':
assert(api_key, 'invalid assemblyai speech credential: api_key is required');
const assemblyaiData = JSON.stringify({api_key});
@@ -534,12 +549,13 @@ router.get('/:sid/test', async(req, res) => {
}
}
else if (cred.vendor === 'aws') {
const {getTtsVoices, getAwsAuthToken} = req.app.locals;
if (cred.use_for_tts) {
const {getTtsVoices} = req.app.locals;
try {
await testAwsTts(logger, getTtsVoices, {
accessKeyId: credential.access_key_id,
secretAccessKey: credential.secret_access_key,
roleArn: credential.role_arn,
region: credential.aws_region || process.env.AWS_REGION
});
results.tts.status = 'ok';
@@ -551,9 +567,10 @@ router.get('/:sid/test', async(req, res) => {
}
if (cred.use_for_stt) {
try {
await testAwsStt(logger, {
await testAwsStt(logger, getAwsAuthToken, {
accessKeyId: credential.access_key_id,
secretAccessKey: credential.secret_access_key,
roleArn: credential.role_arn,
region: credential.aws_region || process.env.AWS_REGION
});
results.stt.status = 'ok';
@@ -742,6 +759,22 @@ router.get('/:sid/test', async(req, res) => {
await testPlayHT(logger, synthAudio, credential);
results.tts.status = 'ok';
SpeechCredential.ttsTestResult(sid, true);
} catch (err) {
let reason = err.message;
// if error is from bent, let get the body
try {
reason = await err.text();
} catch {}
results.tts = {status: 'fail', reason};
SpeechCredential.ttsTestResult(sid, false);
}
}
} else if (cred.vendor === 'rimelabs') {
if (cred.use_for_tts) {
try {
await testRimelabs(logger, synthAudio, credential);
results.tts.status = 'ok';
SpeechCredential.ttsTestResult(sid, true);
} catch (err) {
results.tts = {status: 'fail', reason: err.message};
SpeechCredential.ttsTestResult(sid, false);

View File

@@ -10,6 +10,7 @@ const Account = require('../../models/account');
const sysError = require('../error');
const { getSpeechCredential, decryptCredential } = require('../../utils/speech-utils');
const PCMToMP3Encoder = require('../../record/encoder');
const { pipeline } = require('stream');
router.delete('/', async(req, res) => {
const {purgeTtsCache} = req.app.locals;
@@ -69,6 +70,8 @@ router.post('/Synthesize', async(req, res) => {
voice = arr[1];
model = arr[2];
}
} else if (cred.vendor === 'deepgram') {
model = voice;
}
const stats = {
histogram: () => {},
@@ -84,7 +87,8 @@ router.post('/Synthesize', async(req, res) => {
model,
salt,
credentials: cred,
disableTtsCache: false
disableTtsCache: false,
disableTtsStreaming: true
});
let contentType = 'audio/mpeg';
@@ -92,17 +96,27 @@ router.post('/Synthesize', async(req, res) => {
let readStream = fs.createReadStream(filePath);
if (['nuance', 'nvidia'].includes(cred.vendor) ||
(
process.env.JAMBONES_TTS_TRIM_SILENCE &&
(process.env.JAMBONES_TTS_TRIM_SILENCE || !process.env.JAMBONES_DISABLE_TTS_STREAMING) &&
['microsoft', 'azure'].includes(cred.vendor)
)
) {
if (encodingMp3) {
readStream = readStream
.pipe(new PCMToMP3Encoder({
readStream = pipeline(
readStream,
new PCMToMP3Encoder({
channels: 1,
sampleRate: 8000,
bitRate: 128
}, logger));
}, logger),
(err) => {
if (err) {
logger.error('ttscache/Synthesize failed:', err);
if (!res.headersSent) {
res.status(500).end('Server error');
}
}
}
);
} else {
contentType = 'application/octet-stream';
}
@@ -110,10 +124,17 @@ router.post('/Synthesize', async(req, res) => {
res.writeHead(200, {
'Content-Type': contentType,
});
readStream.pipe(res);
readStream.on('end', () => {
fs.unlink(filePath, (err) => {
if (err) throw err;
pipeline(readStream, res, (err) => {
if (err) {
logger.error('ttscache/Synthesize failed:', err);
if (!res.headersSent) {
res.status(500).end('Server error');
}
}
fs.unlink(filePath, (unlinkErr) => {
if (unlinkErr) throw unlinkErr;
logger.info(`${filePath} was deleted`);
});
});

View File

@@ -4152,6 +4152,22 @@ paths:
type: string
siprecServerURL:
type: string
conferenceParticipantAction:
type: object
properties:
action:
type: string
enum:
- tag
- untag
- coach
- uncoach
- mute
- unmute
- hold
- unhold
tag:
type: string
responses:
200:
description: Accepted

View File

@@ -0,0 +1,5 @@
module.exports = [
{ name: 'Mist', value: 'mist' },
{ name: 'V1', value: 'v1' },
];

View File

@@ -0,0 +1,710 @@
module.exports = [
{
value: 'en-US',
name: 'English (US)',
voices: [
{
value:
's3://mockingbird-prod/abigail_vo_6661b91f-4012-44e3-ad12-589fbdee9948/voices/speaker/manifest.json',
name: 'Abigail - american, female, narrative, smooth',
},
{
value: 'abram',
name: 'Abram - british, old, male, low, narrative, slow, round',
},
{
value: 'adolfo',
name: 'Adolfo - american, adult, male, neutral, narrative, fast, thick',
},
{
value: 'adrian',
name: 'Adrian - american, old, male, neutral, narrative, fast, thick',
},
{
value: 'ahmed',
name: 'Logan - british, old, male, neutral, narrative, neutral, thick',
},
{
value: 'alex',
name: 'Alex - british, adult, male, high, narrative, slow, thick',
},
{
value: 'alexander',
name: 'Alexander - british, old, male, high, narrative, fast, thick',
},
{
value: 'alfonso',
name: 'Alfonso - american, adult, male, neutral, videos, neutral, gravelly',
},
{
value: 'alphonso',
name: 'Alphonso - american, adult, female, low, videos, neutral, smooth',
},
{
value: 'amado',
name: 'Amado - american, old, male, low, narrative, fast, smooth',
},
{
value: 'anny',
name: 'Anny - american, youth, female, neutral, narrative, neutral, thick',
},
{
value: 'anthony',
name: 'Anthony - american, adult, male, neutral, training, slow, thick',
},
{
value: 'spencer',
name: 'April - british, adult, female, neutral, narrative, slow, smooth',
},
{
value: 'victor',
name: 'Ariana - american, youth, female, high, videos, fast, thick',
},
{
value: 'arthur',
name: 'Arthur - british, adult, male, neutral, narrative, neutral, smooth',
},
{
value: 'aubrey',
name: 'Aubrey - british, adult, male, neutral, videos, neutral, smooth',
},
{
value: 'hipolito',
name: 'Audrey - american, adult, female, low, narrative, slow, round',
},
{
value: 'aurora',
name: 'Aurora - british, adult, female, low, training, slow, round',
},
{
value: 'axel',
name: 'Axel - american, adult, male, neutral, narrative, fast, thick',
},
{
value:
's3://mockingbird-prod/ayla_vo_commercials_d66900d5-69f5-476f-9bd6-8eab2936dda3/voices/speaker/manifest.json',
name: 'Ayla (Advertising) - american, female, advertising',
},
{
value:
's3://mockingbird-prod/ayla_vo_expressive_16095e08-b9e8-429b-947c-47a75e41053b/voices/speaker/manifest.json',
name: 'Ayla (Expressive) - american, female, narrative',
},
{
value:
's3://mockingbird-prod/ayla_vo_meditation_d11dd9da-b5f1-4709-95a6-e6d5dc77614a/voices/speaker/manifest.json',
name: 'Ayla (Meditation) - american, female, meditation',
},
{
value:
's3://mockingbird-prod/ayla_vo_narrative_d8199dfd-b50f-40c7-9d99-e203ba5f4152/voices/speaker/manifest.json',
name: 'Ayla (Narrative) - american, female, narrative',
},
{
value:
's3://mockingbird-prod/ayla_vo_training_e6751ca5-e47c-4c4b-ad05-d3a194417600/voices/speaker/manifest.json',
name: 'Ayla (Training) - american, female, training',
},
{
value: 'benton',
name: 'Benton - american, old, male, high, videos, fast, smooth',
},
{
value: 'bertram',
name: 'Bertram - british, adult, male, low, narrative, neutral, gravelly',
},
{
value: 'bill',
name: 'Harper - american, adult, female, high, videos, fast, smooth',
},
{
// eslint-disable-next-line max-len
value:'s3://mockingbird-prod/nathan_drake_carmelo_pampillonio_7d540ad6-7d32-41f6-8d53-2584901aa03d/voices/speaker/manifest.json',
name: 'Billy - american, male, gaming',
},
{
value: 'blaine',
name: 'Blaine - british, adult, male, high, narrative, neutral, thick',
},
{
value: 'booker',
name: 'Booker - british, youth, male, neutral, narrative, neutral, round',
},
{
value: 'bret',
name: 'Bret - american, adult, female, neutral, narrative, slow, smooth',
},
{
value: 'bruce',
name: 'Bruce - british, adult, male, high, training, fast, thick',
},
{
value: 'bryan',
name: 'Bryan - american, adult, male, low, videos, fast, gravelly',
},
{
value: 'carlo',
name: 'Carlo - british, adult, male, neutral, advertising, neutral, smooth',
},
{
value: 'carter',
name: 'Carter - american, adult, male, neutral, narrative, neutral, thick',
},
{
value: 'charles',
name: 'Charles - american, adult, male, neutral, narrative, neutral, round',
},
{
value: 'charlotte',
name: 'Charlotte - canadian, adult, female, low, narrative, neutral, smooth',
},
{
value:
's3://voice-cloning-zero-shot/028a32d4-6a79-4ca3-a303-d6559843114b/chris/manifest.json',
name: 'Chris - american, adult, male,',
},
{
value: 'chuck',
name: 'Chuck - british, adult, male, neutral, videos, slow, round',
},
{
value: 'clark',
name: 'Clark - british, old, male, neutral, narrative, slow, smooth',
},
{
value: 'clifton',
name: 'Clifton - american, old, male, high, narrative, neutral, gravelly',
},
{
value: 'hayden',
name: 'Cooper - american, adult, male, neutral, narrative, neutral, round',
},
{
value: 'daisy',
name: 'Daisy - british, adult, female, low, narrative, neutral, gravelly',
},
{
value: 'dane',
name: 'Dane - american, adult, male, neutral, videos, neutral, round',
},
{
value: 'daniel',
name: 'Daniel - canadian, adult, male, low, narrative, neutral, smooth',
},
{
value: 'darnell',
name: 'Darnell - american, youth, male, neutral, narrative, neutral, smooth',
},
{
value: 'daron',
name: 'Daron - american, old, male, low, narrative, slow, round',
},
{
value: 'darrell',
name: 'Darrell - british, adult, male, neutral, advertising, neutral, thick',
},
{
value: 's3://peregrine-voices/a10/manifest.json',
name: 'Davis - american, adult, male,',
},
{
value: 'ignacio',
name: 'Delilah - american, adult, female, neutral, narrative, slow, smooth',
},
{
value: 'denis',
name: 'Eleanor - british, adult, female, neutral, advertising, neutral, smooth',
},
{
value: 'dick',
name: 'Dick - american, adult, male, neutral, training, fast, smooth',
},
{
value: 'domenic',
name: 'Domenic - british, adult, male, high, videos, neutral, thick',
},
{
value: 's3://peregrine-voices/donna_meditation_saad/manifest.json',
name: 'Donna (Meditation) - american, female, meditation',
},
{
value: 's3://peregrine-voices/donna_parrot_saad/manifest.json',
name: 'Donna (Narrative) - american, female, narrative',
},
{
value: 'donovan',
name: 'Donovan - american, adult, male, low, narrative, neutral, smooth',
},
{
value: 'dudley',
name: 'Dudley - american, old, male, low, narrative, fast, smooth',
},
{
value: 'dylan',
name: 'Dylan - british, old, male, high, gaming, slow, smooth',
},
{
value: 'earle',
name: 'Earle - british, adult, male, high, narrative, neutral, gravelly',
},
{
value: 'efren',
name: 'Efren - american, adult, male, neutral, training, slow, thick',
},
{
value: 'denis',
name: 'Eleanor - british, adult, female, neutral, advertising, neutral, smooth',
},
{
value: 'elijah',
name: 'Elijah - american, old, male, neutral, training, neutral, gravelly',
},
{
value: 'ellie',
name: 'Ellie - american, adult, female, low, training, slow, smooth',
},
{
value: 'erasmo',
name: 'Erasmo - american, old, male, low, training, fast, smooth',
},
{
value: 's3://peregrine-voices/evelyn 2 saad parrot/manifest.json',
name: 'Evelyn - american, adult, female, low, videos, neutral, smooth',
},
{
value: 'fletcher',
name: 'Fletcher - british, adult, male, neutral, narrative, fast, gravelly',
},
{
value: 'florencio',
name: 'Madison - british, old, female, neutral, narrative, slow, round',
},
{
value: 'flynn',
name: 'Flynn - british, adult, male, neutral, narrative, fast, round',
},
{
value: 'gabriel',
name: 'Samantha - american, old, female, neutral, narrative, neutral, thick',
},
{
value: 'greg',
name: 'Greg - british, adult, male, high, narrative, slow, round',
},
{
value: 'harold',
name: 'Harold - american, adult, male, neutral, narrative, slow, smooth',
},
{
value: 'bill',
name: 'Harper - american, adult, female, high, videos, fast, smooth',
},
{
value: 'harris',
name: 'Harris - british, adult, male, low, narrative, fast, smooth',
},
{
value: 'harrison',
name: 'Harrison - american, adult, male, neutral, narrative, fast, round',
},
{
value: 'hayden',
name: 'Cooper - american, adult, male, neutral, narrative, neutral, round',
},
{
value: 'hipolito',
name: 'Audrey - american, adult, female, low, narrative, slow, round',
},
{
value:
's3://mockingbird-prod/hook_1_chico_a3e5e83f-08ae-4a9f-825c-7e48d32d2fd8/voices/speaker/manifest.json',
name: 'Hook - american, male, gaming',
},
{
value: 's3://peregrine-voices/hudson saad parrot/manifest.json',
name: 'Hudson - american, adult, male, neutral, videos, neutral, thick',
},
{
value: 'hunter',
name: 'Hunter - british, old, male, high, narrative, fast, round',
},
{
value: 'ignacio',
name: 'Delilah - american, adult, female, neutral, narrative, slow, smooth',
},
{
value: 's3://peregrine-voices/mel28/manifest.json',
name: 'Jack - american, adult, male,',
},
{
value: 'jarrett',
name: 'Jarrett - american, adult, male, low, advertising, slow, smooth',
},
{
value:
's3://voice-cloning-zero-shot/801a663f-efd0-4254-98d0-5c175514c3e8/jennifer/manifest.json',
name: 'Jennifer - american, adult, female,',
},
{
value: 'jerrell',
name: 'Jerrell - american, adult, male, low, narrative, neutral, round',
},
{
value: 'jordan',
name: 'Jordan - american, adult, male, neutral, training, slow, round',
},
{
value:
's3://voice-cloning-zero-shot/dc23bb38-f568-4323-b6fb-7d64f685b97a/joseph/manifest.json',
name: 'Joseph - american, adult, male,',
},
{
value: 'judson',
name: 'Judson - american, adult, male, low, narrative, slow, smooth',
},
{
value: 'lance',
name: 'Lance - british, adult, male, low, videos, neutral, smooth',
},
{
value: 'larry',
name: 'Larry - american, adult, male, neutral, narrative, neutral, smooth',
},
{
value: 's3://peregrine-voices/larry_ads3_parrot_saad/manifest.json',
name: 'Larry (Advertising) - american, adult, male, neutral, advertising, neutral, smooth',
},
{
value:
's3://mockingbird-prod/larry_vo_narrative_4bd5c1bd-f662-4a38-b5b9-76563f7b92ec/voices/speaker/manifest.json',
name: 'Larry (Narrative) - american, adult, male, neutral, narrative, neutral, smooth',
},
{
value: 'lillian',
name: 'Lillian - british, old, female, neutral, training, slow, round',
},
{
value: 'ahmed',
name: 'Logan - british, old, male, neutral, narrative, neutral, thick',
},
{
value: 'lottie',
name: 'Lottie - british, adult, female, low, narrative, slow, smooth',
},
{
value: 'lucius',
name: 'Lucius - british, adult, male, low, narrative, slow, smooth',
},
{
value: 'mickey',
name: 'Madelyn - british, adult, female, neutral, videos, fast, thick',
},
{
value:
's3://voice-cloning-zero-shot/09b5c0cc-a8f4-4450-aaab-3657b9965d0b/podcaster/manifest.json',
name: 'Matt - american, adult, male,',
},
{
value: 's3://peregrine-voices/mel21/manifest.json',
name: 'Melissa - american, adult, female,',
},
{
value: 'micah',
name: 'Micah - british, adult, female, neutral, narrative, neutral, smooth',
},
{
value:
's3://voice-cloning-zero-shot/7c339a9d-370f-4643-adf5-4134e3ec9886/mlae02/manifest.json',
name: 'Michael - american, adult, male,',
},
{
value: 'mickey',
name: 'Madelyn - british, adult, female, neutral, videos, fast, thick',
},
{
value:
's3://voice-cloning-zero-shot/7c38b588-14e8-42b9-bacd-e03d1d673c3c/nicole/manifest.json',
name: 'Nicole - american, adult, female,',
},
{
value: 's3://peregrine-voices/nolan saad parrot/manifest.json',
name: 'Nolan - british, adult, male, high, videos, neutral, round',
},
{
value: 'nova',
name: 'Nova - american, adult, female, whisper, narrative, slow, smooth',
},
{
value: 'oliver',
name: 'Oliver - british, adult, male, high, videos, neutral, round',
},
{
value: 'oscar',
name: 'Oscar - british, adult, male, neutral, narrative, slow, smooth',
},
{
value: 'owen',
name: 'Owen - american, youth, male, high, narrative, neutral, round',
},
{
value: 'pedro',
name: 'Pedro - american, adult, male, neutral, narrative, slow, round',
},
{
value: 'phoebe',
name: 'Phoebe - british, adult, female, high, videos, fast, smooth',
},
{
value: 'randall',
name: 'Randall - british, adult, male, high, narrative, fast, thick',
},
{
value: 'reynaldo',
name: 'Reynaldo - british, old, male, low, narrative, fast, smooth',
},
{
value: 'rodrick',
name: 'Rodrick - american, adult, male, neutral, narrative, neutral, smooth',
},
{
value: 'gabriel',
name: 'Samantha - american, old, female, neutral, narrative, neutral, thick',
},
{
value: 'samuel',
name: 'Samuel - american, old, male, high, narrative, slow, gravelly',
},
{
value:
// eslint-disable-next-line max-len
's3://mockingbird-prod/agent_47_carmelo_pampillonio_58e796e1-0b87-4f3e-8b36-7def6d65ce66/voices/speaker/manifest.json',
name: 'Sarge - american, male, gaming',
},
{
value:
's3://voice-cloning-zero-shot/1f44b3e7-22ea-4c2e-87d0-b4d9c8f1d47d/sophia/manifest.json',
name: 'Sophia - american, adult, female,',
},
{
value: 'spencer',
name: 'April - british, adult, female, neutral, narrative, slow, smooth',
},
{
value: 'stella',
name: 'Stella - british, old, female, neutral, training, slow, round',
},
{
value: 'susan',
name: 'Susan - american, adult, female, high, videos, neutral, round',
},
{
value:
// eslint-disable-next-line max-len
's3://mockingbird-prod/susan_vo_commercials_0f4fa663-6eba-4582-be1e-2d5bde798f1c/voices/speaker/manifest.json',
name: 'Susan (Advertising) - american, adult, female, high, advertising, neutral, round',
},
{
value:
's3://mockingbird-prod/susan_vo_narrative_73051c90-460b-4e54-adab-9235f45c5e5f/voices/speaker/manifest.json',
name: 'Susan (Narrative) - american, adult, female, high, narrative, neutral, round',
},
{
value:
's3://mockingbird-prod/susan_vo_training_46ffcc60-d630-42f6-acfe-4affd003ae7a/voices/speaker/manifest.json',
name: 'Susan (Training) - american, adult, female, high, training, neutral, round',
},
{
value: 'theodore',
name: 'Theodore - american, old, male, neutral, narrative, neutral, gravelly',
},
{
value: 'victor',
name: 'Ariana - american, youth, female, high, videos, fast, thick',
},
{
value: 'wilbert',
name: 'Wilbert - british, adult, male, neutral, narrative, neutral, round',
},
{
value: 'wilbur',
name: 'Wilbur - american, youth, male, neutral, narrative, neutral, smooth',
},
{
value: 'wilfred',
name: 'Wilfred - american, old, male, low, training, slow, smooth',
},
{
value: 's3://peregrine-voices/mel22/manifest.json',
name: 'Will - american, adult, male,',
},
{
value: 'william',
name: 'William - american, adult, male, neutral, videos, neutral, round',
},
{
value:
// eslint-disable-next-line max-len
's3://mockingbird-prod/william_vo_narrative_0eacdff5-6243-4e26-8b3b-66e03458c1d1/voices/speaker/manifest.json',
name: 'William (Narrative) - american, adult, male, neutral, narrative, neutral, round',
},
{
value:
's3://mockingbird-prod/william_vo_training_1b939b71-14fa-41f0-b1db-7d94f194ad0a/voices/speaker/manifest.json',
name: 'William (Training) - american, adult, male, neutral, training, neutral, round',
},
],
},
{
value: 'en-GB',
name: 'English (GB)',
voices: [
{
value: 's3://peregrine-voices/arthur ads parrot saad/manifest.json',
name: 'Arthur (Advertising) - british, adult, male, neutral, advertising, neutral, smooth',
},
{
value:
// eslint-disable-next-line max-len
's3://mockingbird-prod/arthur_vo_meditatoin_211f702d-b185-4115-b8b4-801f8130a38d/voices/speaker/manifest.json',
name: 'Arthur (Meditation) - british, adult, male, neutral, meditation, neutral, smooth',
},
{
value:
's3://mockingbird-prod/arthur_vo_narrative_a33fd610-73a9-4401-9a78-6b8219c68a9e/voices/speaker/manifest.json',
name: 'Arthur (Narrative) - british, adult, male, neutral, narrative, neutral, smooth',
},
{
value:
's3://mockingbird-prod/arthur_vo_training_9281c8fd-c7f0-4445-a148-466292d3d329/voices/speaker/manifest.json',
name: 'Arthur (Training) - british, adult, male, neutral, training, neutral, smooth',
},
{
value:
's3://mockingbird-prod/eileen_vo_5d7b2bcc-d635-4301-97e8-d97c13768514/voices/speaker/manifest.json',
name: 'Eileen - british, female, narrative',
},
{
value: 'frankie',
name: 'Frankie - british, old, male, neutral, training, neutral, thick',
},
{
value:
's3://voice-cloning-zero-shot/418a94fa-2395-4487-81d8-22daf107781f/george/manifest.json',
name: 'George - british, adult, male,',
},
{
value: 'julian',
name: 'Julian - british, adult, male, neutral, videos, neutral, round',
},
{
value:
's3://voice-cloning-zero-shot/0b5b2e4b-5103-425e-8aa0-510dd35226e2/mark/manifest.json',
name: 'Mark - british, adult, male,',
},
{
value: 's3://peregrine-voices/oliver_ads2_parrot_saad/manifest.json',
name: 'Oliver (Advertising) - british, adult, male, high, advertising, neutral, round',
},
{
value:
's3://peregrine-voices/oliver_narrative2_parrot_saad/manifest.json',
name: 'Oliver (Narrative) - british, adult, male, high, narrative, neutral, round',
},
{
value:
's3://mockingbird-prod/oliver_vo_training_6e3f604a-5605-4542-948d-347b0d7546fc/voices/speaker/manifest.json',
name: 'Oliver (Training) - british, adult, male, high, training, neutral, round',
},
{
value:
's3://voice-cloning-zero-shot/820da3d2-3a3b-42e7-844d-e68db835a206/sarah/manifest.json',
name: 'Sarah - british, adult, female,',
},
],
},
{
value: 'en-AU',
name: 'English (AU)',
voices: [
{
value: 's3://peregrine-voices/barry ads parrot saad/manifest.json',
name: 'Barry (Advertising) - australian, male, advertising',
},
{
value:
's3://peregrine-voices/barry narrative parrot saad/manifest.json',
name: 'Barry (Narrative) - australian, male, narrative',
},
{
value: 'frederick',
name: 'Frederick - australian, adult, male, low, narrative, slow, thick',
},
{
value: 's3://peregrine-voices/russell2_parrot_saad/manifest.json',
name: 'Russell - australian, male,',
},
],
},
{
value: 'en-CA',
name: 'English (CA)',
voices: [
{
value: 's3://peregrine-voices/charlotte ads parrot saad/manifest.json',
name: 'Charlotte (Advertising) - canadian, adult, female, low, advertising, neutral, smooth',
},
{
value:
's3://peregrine-voices/charlotte meditation 2 parrot saad/manifest.json',
name: 'Charlotte (Meditation) - canadian, adult, female, low, meditation, neutral, smooth',
},
{
value:
// eslint-disable-next-line max-len
's3://mockingbird-prod/charlotte_vo_narrative_9290be17-ccea-4700-a7fd-a8fe5c49fb20/voices/speaker/manifest.json',
name: 'Charlotte (Narrative) - canadian, adult, female, low, narrative, neutral, smooth',
},
{
value:
's3://peregrine-voices/charlotte_training_parrot_saad/manifest.json',
name: 'Charlotte (Training) - canadian, adult, female, low, training, neutral, smooth',
},
{
value:
// eslint-disable-next-line max-len
's3://mockingbird-prod/olivia_vo_commercials_6e3c384f-15d6-4fe7-b9a4-0cb1d69daeba/voices/speaker/manifest.json',
name: 'Olivia (Advertising) - canadian, female, advertising',
},
{
value: 's3://peregrine-voices/olivia_ads3_parrot_saad/manifest.json',
name: 'Olivia (Narrative) - canadian, female, narrative',
},
{
value:
's3://mockingbird-prod/olivia_vo_training_4376204f-a411-4e5d-a5c0-ce6cc3908052/voices/speaker/manifest.json',
name: 'Olivia (Training) - canadian, female, training',
},
],
},
{
value: 'en-IE',
name: 'English (IE)',
voices: [
{
value: 'florencio',
name: 'Madison - irish, old, female, neutral, narrative, slow, round',
},
],
},
{
value: 'en-NZ',
name: 'English (NZ)',
voices: [
{
value:
's3://voice-cloning-zero-shot/d9ff78ba-d016-47f6-b0ef-dd630f59414e/female-cs/manifest.json',
name: 'Ruby - australian, adult, female,',
},
],
},
];

View File

@@ -17,11 +17,13 @@ const TtsIbmLanguagesVoices = require('./speech-data/tts-ibm');
const TtsNvidiaLanguagesVoices = require('./speech-data/tts-nvidia');
const TtsElevenlabsLanguagesVoices = require('./speech-data/tts-elevenlabs');
const TtsWhisperLanguagesVoices = require('./speech-data/tts-whisper');
const TtsPlayHtLanguagesVoices = require('./speech-data/tts-playht');
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 TtsModelRimelabs = require('./speech-data/tts-model-rimelabs');
const SttGoogleLanguagesVoices = require('./speech-data/stt-google');
const SttAwsLanguagesVoices = require('./speech-data/stt-aws');
@@ -163,16 +165,26 @@ const testAwsTts = async(logger, getTtsVoices, credentials) => {
}
};
const testAwsStt = async(logger, credentials) => {
const testAwsStt = async(logger, getAwsAuthToken, credentials) => {
try {
const {region, accessKeyId, secretAccessKey} = credentials;
const client = new TranscribeClient({
region,
credentials: {
accessKeyId,
secretAccessKey
}
});
const {region, accessKeyId, secretAccessKey, roleArn} = credentials;
let client = null;
if (accessKeyId && secretAccessKey) {
client = new TranscribeClient({
region,
credentials: {
accessKeyId,
secretAccessKey
}
});
} else if (roleArn) {
client = new TranscribeClient({
region,
credentials: await getAwsAuthToken(null, null, region, roleArn),
});
} else {
client = new TranscribeClient({region});
}
const command = new ListVocabulariesCommand({});
const response = await client.send(command);
return response;
@@ -256,6 +268,29 @@ const testPlayHT = async(logger, synthAudio, credentials) => {
text: 'Hi there and welcome to jambones!'
}
);
// Test if playHT can fetch voices
await fetchLayHTVoices(credentials);
} catch (err) {
logger.info({err}, 'synth Playht returned error');
throw err;
}
};
const testRimelabs = async(logger, synthAudio, credentials) => {
try {
await synthAudio(
{
increment: () => {},
histogram: () => {}
},
{
vendor: 'rimelabs',
credentials,
language: 'en-US',
voice: 'amber',
text: 'Hi there and welcome to jambones!'
}
);
} catch (err) {
logger.info({err}, 'synth Playht returned error');
throw err;
@@ -375,6 +410,7 @@ const getSpeechCredential = (credential, logger) => {
...credential,
accessKeyId: credential.access_key_id,
secretAccessKey: credential.secret_access_key,
roleArn: credential.role_arn,
region: credential.aws_region || 'us-east-1'
};
}
@@ -396,6 +432,7 @@ function decryptCredential(obj, credential, logger, isObscureKey = true) {
else if ('aws' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.access_key_id = o.access_key_id;
obj.role_arn = o.role_arn;
obj.secret_access_key = isObscureKey ? obscureKey(o.secret_access_key) : o.secret_access_key;
obj.aws_region = o.aws_region;
logger.info({obj, o}, 'retrieving aws speech credential');
@@ -456,6 +493,11 @@ function decryptCredential(obj, credential, logger, isObscureKey = true) {
obj.user_id = o.user_id;
obj.voice_engine = o.voice_engine;
obj.options = o.options;
} else if ('rimelabs' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.api_key = isObscureKey ? obscureKey(o.api_key) : o.api_key;
obj.model_id = o.model_id;
obj.options = o.options;
} else if (obj.vendor.startsWith('custom:')) {
const o = JSON.parse(decrypt(credential));
obj.auth_token = isObscureKey ? obscureKey(o.auth_token) : o.auth_token;
@@ -518,6 +560,8 @@ async function getLanguagesAndVoicesForVendor(logger, vendor, credential, getTts
return await getLanguagesVoicesForElevenlabs(credential, getTtsVoices, logger);
case 'playht':
return await getLanguagesVoicesForPlayHT(credential, getTtsVoices, logger);
case 'rimelabs':
return await getLanguagesVoicesForRimelabs(credential, getTtsVoices, logger);
case 'assemblyai':
return await getLanguagesVoicesForAssemblyAI(credential, getTtsVoices, logger);
case 'whisper':
@@ -552,6 +596,7 @@ async function getLanguagesVoicesForAws(credential, getTtsVoices, logger) {
credentials: {
accessKeyId: credential.access_key_id,
secretAccessKey: credential.secret_access_key,
roleArn: credential.role_arn,
region: credential.aws_region || process.env.AWS_REGION
}
});
@@ -679,7 +724,7 @@ const concat = (a) => {
return a ? ` ${a},` : '';
};
async function getLanguagesVoicesForPlayHT(credential) {
const fetchLayHTVoices = async(credential) => {
if (credential) {
const get = bent('https://api.play.ht', 'GET', 'json', {
'AUTHORIZATION' : credential.api_key,
@@ -688,22 +733,41 @@ async function getLanguagesVoicesForPlayHT(credential) {
});
const voices = await get('/api/v2/voices');
let clone_voices = [];
try {
// try if the account has permission to cloned voice
//otherwise ignore this.
clone_voices = await get('/api/v2/cloned-voices');
} catch {}
return [clone_voices, voices];
}
};
async function getLanguagesVoicesForPlayHT(credential) {
if (credential) {
const [cloned_voice, voices] = await fetchLayHTVoices(credential);
const list_voices = [...cloned_voice, ...voices];
const buildVoice = (d) => {
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.slice(0, -1) : name;
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;
return {
value: `${d.id}`,
name
};
};
const ttsVoices = voices.reduce((acc, voice) => {
const ttsVoices = list_voices.reduce((acc, voice) => {
if (!credential.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));
@@ -718,7 +782,28 @@ ${concat(d.loudness)}${concat(d.style)}${concat(d.tempo)}${concat(d.texture)}` ;
}, []);
return tranform(ttsVoices, undefined, TtsModelPlayHT);
}
return tranform(undefined, undefined, TtsModelPlayHT);
return tranform(TtsPlayHtLanguagesVoices, undefined, TtsModelPlayHT);
}
async function getLanguagesVoicesForRimelabs(credential) {
const model_id = credential ? credential.model_id : null;
const get = bent('https://users.rime.ai', 'GET', 'json', {
'Accept': 'application/json'
});
const voices = await get('/data/voices/all.json');
let selectedVoices = model_id ? voices[model_id] : Object.values(voices).reduce((acc, val) => [...acc, ...val], []);
selectedVoices = selectedVoices.map((v) => ({
name: v.charAt(0).toUpperCase() + v.slice(1),
value: v
}));
const ttsVoices = [
{
value: 'en-US',
name: 'English (US)',
voices: selectedVoices
}
];
return tranform(ttsVoices, undefined, TtsModelRimelabs);
}
async function getLanguagesVoicesForAssemblyAI(credential) {
@@ -873,6 +958,7 @@ module.exports = {
testSonioxStt,
testElevenlabs,
testPlayHT,
testRimelabs,
testAssemblyStt,
testDeepgramTTS,
getSpeechCredential,

8
package-lock.json generated
View File

@@ -19,7 +19,7 @@
"@jambonz/lamejs": "^1.2.2",
"@jambonz/mw-registrar": "^0.2.7",
"@jambonz/realtimedb-helpers": "^0.8.8",
"@jambonz/speech-utils": "^0.0.50",
"@jambonz/speech-utils": "^0.1.0",
"@jambonz/time-series": "^0.2.8",
"@jambonz/verb-specifications": "^0.0.69",
"@soniox/soniox-node": "^1.2.2",
@@ -2027,9 +2027,9 @@
}
},
"node_modules/@jambonz/speech-utils": {
"version": "0.0.50",
"resolved": "https://registry.npmjs.org/@jambonz/speech-utils/-/speech-utils-0.0.50.tgz",
"integrity": "sha512-fcMaOuWrBVFh6FKiiurYhnQV71xXmnkyBQmp4OjNd1Zo8Ya+tZMdAJjyHtimjJdgiwJbwDnfdSwKSuz8G9CVkQ==",
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@jambonz/speech-utils/-/speech-utils-0.1.0.tgz",
"integrity": "sha512-45K6Vrl2PMEbbcnvm65afCDujDxck/bEUq7+P6KRw/cei3mrKtwjGh3HXi1cKhC1gA5UF1+5YrUoPO9LdoZnog==",
"dependencies": {
"@aws-sdk/client-polly": "^3.496.0",
"@aws-sdk/client-sts": "^3.496.0",

View File

@@ -29,7 +29,7 @@
"@jambonz/lamejs": "^1.2.2",
"@jambonz/mw-registrar": "^0.2.7",
"@jambonz/realtimedb-helpers": "^0.8.8",
"@jambonz/speech-utils": "^0.0.50",
"@jambonz/speech-utils": "^0.1.0",
"@jambonz/time-series": "^0.2.8",
"@jambonz/verb-specifications": "^0.0.69",
"@soniox/soniox-node": "^1.2.2",

View File

@@ -170,6 +170,20 @@ test('speech credentials tests', async(t) => {
//console.log(JSON.stringify(result));
t.ok(result.statusCode === 200 && result.body.tts.status === 'ok', 'successfully tested speech credential for google tts');
t.ok(result.statusCode === 200 && result.body.stt.status === 'ok', 'successfully tested speech credential for google stt');
result = await request.post(`/Accounts/${account_sid}/TtsCache/Synthesize`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
body: {
speech_credential_sid: ms_sid,
text: "Hello How are you",
language: "en-US",
voice: "en-US-Standard-C"
}
});
t.ok(result.statusCode === 200, 'successfully google tested synthesize');
}
/* add / test a credential for microsoft */
@@ -198,6 +212,20 @@ test('speech credentials tests', async(t) => {
//console.log(JSON.stringify(result));
t.ok(result.statusCode === 200 && result.body.tts.status === 'ok', 'successfully tested speech credential for microsoft tts');
t.ok(result.statusCode === 200 && result.body.stt.status === 'ok', 'successfully tested speech credential for microsoft stt');
result = await request.post(`/Accounts/${account_sid}/TtsCache/Synthesize`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
body: {
speech_credential_sid: ms_sid,
text: "Hello How are you",
language: "en-US",
voice: "en-US-AvaMultilingualNeural"
}
});
t.ok(result.statusCode === 200, 'successfully microsoft tested synthesize');
}
/* add / test a credential for AWS */
@@ -227,6 +255,20 @@ test('speech credentials tests', async(t) => {
//console.log(JSON.stringify(result));
t.ok(result.statusCode === 200 && result.body.tts.status === 'ok', 'successfully tested speech credential for AWS tts');
t.ok(result.statusCode === 200 && result.body.stt.status === 'ok', 'successfully tested speech credential for AWS stt');
result = await request.post(`/Accounts/${account_sid}/TtsCache/Synthesize`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
body: {
speech_credential_sid: ms_sid,
text: "Hello How are you",
language: "en-US",
voice: "Joanna"
}
});
t.ok(result.statusCode === 200, 'successfully AWS tested synthesize');
}
/* add a credential for wellsaid */
@@ -253,6 +295,20 @@ test('speech credentials tests', async(t) => {
//console.log(JSON.stringify(result));
t.ok(result.statusCode === 200 && result.body.tts.status === 'ok', 'successfully tested speech credential for wellsaid');
result = await request.post(`/Accounts/${account_sid}/TtsCache/Synthesize`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
body: {
speech_credential_sid: ms_sid,
text: "Hello How are you",
language: "en-US",
voice: "3"
}
});
t.ok(result.statusCode === 200, 'successfully Wellsaid tested synthesize');
/* delete the credential */
result = await request.delete(`/Accounts/${account_sid}/SpeechCredentials/${ms_sid}`, {
auth: authUser,
@@ -285,6 +341,20 @@ test('speech credentials tests', async(t) => {
//console.log(JSON.stringify(result));
t.ok(result.statusCode === 200 && result.body.stt.status === 'ok', 'successfully tested speech credential for deepgram');
result = await request.post(`/Accounts/${account_sid}/TtsCache/Synthesize`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
body: {
speech_credential_sid: ms_sid,
text: "Hello How are you",
language: "en-US",
voice: "aura-asteria-en"
}
});
t.ok(result.statusCode === 200, 'successfully deepgram tested synthesize');
/* delete the credential */
result = await request.delete(`/Accounts/${account_sid}/SpeechCredentials/${ms_sid}`, {
auth: authUser,
@@ -404,6 +474,20 @@ test('speech credentials tests', async(t) => {
//console.log(JSON.stringify(result));
t.ok(result.statusCode === 200 && result.body.stt.status === 'ok', 'successfully tested speech credential for ibm stt');
result = await request.post(`/Accounts/${account_sid}/TtsCache/Synthesize`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
body: {
speech_credential_sid: ms_sid,
text: "Hello How are you",
language: "en-US",
voice: "en-US_MichaelExpressive"
}
});
t.ok(result.statusCode === 200, 'successfully IBM tested synthesize');
/* delete the credential */
result = await request.delete(`/Accounts/${account_sid}/SpeechCredentials/${ms_sid}`, {
auth: authUser,
@@ -570,6 +654,29 @@ test('speech credentials tests', async(t) => {
});
t.ok(result.statusCode === 204, 'successfully deleted speech credential for playht');
/* add a credential for rimelabs */
result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
body: {
vendor: 'rimelabs',
use_for_stt: false,
use_for_tts: true,
api_key: 'asdasdasdasddsadasda',
model_id: 'mist',
}
});
t.ok(result.statusCode === 201, 'successfully added speech credential for rimelabs');
const rimelabs_sid = result.body.sid;
/* delete the credential */
result = await request.delete(`/Accounts/${account_sid}/SpeechCredentials/${rimelabs_sid}`, {
auth: authUser,
resolveWithFullResponse: true,
});
t.ok(result.statusCode === 204, 'successfully deleted speech credential for rimelabs');
/* add a credential for custom voices google */
result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, {
@@ -636,6 +743,30 @@ test('speech credentials tests', async(t) => {
});
t.ok(result.statusCode === 204, 'successfully deleted speech credential');
/* add a credential for aws polly by roleArn */
result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
body: {
vendor: 'aws',
labe: 'aws_polly_with_arn',
use_for_tts: true,
use_for_stt: false,
role_arn: 'Arn::aws::role',
aws_region: 'us-east-1'
}
});
t.ok(result.statusCode === 201, 'successfully added speech credential for AWS Polly By RoleArn');
const awsPollySid = result.body.sid;
/* delete the credential */
result = await request.delete(`/Accounts/${account_sid}/SpeechCredentials/${awsPollySid}`, {
auth: authUser,
resolveWithFullResponse: true,
});
t.ok(result.statusCode === 204, 'successfully deleted speech credential');
/* Check google supportedLanguagesAndVoices */
result = await request.get(`/Accounts/${account_sid}/SpeechCredentials/speech/supportedLanguagesAndVoices?vendor=google`, {
resolveWithFullResponse: true,