mirror of
https://github.com/jambonz/jambonz-api-server.git
synced 2026-01-25 02:08:24 +00:00
Compare commits
49 Commits
fix/test_p
...
v0.9.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad3ec926ee | ||
|
|
66bd9a442c | ||
|
|
fa81d179a1 | ||
|
|
fab8a391b7 | ||
|
|
89288acf6e | ||
|
|
23cd4408a5 | ||
|
|
ce4618523c | ||
|
|
0eb8097e32 | ||
|
|
8851b3fac0 | ||
|
|
e080118b6a | ||
|
|
75c27e3f80 | ||
|
|
843980c7f6 | ||
|
|
f9990da468 | ||
|
|
e8d5655abb | ||
|
|
e908f5830c | ||
|
|
5c7bac91a8 | ||
|
|
de250c8d58 | ||
|
|
84d83a0a48 | ||
|
|
b5bede7a08 | ||
|
|
6e779f6744 | ||
|
|
77b9ca4cba | ||
|
|
0451b6982c | ||
|
|
71adc577e9 | ||
|
|
e8b32103fe | ||
|
|
57d8d0a02c | ||
|
|
a41760fa9f | ||
|
|
c6bae80a03 | ||
|
|
4cddbd83a1 | ||
|
|
6275aac341 | ||
|
|
52de41c9bc | ||
|
|
ed71abd675 | ||
|
|
2d2b98dab5 | ||
|
|
7553e2b617 | ||
|
|
b921cab867 | ||
|
|
48e1a72ef3 | ||
|
|
4337a55a27 | ||
|
|
6041b1d595 | ||
|
|
d33d0aa519 | ||
|
|
ffe9cb23eb | ||
|
|
dbbc894832 | ||
|
|
82c16380f5 | ||
|
|
c0fab2880b | ||
|
|
ce2fa392a4 | ||
|
|
3b47162d13 | ||
|
|
b765232d4f | ||
|
|
2436bea6ea | ||
|
|
f67abddbd4 | ||
|
|
39fcb17dec | ||
|
|
80418aa7e5 |
11
.github/workflows/ci.yml
vendored
11
.github/workflows/ci.yml
vendored
@@ -7,11 +7,22 @@ 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
|
||||
- run: npm run test:encrypt-decrypt
|
||||
|
||||
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Drachtio Communications Services, LLC
|
||||
Copyright (c) 2018-2024 FirstFive8, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
10
app.js
10
app.js
@@ -46,12 +46,15 @@ const {
|
||||
addKey,
|
||||
retrieveKey,
|
||||
deleteKey,
|
||||
incrKey
|
||||
incrKey,
|
||||
listConferences
|
||||
} = require('./lib/helpers/realtimedb-helpers');
|
||||
const {
|
||||
getTtsVoices,
|
||||
getTtsSize,
|
||||
purgeTtsCache,
|
||||
getAwsAuthToken,
|
||||
getVerbioAccessToken,
|
||||
synthAudio
|
||||
} = require('@jambonz/speech-utils')({}, logger);
|
||||
const {
|
||||
@@ -87,6 +90,7 @@ app.locals = {
|
||||
deleteCall,
|
||||
listCalls,
|
||||
listSortedSets,
|
||||
listConferences,
|
||||
purgeCalls,
|
||||
retrieveSet,
|
||||
addKey,
|
||||
@@ -95,6 +99,8 @@ app.locals = {
|
||||
deleteKey,
|
||||
getTtsVoices,
|
||||
getTtsSize,
|
||||
getAwsAuthToken,
|
||||
getVerbioAccessToken,
|
||||
purgeTtsCache,
|
||||
synthAudio,
|
||||
lookupAppBySid,
|
||||
@@ -216,7 +222,7 @@ server.on('upgrade', (request, socket, head) => {
|
||||
|
||||
/* complete the upgrade */
|
||||
wsServer.handleUpgrade(request, socket, head, (ws) => {
|
||||
logger.info(`upgraded to websocket, url: ${request.url}`);
|
||||
logger.debug(`upgraded to websocket, url: ${request.url}`);
|
||||
wsServer.emit('connection', ws, request.url);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -162,7 +162,7 @@ regex VARCHAR(32) NOT NULL COMMENT 'regex-based pattern match against dialed num
|
||||
description VARCHAR(1024),
|
||||
priority INTEGER NOT NULL COMMENT 'lower priority routes are attempted first',
|
||||
PRIMARY KEY (lcr_route_sid)
|
||||
) COMMENT='An ordered list of digit patterns in an LCR table. The patterns are tested in sequence until one matches';
|
||||
) COMMENT='An ordered list of digit patterns in an LCR table. The pat';
|
||||
|
||||
CREATE TABLE lcr
|
||||
(
|
||||
@@ -173,7 +173,7 @@ default_carrier_set_entry_sid CHAR(36) COMMENT 'default carrier/route to use whe
|
||||
service_provider_sid CHAR(36),
|
||||
account_sid CHAR(36),
|
||||
PRIMARY KEY (lcr_sid)
|
||||
) COMMENT='An LCR (least cost routing) table that is used by a service provider or account to make decisions about routing outbound calls when multiple carriers are available.';
|
||||
) COMMENT='An LCR (least cost routing) table that is used by a service ';
|
||||
|
||||
CREATE TABLE password_settings
|
||||
(
|
||||
@@ -351,6 +351,8 @@ speech_credential_sid CHAR(36) NOT NULL,
|
||||
model VARCHAR(512) NOT NULL,
|
||||
reported_usage ENUM('REPORTED_USAGE_UNSPECIFIED','REALTIME','OFFLINE') DEFAULT 'REALTIME',
|
||||
name VARCHAR(64) NOT NULL,
|
||||
voice_cloning_key MEDIUMTEXT,
|
||||
use_voice_cloning_key BOOLEAN DEFAULT false,
|
||||
PRIMARY KEY (google_custom_voice_sid)
|
||||
);
|
||||
|
||||
@@ -358,7 +360,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
|
||||
@@ -412,6 +416,7 @@ register_from_user VARCHAR(128),
|
||||
register_from_domain VARCHAR(255),
|
||||
register_public_ip_in_contact BOOLEAN NOT NULL DEFAULT false,
|
||||
register_status VARCHAR(4096),
|
||||
dtmf_type ENUM('rfc2833','tones','info') NOT NULL DEFAULT 'rfc2833',
|
||||
PRIMARY KEY (voip_carrier_sid)
|
||||
) COMMENT='A Carrier or customer PBX that can send or receive calls';
|
||||
|
||||
@@ -459,6 +464,7 @@ outbound BOOLEAN NOT NULL COMMENT 'if true, include in least-cost routing when p
|
||||
voip_carrier_sid CHAR(36) NOT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT 1,
|
||||
send_options_ping BOOLEAN NOT NULL DEFAULT 0,
|
||||
use_sips_scheme BOOLEAN NOT NULL DEFAULT 0,
|
||||
pad_crypto BOOLEAN NOT NULL DEFAULT 0,
|
||||
protocol ENUM('udp','tcp','tls', 'tls/srtp') DEFAULT 'udp' COMMENT 'Outbound call protocol',
|
||||
PRIMARY KEY (sip_gateway_sid)
|
||||
@@ -551,6 +557,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
@@ -196,6 +196,19 @@ const sql = {
|
||||
'ALTER TABLE sip_gateways ADD COLUMN send_options_ping BOOLEAN NOT NULL DEFAULT 0',
|
||||
'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',
|
||||
'ALTER TABLE google_custom_voices ADD COLUMN use_voice_cloning_key BOOLEAN DEFAULT false',
|
||||
'ALTER TABLE google_custom_voices ADD COLUMN voice_cloning_key MEDIUMTEXT',
|
||||
],
|
||||
9003: [
|
||||
'ALTER TABLE google_custom_voices ADD COLUMN voice_cloning_key MEDIUMTEXT',
|
||||
'ALTER TABLE google_custom_voices ADD COLUMN use_voice_cloning_key BOOLEAN DEFAULT false',
|
||||
'ALTER TABLE voip_carriers ADD COLUMN dtmf_type ENUM(\'rfc2833\',\'tones\',\'info\') NOT NULL DEFAULT \'rfc2833\'',
|
||||
]
|
||||
};
|
||||
|
||||
@@ -229,6 +242,8 @@ 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']);
|
||||
if (val < 9003) upgrades.push(...sql['9003']);
|
||||
|
||||
// perform all upgrades
|
||||
logger.info({upgrades}, 'applying schema upgrades..');
|
||||
|
||||
@@ -13,6 +13,7 @@ const {
|
||||
deleteKey,
|
||||
incrKey,
|
||||
client: redisClient,
|
||||
listConferences
|
||||
} = require('@jambonz/realtimedb-helpers')({}, logger);
|
||||
|
||||
module.exports = {
|
||||
@@ -27,5 +28,6 @@ module.exports = {
|
||||
retrieveKey,
|
||||
deleteKey,
|
||||
redisClient,
|
||||
incrKey
|
||||
incrKey,
|
||||
listConferences
|
||||
};
|
||||
|
||||
@@ -56,9 +56,13 @@ AND effective_end_date IS NULL
|
||||
AND pending = 0`;
|
||||
|
||||
const extractBucketCredential = (obj) => {
|
||||
const {bucket_credential} = obj;
|
||||
if (bucket_credential) {
|
||||
obj.bucket_credential = JSON.parse(decrypt(bucket_credential));
|
||||
try {
|
||||
const {bucket_credential} = obj;
|
||||
if (bucket_credential) {
|
||||
obj.bucket_credential = JSON.parse(decrypt(bucket_credential));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error while decrypting data', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -37,19 +37,24 @@ class Application extends Model {
|
||||
}
|
||||
|
||||
/**
|
||||
* list all applications - for all service providers, for one service provider, or for one account
|
||||
* list all applications - for all service providers, for one service provider, or for one account,
|
||||
* or by an optional name
|
||||
*/
|
||||
static retrieveAll(service_provider_sid, account_sid) {
|
||||
let sql = retrieveSql;
|
||||
static retrieveAll(service_provider_sid, account_sid, name) {
|
||||
let sql = retrieveSql + ' WHERE 1 = 1';
|
||||
const args = [];
|
||||
if (account_sid) {
|
||||
sql = `${sql} WHERE app.account_sid = ?`;
|
||||
sql = `${sql} AND app.account_sid = ?`;
|
||||
args.push(account_sid);
|
||||
}
|
||||
else if (service_provider_sid) {
|
||||
sql = `${sql} WHERE account_sid in (SELECT account_sid from accounts WHERE service_provider_sid = ?)`;
|
||||
sql = `${sql} AND account_sid in (SELECT account_sid from accounts WHERE service_provider_sid = ?)`;
|
||||
args.push(service_provider_sid);
|
||||
}
|
||||
if (name) {
|
||||
sql = `${sql} AND app.name = ?`;
|
||||
args.push(name);
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
getMysqlConnection((err, conn) => {
|
||||
if (err) return reject(err);
|
||||
|
||||
@@ -13,9 +13,9 @@ class Client extends Model {
|
||||
}
|
||||
|
||||
static async retrieveAllByServiceProviderSid(service_provider_sid) {
|
||||
const sql = `SELECT c.client_sid, c.account_sid, c.is_active, c.username, c.hashed_password
|
||||
const sql = `SELECT c.client_sid, c.account_sid, c.is_active, c.username, c.password
|
||||
FROM ${this.table} AS c LEFT JOIN accounts AS acc ON c.account_sid = acc.account_sid
|
||||
LEFT JOIN service_providers AS sp ON sp.service_provider_sid = accs.service_provider_sid
|
||||
LEFT JOIN service_providers AS sp ON sp.service_provider_sid = acc.service_provider_sid
|
||||
WHERE sp.service_provider_sid = ?`;
|
||||
const [rows] = await promisePool.query(sql, service_provider_sid);
|
||||
return rows;
|
||||
|
||||
@@ -22,13 +22,17 @@ class SpeechCredential extends Model {
|
||||
|
||||
static async getSpeechCredentialsByVendorAndLabel(service_provider_sid, account_sid, vendor, label) {
|
||||
let sql;
|
||||
let rows = [];
|
||||
if (account_sid) {
|
||||
sql = `SELECT * FROM speech_credentials WHERE account_sid = ? AND vendor = ? ${label ? 'AND label = ?' : ''}`;
|
||||
} else {
|
||||
sql = `SELECT * FROM speech_credentials WHERE service_provider_sid = ? AND vendor = ?
|
||||
${label ? 'AND label = ?' : ''}`;
|
||||
sql = `SELECT * FROM speech_credentials WHERE account_sid = ? AND vendor = ?
|
||||
AND label ${label ? '= ?' : 'is NULL'}`;
|
||||
[rows] = await promisePool.query(sql, [account_sid, vendor, label]);
|
||||
}
|
||||
if (rows.length === 0) {
|
||||
sql = `SELECT * FROM speech_credentials WHERE service_provider_sid = ? AND vendor = ?
|
||||
AND label ${label ? '= ?' : 'is NULL'}`;
|
||||
[rows] = await promisePool.query(sql, [service_provider_sid, vendor, label]);
|
||||
}
|
||||
const [rows] = await promisePool.query(sql, [account_sid ? account_sid : service_provider_sid, vendor, label]);
|
||||
return rows;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,10 @@ SystemInformation.fields = [
|
||||
name: 'monitoring_domain_name',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
name: 'private_network_cidr',
|
||||
type: 'string',
|
||||
},
|
||||
];
|
||||
|
||||
module.exports = SystemInformation;
|
||||
|
||||
@@ -136,6 +136,10 @@ VoipCarrier.fields = [
|
||||
{
|
||||
name: 'register_status',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'dtmf_type',
|
||||
type: 'string'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -19,11 +19,12 @@ const {
|
||||
parseCallSid,
|
||||
enableSubspace,
|
||||
disableSubspace,
|
||||
parseVoipCarrierSid
|
||||
parseVoipCarrierSid,
|
||||
hasValue,
|
||||
} = require('./utils');
|
||||
const short = require('short-uuid');
|
||||
const VoipCarrier = require('../../models/voip-carrier');
|
||||
const { encrypt } = require('../../utils/encrypt-decrypt');
|
||||
const { encrypt, obscureBucketCredentialsSensitiveData, isObscureKey } = require('../../utils/encrypt-decrypt');
|
||||
const { testS3Storage, testGoogleStorage, testAzureStorage } = require('../../utils/storage-utils');
|
||||
const translator = short();
|
||||
|
||||
@@ -42,7 +43,7 @@ const getFsUrl = async(logger, retrieveSet, setName) => {
|
||||
return ;
|
||||
}
|
||||
const f = fs[idx++ % fs.length];
|
||||
logger.info({fs}, `feature servers available for createCall API request, selecting ${f}`);
|
||||
logger.debug({fs}, `feature servers available for createCall API request, selecting ${f}`);
|
||||
return `${f}/v1/createCall`;
|
||||
} catch (err) {
|
||||
logger.error({err}, 'getFsUrl: error retreving feature servers from redis');
|
||||
@@ -265,7 +266,9 @@ function validateUpdateCall(opts) {
|
||||
'sip_request',
|
||||
'record',
|
||||
'tag',
|
||||
'dtmf'
|
||||
'dtmf',
|
||||
'conferenceParticipantAction',
|
||||
'dub'
|
||||
]
|
||||
.reduce((acc, prop) => (opts[prop] ? ++acc : acc), 0);
|
||||
|
||||
@@ -316,6 +319,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.conferenceParticipantAction.tag) {
|
||||
throw new DbErrorBadRequest('conferenceParticipantAction requires tag property when action is \'tag\'');
|
||||
}
|
||||
if ('coach' == opts.conferenceParticipantAction.action && !opts.conferenceParticipantAction.tag) {
|
||||
throw new DbErrorBadRequest('conferenceParticipantAction requires tag property when action is \'coach\'');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validateTo(to) {
|
||||
@@ -540,9 +556,12 @@ router.get('/:sid', async(req, res) => {
|
||||
const account_sid = parseAccountSid(req);
|
||||
await validateRequest(req, account_sid);
|
||||
const service_provider_sid = req.user.hasServiceProviderAuth ? req.user.service_provider_sid : null;
|
||||
const results = await Account.retrieve(account_sid, service_provider_sid);
|
||||
if (results.length === 0) return res.status(404).end();
|
||||
return res.status(200).json(results[0]);
|
||||
const [result] = await Account.retrieve(account_sid, service_provider_sid) || [];
|
||||
if (!result) return res.status(404).end();
|
||||
|
||||
result.bucket_credential = obscureBucketCredentialsSensitiveData(result.bucket_credential);
|
||||
|
||||
return res.status(200).json(result);
|
||||
}
|
||||
catch (err) {
|
||||
sysError(logger, res, err);
|
||||
@@ -624,19 +643,21 @@ router.delete('/:sid/SubspaceTeleport', async(req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
function encryptBucketCredential(obj) {
|
||||
if (!obj.bucket_credential) return;
|
||||
function encryptBucketCredential(obj, storedCredentials = {}) {
|
||||
if (!hasValue(obj?.bucket_credential)) return;
|
||||
const {
|
||||
vendor,
|
||||
region,
|
||||
name,
|
||||
access_key_id,
|
||||
secret_access_key,
|
||||
tags,
|
||||
service_key,
|
||||
connection_string,
|
||||
endpoint
|
||||
} = obj.bucket_credential;
|
||||
let {
|
||||
secret_access_key,
|
||||
service_key,
|
||||
connection_string
|
||||
} = obj.bucket_credential;
|
||||
|
||||
switch (vendor) {
|
||||
case 'aws_s3':
|
||||
@@ -644,6 +665,9 @@ function encryptBucketCredential(obj) {
|
||||
assert(secret_access_key, 'invalid aws S3 bucket credential: secret_access_key is required');
|
||||
assert(name, 'invalid aws bucket name: name is required');
|
||||
assert(region, 'invalid aws bucket region: region is required');
|
||||
if (isObscureKey(obj.bucket_credential) && hasValue(storedCredentials)) {
|
||||
secret_access_key = storedCredentials.secret_access_key;
|
||||
}
|
||||
const awsData = JSON.stringify({vendor, region, name, access_key_id,
|
||||
secret_access_key, tags});
|
||||
obj.bucket_credential = encrypt(awsData);
|
||||
@@ -653,18 +677,27 @@ function encryptBucketCredential(obj) {
|
||||
assert(secret_access_key, 'invalid aws S3 bucket credential: secret_access_key is required');
|
||||
assert(name, 'invalid aws bucket name: name is required');
|
||||
assert(endpoint, 'invalid endpoint uri: endpoint is required');
|
||||
if (isObscureKey(obj.bucket_credential) && hasValue(storedCredentials)) {
|
||||
secret_access_key = storedCredentials.secret_access_key;
|
||||
}
|
||||
const s3Data = JSON.stringify({vendor, endpoint, name, access_key_id,
|
||||
secret_access_key, tags});
|
||||
obj.bucket_credential = encrypt(s3Data);
|
||||
break;
|
||||
case 'google':
|
||||
assert(service_key, 'invalid google cloud storage credential: service_key is required');
|
||||
if (isObscureKey(obj.bucket_credential) && hasValue(storedCredentials)) {
|
||||
service_key = storedCredentials.service_key;
|
||||
}
|
||||
const googleData = JSON.stringify({vendor, name, service_key, tags});
|
||||
obj.bucket_credential = encrypt(googleData);
|
||||
break;
|
||||
case 'azure':
|
||||
assert(name, 'invalid azure container name: name is required');
|
||||
assert(connection_string, 'invalid azure cloud storage credential: connection_string is required');
|
||||
if (isObscureKey(obj.bucket_credential) && hasValue(storedCredentials)) {
|
||||
connection_string = storedCredentials.connection_string;
|
||||
}
|
||||
const azureData = JSON.stringify({vendor, name, connection_string, tags});
|
||||
obj.bucket_credential = encrypt(azureData);
|
||||
break;
|
||||
@@ -722,7 +755,17 @@ router.put('/:sid', async(req, res) => {
|
||||
delete obj.registration_hook;
|
||||
delete obj.queue_event_hook;
|
||||
|
||||
encryptBucketCredential(obj);
|
||||
let storedBucketCredentials = {};
|
||||
if (isObscureKey(obj?.bucket_credential)) {
|
||||
const [account] = await Account.retrieve(sid) || [];
|
||||
/* to avoid overwriting valid credentials with the obscured secret,
|
||||
* that the frontend might send, we pass the stored account bucket credentials
|
||||
* in the case it is a obscured key, we replace it with the stored one
|
||||
*/
|
||||
storedBucketCredentials = account.bucket_credential;
|
||||
}
|
||||
|
||||
encryptBucketCredential(obj, storedBucketCredentials);
|
||||
|
||||
const rowsAffected = await Account.update(sid, obj);
|
||||
if (rowsAffected === 0) {
|
||||
@@ -822,6 +865,13 @@ router.post('/:sid/BucketCredentialTest', async(req, res) => {
|
||||
try {
|
||||
const account_sid = parseAccountSid(req);
|
||||
await validateRequest(req, account_sid);
|
||||
/* if the req.body bucket credentials contain an obscured key, replace with stored account.bucket_credential */
|
||||
if (isObscureKey(req.body)) {
|
||||
const service_provider_sid = req.user.hasServiceProviderAuth ? req.user.service_provider_sid : null;
|
||||
const [account] = await Account.retrieve(account_sid, service_provider_sid) || [];
|
||||
if (!account) return res.status(404).end();
|
||||
req.body = account.bucket_credential;
|
||||
}
|
||||
const {vendor, name, region, access_key_id, secret_access_key, service_key, connection_string, endpoint} = req.body;
|
||||
const ret = {
|
||||
status: 'not tested'
|
||||
@@ -1089,5 +1139,21 @@ router.get('/:sid/Queues', async(req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* retrieve info for a list of conferences under an account
|
||||
*/
|
||||
router.get('/:sid/Conferences', async(req, res) => {
|
||||
const {logger, listConferences} = req.app.locals;
|
||||
try {
|
||||
const accountSid = parseAccountSid(req);
|
||||
await validateRequest(req, accountSid);
|
||||
const conferences = await listConferences(accountSid);
|
||||
logger.debug(`retrieved ${conferences.length} queues for account sid ${accountSid}`);
|
||||
res.status(200).json(conferences.map((c) => c.split(':').pop()));
|
||||
updateLastUsed(logger, accountSid, req).catch((err) => {});
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -156,7 +156,8 @@ router.get('/', async(req, res) => {
|
||||
try {
|
||||
const service_provider_sid = req.user.hasServiceProviderAuth ? req.user.service_provider_sid : null;
|
||||
const account_sid = req.user.hasAccountAuth ? req.user.account_sid : null;
|
||||
const results = await Application.retrieveAll(service_provider_sid, account_sid);
|
||||
const name = req.query.name;
|
||||
const results = await Application.retrieveAll(service_provider_sid, account_sid, name);
|
||||
res.status(200).json(results);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
|
||||
@@ -4,6 +4,9 @@ const SpeechCredential = require('../../models/speech-credential');
|
||||
const decorate = require('./decorate');
|
||||
const {DbErrorBadRequest, DbErrorForbidden} = require('../../utils/errors');
|
||||
const sysError = require('../error');
|
||||
const multer = require('multer');
|
||||
const upload = multer({ dest: '/tmp/csv/' });
|
||||
const fs = require('fs');
|
||||
|
||||
const validateCredentialPermission = async(req) => {
|
||||
const credential = await SpeechCredential.retrieve(req.body.speech_credential_sid);
|
||||
@@ -41,6 +44,33 @@ const preconditions = {
|
||||
|
||||
decorate(router, GoogleCustomVoice, ['add', 'retrieve', 'update', 'delete'], preconditions);
|
||||
|
||||
const voiceCloningKeySubString = (voice_cloning_key) => {
|
||||
return voice_cloning_key ? voice_cloning_key.substring(0, 100) + '...' : undefined;
|
||||
};
|
||||
|
||||
router.get('/: sid', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
const {sid} = req.params;
|
||||
const account_sid = req.user.account_sid;
|
||||
const service_provider_sid = req.user.service_provider_sid;
|
||||
|
||||
const google_voice = await GoogleCustomVoice.retrieve(sid);
|
||||
google_voice.voice_cloning_key = voiceCloningKeySubString(google_voice.voice_cloning_key);
|
||||
if (!google_voice) {
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
if (req.user.hasScope('service_provider') && google_voice.service_provider_sid !== service_provider_sid ||
|
||||
req.user.hasScope('account') && google_voice.account_sid !== account_sid) {
|
||||
throw new DbErrorForbidden('Insufficient privileges');
|
||||
}
|
||||
|
||||
return res.status(200).json(google_voice);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
const account_sid = req.user.account_sid || req.query.account_sid;
|
||||
@@ -67,7 +97,37 @@ router.get('/', async(req, res) => {
|
||||
}
|
||||
results = await GoogleCustomVoice.retrieveAllByLabel(service_provider_sid, account_sid, label);
|
||||
}
|
||||
res.status(200).json(results);
|
||||
res.status(200).json(results.map((r) => {
|
||||
r.voice_cloning_key = voiceCloningKeySubString(r.voice_cloning_key);
|
||||
return r;
|
||||
}));
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/:sid/VoiceCloningKey', upload.single('file'), async(req, res) => {
|
||||
const {logger} = req.app.locals;
|
||||
const {sid} = req.params;
|
||||
const account_sid = req.user.account_sid;
|
||||
const service_provider_sid = req.user.service_provider_sid;
|
||||
try {
|
||||
const google_voice = await GoogleCustomVoice.retrieve(sid);
|
||||
if (!google_voice) {
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
if (req.user.hasScope('service_provider') && google_voice.service_provider_sid !== service_provider_sid ||
|
||||
req.user.hasScope('account') && google_voice.account_sid !== account_sid) {
|
||||
throw new DbErrorForbidden('Insufficient privileges');
|
||||
}
|
||||
|
||||
const voice_cloning_key = Buffer.from(fs.readFileSync(req.file.path)).toString();
|
||||
await GoogleCustomVoice.update(sid, {
|
||||
voice_cloning_key
|
||||
});
|
||||
fs.unlinkSync(req.file.path);
|
||||
return res.sendStatus(204);
|
||||
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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})`;
|
||||
|
||||
@@ -7,7 +7,12 @@ 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,
|
||||
testVerbioTts,
|
||||
testVerbioStt,
|
||||
testSpeechmaticsStt,
|
||||
testCartesia} = require('../../utils/speech-utils');
|
||||
const {DbErrorUnprocessableRequest, DbErrorForbidden, DbErrorBadRequest} = require('../../utils/errors');
|
||||
const {
|
||||
testGoogleTts,
|
||||
@@ -112,13 +117,17 @@ const encryptCredential = (obj) => {
|
||||
secret_access_key,
|
||||
aws_region,
|
||||
api_key,
|
||||
role_arn,
|
||||
region,
|
||||
client_id,
|
||||
client_secret,
|
||||
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,
|
||||
@@ -133,11 +142,13 @@ const encryptCredential = (obj) => {
|
||||
instance_id,
|
||||
custom_stt_url,
|
||||
custom_tts_url,
|
||||
custom_tts_streaming_url,
|
||||
auth_token = '',
|
||||
cobalt_server_uri,
|
||||
model_id,
|
||||
user_id,
|
||||
voice_engine,
|
||||
engine_version,
|
||||
options
|
||||
} = obj;
|
||||
|
||||
@@ -154,10 +165,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':
|
||||
@@ -191,10 +209,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':
|
||||
@@ -222,6 +240,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');
|
||||
@@ -229,6 +253,18 @@ const encryptCredential = (obj) => {
|
||||
const playhtData = JSON.stringify({api_key, user_id, voice_engine, options});
|
||||
return encrypt(playhtData);
|
||||
|
||||
case 'cartesia':
|
||||
assert(api_key, 'invalid cartesia speech credential: api_key is required');
|
||||
assert(model_id, 'invalid cartesia speech credential: model_id is required');
|
||||
const cartesiaData = JSON.stringify({api_key, model_id, options});
|
||||
return encrypt(cartesiaData);
|
||||
|
||||
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});
|
||||
@@ -240,9 +276,16 @@ const encryptCredential = (obj) => {
|
||||
const whisperData = JSON.stringify({api_key, model_id});
|
||||
return encrypt(whisperData);
|
||||
|
||||
case 'verbio':
|
||||
assert(engine_version, 'invalid verbio speech credential: client_id is required');
|
||||
assert(client_id, 'invalid verbio speech credential: client_id is required');
|
||||
assert(client_secret, 'invalid verbio speech credential: secret is required');
|
||||
const verbioData = JSON.stringify({client_id, client_secret, engine_version});
|
||||
return encrypt(verbioData);
|
||||
|
||||
default:
|
||||
if (vendor.startsWith('custom:')) {
|
||||
const customData = JSON.stringify({auth_token, custom_stt_url, custom_tts_url});
|
||||
const customData = JSON.stringify({auth_token, custom_stt_url, custom_tts_url, custom_tts_streaming_url});
|
||||
return encrypt(customData);
|
||||
}
|
||||
else assert(false, `invalid or missing vendor: ${vendor}`);
|
||||
@@ -426,12 +469,15 @@ router.put('/:sid', async(req, res) => {
|
||||
custom_stt_endpoint_url,
|
||||
custom_stt_url,
|
||||
custom_tts_url,
|
||||
custom_tts_streaming_url,
|
||||
cobalt_server_uri,
|
||||
model_id,
|
||||
voice_engine,
|
||||
options,
|
||||
deepgram_stt_uri,
|
||||
deepgram_stt_use_tls,
|
||||
deepgram_tts_uri,
|
||||
engine_version
|
||||
} = req.body;
|
||||
|
||||
const newCred = {
|
||||
@@ -452,12 +498,15 @@ router.put('/:sid', async(req, res) => {
|
||||
nuance_tts_uri,
|
||||
custom_stt_url,
|
||||
custom_tts_url,
|
||||
custom_tts_streaming_url,
|
||||
cobalt_server_uri,
|
||||
model_id,
|
||||
voice_engine,
|
||||
options,
|
||||
deepgram_stt_uri,
|
||||
deepgram_stt_use_tls,
|
||||
deepgram_tts_uri,
|
||||
engine_version
|
||||
};
|
||||
logger.info({o, newCred}, 'updating speech credential with this new credential');
|
||||
obj.credential = encryptCredential(newCred);
|
||||
@@ -486,7 +535,7 @@ router.put('/:sid', async(req, res) => {
|
||||
* Test a credential
|
||||
*/
|
||||
router.get('/:sid/test', async(req, res) => {
|
||||
const {logger, synthAudio} = req.app.locals;
|
||||
const {logger, synthAudio, getVerbioAccessToken} = req.app.locals;
|
||||
try {
|
||||
const sid = parseSpeechCredentialSid(req);
|
||||
const creds = await SpeechCredential.retrieve(sid);
|
||||
@@ -534,12 +583,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 +601,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';
|
||||
@@ -596,7 +647,7 @@ router.get('/:sid/test', async(req, res) => {
|
||||
}
|
||||
if (cred.use_for_stt) {
|
||||
try {
|
||||
await testMicrosoftStt(logger, {api_key, region});
|
||||
await testMicrosoftStt(logger, {api_key, region, use_custom_stt, custom_stt_endpoint_url});
|
||||
results.stt.status = 'ok';
|
||||
SpeechCredential.sttTestResult(sid, true);
|
||||
} catch (err) {
|
||||
@@ -655,8 +706,7 @@ router.get('/:sid/test', async(req, res) => {
|
||||
SpeechCredential.sttTestResult(sid, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (cred.vendor === 'deepgram') {
|
||||
} else if (cred.vendor === 'deepgram') {
|
||||
const {api_key} = credential;
|
||||
if (cred.use_for_tts) {
|
||||
try {
|
||||
@@ -736,12 +786,56 @@ 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 {
|
||||
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 === 'cartesia') {
|
||||
if (cred.use_for_tts) {
|
||||
try {
|
||||
await testCartesia(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);
|
||||
@@ -770,6 +864,27 @@ router.get('/:sid/test', async(req, res) => {
|
||||
SpeechCredential.ttsTestResult(sid, false);
|
||||
}
|
||||
}
|
||||
} else if (cred.vendor === 'verbio') {
|
||||
if (cred.use_for_tts) {
|
||||
try {
|
||||
await testVerbioTts(logger, synthAudio, credential);
|
||||
results.tts.status = 'ok';
|
||||
SpeechCredential.ttsTestResult(sid, true);
|
||||
} catch (err) {
|
||||
results.tts = {status: 'fail', reason: err.message};
|
||||
SpeechCredential.ttsTestResult(sid, false);
|
||||
}
|
||||
}
|
||||
if (cred.use_for_stt) {
|
||||
try {
|
||||
await testVerbioStt(logger, getVerbioAccessToken, credential);
|
||||
results.stt.status = 'ok';
|
||||
SpeechCredential.sttTestResult(sid, true);
|
||||
} catch (err) {
|
||||
results.stt = {status: 'fail', reason: err.message};
|
||||
SpeechCredential.sttTestResult(sid, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.status(200).json(results);
|
||||
@@ -786,7 +901,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');
|
||||
}
|
||||
@@ -794,7 +909,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;
|
||||
|
||||
@@ -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';
|
||||
@@ -110,10 +114,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`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -440,6 +440,22 @@ const validatePasswordSettings = async(password) => {
|
||||
return;
|
||||
};
|
||||
|
||||
function hasValue(data) {
|
||||
if (typeof data === 'string') {
|
||||
return data && data.length > 0;
|
||||
} else if (Array.isArray(data)) {
|
||||
return data && data.length > 0;
|
||||
} else if (typeof data === 'object') {
|
||||
return data && Object.keys(data).length > 0;
|
||||
} else if (typeof data === 'number') {
|
||||
return data !== null;
|
||||
} else if (typeof data === 'boolean') {
|
||||
return data !== null;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setupFreeTrial,
|
||||
createTestCdrs,
|
||||
@@ -460,5 +476,6 @@ module.exports = {
|
||||
checkLimits,
|
||||
enableSubspace,
|
||||
disableSubspace,
|
||||
validatePasswordSettings
|
||||
validatePasswordSettings,
|
||||
hasValue,
|
||||
};
|
||||
|
||||
@@ -3882,6 +3882,34 @@ paths:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
|
||||
|
||||
/Accounts/{AccountSid}/Conferences:
|
||||
get:
|
||||
tags:
|
||||
- Conferences
|
||||
summary: list conferences
|
||||
operationId: listConferences
|
||||
parameters:
|
||||
- name: AccountSid
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
200:
|
||||
description: list of conferences for a specified account
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
500:
|
||||
description: system error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
|
||||
/Accounts/{AccountSid}/Calls:
|
||||
post:
|
||||
tags:
|
||||
@@ -4152,6 +4180,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
|
||||
|
||||
@@ -17,10 +17,15 @@ const encrypt = (text) => {
|
||||
};
|
||||
|
||||
const decrypt = (data) => {
|
||||
const hash = JSON.parse(data);
|
||||
const decipher = crypto.createDecipheriv(algorithm, secretKey, Buffer.from(hash.iv, 'hex'));
|
||||
const decrpyted = Buffer.concat([decipher.update(Buffer.from(hash.content, 'hex')), decipher.final()]);
|
||||
return decrpyted.toString();
|
||||
try {
|
||||
const hash = JSON.parse(data);
|
||||
const decipher = crypto.createDecipheriv(algorithm, secretKey, Buffer.from(hash.iv, 'hex'));
|
||||
const decrpyted = Buffer.concat([decipher.update(Buffer.from(hash.content, 'hex')), decipher.final()]);
|
||||
return decrpyted.toString();
|
||||
} catch (error) {
|
||||
console.error('Error while decrypting data', error);
|
||||
return '{}';
|
||||
}
|
||||
};
|
||||
|
||||
const obscureKey = (key, key_spoiler_length = 6) => {
|
||||
@@ -33,8 +38,85 @@ const obscureKey = (key, key_spoiler_length = 6) => {
|
||||
return `${key.slice(0, key_spoiler_length)}${key_spoiler_char.repeat(key.length - key_spoiler_length)}`;
|
||||
};
|
||||
|
||||
function isObscureKey(bucketCredentials) {
|
||||
if (!bucketCredentials) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const {
|
||||
vendor,
|
||||
secret_access_key = '',
|
||||
service_key = '',
|
||||
connection_string = ''
|
||||
} = bucketCredentials || {};
|
||||
let pattern;
|
||||
switch (vendor) {
|
||||
case 'aws_s3':
|
||||
case 's3_compatible':
|
||||
pattern = /^([A-Za-z0-9]{4,6}X+$)/;
|
||||
return pattern.test(secret_access_key);
|
||||
case 'azure':
|
||||
pattern = /^https:[A-Za-z0-9\/.:?=&_-]+$/;
|
||||
return pattern.test(connection_string);
|
||||
|
||||
case 'google': {
|
||||
pattern = /^([A-Za-z0-9]{4,6}X+$)/;
|
||||
let {private_key} = JSON.parse(service_key);
|
||||
const key_header = '-----BEGIN PRIVATE KEY-----\n';
|
||||
private_key = private_key.slice(key_header.length, private_key.length);
|
||||
return pattern.test(private_key || '');
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.log('Error in isObscureKey', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* obscure sensitive data in bucket credentials
|
||||
* an obscured key contains of 6 'spoiled' characters of the key followed by 'X' characters
|
||||
* '123456XXXXXXXXXXXXXXXXXXXXXXXX'
|
||||
* @param {*} obj
|
||||
* @returns
|
||||
*/
|
||||
function obscureBucketCredentialsSensitiveData(obj) {
|
||||
if (!obj) return obj;
|
||||
const {vendor, service_key, connection_string, secret_access_key} = obj;
|
||||
switch (vendor) {
|
||||
case 'aws_s3':
|
||||
case 's3_compatible':
|
||||
obj.secret_access_key = obscureKey(secret_access_key);
|
||||
break;
|
||||
case 'google':
|
||||
const o = JSON.parse(service_key);
|
||||
let private_key = o.private_key;
|
||||
if (!isObscureKey(obj)) {
|
||||
const key_header = '-----BEGIN PRIVATE KEY-----\n';
|
||||
private_key = o.private_key.slice(key_header.length, o.private_key.length);
|
||||
private_key = `${key_header}${obscureKey(private_key)}`;
|
||||
}
|
||||
const obscured = {
|
||||
...o,
|
||||
private_key
|
||||
};
|
||||
obj.service_key = JSON.stringify(obscured);
|
||||
break;
|
||||
case 'azure':
|
||||
obj.connection_string = obscureKey(connection_string);
|
||||
break;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
encrypt,
|
||||
decrypt,
|
||||
obscureKey
|
||||
obscureKey,
|
||||
isObscureKey,
|
||||
obscureBucketCredentialsSensitiveData,
|
||||
};
|
||||
|
||||
218
lib/utils/speech-data/stt-speechmatics.js
Normal file
218
lib/utils/speech-data/stt-speechmatics.js
Normal 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',
|
||||
},
|
||||
];
|
||||
14
lib/utils/speech-data/stt-verbio.js
Normal file
14
lib/utils/speech-data/stt-verbio.js
Normal file
@@ -0,0 +1,14 @@
|
||||
module.exports = [
|
||||
{ name: 'US English', value: 'en-US' },
|
||||
{ name: 'British English', value: 'en-GB' },
|
||||
{ name: 'LATAM Spanish', value: 'en-USes-419' },
|
||||
{ name: 'Spanish', value: 'es' },
|
||||
{ name: 'Catalan', value: 'ca-ES', version: 'v2' },
|
||||
{ name: 'Brazilian Portuguese', value: 'pt-BR' },
|
||||
{ name: 'French', value: 'fr', version: 'v1' },
|
||||
{ name: 'Canadian French', value: 'fr-CA', version: 'v1' },
|
||||
{ name: 'German', value: 'de', version: 'v1' },
|
||||
{ name: 'Italian', value: 'it', version: 'v1' },
|
||||
{ name: 'Turkish', value: 'tr', version: 'v1' },
|
||||
{ name: 'Japanese', value: 'ja', version: 'v1' },
|
||||
];
|
||||
301
lib/utils/speech-data/tts-cartesia.js
Normal file
301
lib/utils/speech-data/tts-cartesia.js
Normal file
@@ -0,0 +1,301 @@
|
||||
/* eslint-disable max-len */
|
||||
module.exports = [
|
||||
{
|
||||
value: 'en',
|
||||
name: 'English',
|
||||
voices: [
|
||||
{
|
||||
value: '79f8b5fb-2cc8-479a-80df-29f7a7cf1a3e',
|
||||
name: 'Nonfiction Man - This voice is smooth, confident, and resonant, perfect for narrating educational content',
|
||||
},
|
||||
{
|
||||
value: 'e00d0e4c-a5c8-443f-a8a3-473eb9a62355',
|
||||
name: 'Friendly Sidekick - This voice is friendly and supportive, designed for voicing characters in games and videos',
|
||||
},
|
||||
{
|
||||
value: '3b554273-4299-48b9-9aaf-eefd438e3941',
|
||||
name: 'Indian Lady - This voice is young, rich, and curious, perfect for a narrator or fictional character',
|
||||
},
|
||||
{
|
||||
value: '71a7ad14-091c-4e8e-a314-022ece01c121',
|
||||
name: 'British Reading Lady - This is a calm and elegant voice with a British accent, perfect for storytelling and narration',
|
||||
},
|
||||
{
|
||||
value: '4d2fd738-3b3d-4368-957a-bb4805275bd9',
|
||||
name: 'British Narration Lady - This is a neutral voice with a British accent, perfect for narrations ',
|
||||
},
|
||||
{
|
||||
value: '15a9cd88-84b0-4a8b-95f2-5d583b54c72e',
|
||||
name: 'Reading Lady - This voice is monotone and deliberate, perfect for a slower-paced and more serious reading voice',
|
||||
},
|
||||
{
|
||||
value: 'd46abd1d-2d02-43e8-819f-51fb652c1c61',
|
||||
name: 'Newsman - This voice is neutral and educational, perfect for a news anchor',
|
||||
},
|
||||
{
|
||||
value: '2ee87190-8f84-4925-97da-e52547f9462c',
|
||||
name: 'Child - This voice is young and full, perfect for a child',
|
||||
},
|
||||
{
|
||||
value: 'cd17ff2d-5ea4-4695-be8f-42193949b946',
|
||||
name: 'Meditation Lady - This voice is calm, soothing, and relaxing, perfect for meditation',
|
||||
},
|
||||
{
|
||||
value: '5345cf08-6f37-424d-a5d9-8ae1101b9377',
|
||||
name: 'Maria - This voice is laid back, natural, and conversational, like you\'re catching up with a good friend',
|
||||
},
|
||||
{
|
||||
value: '41534e16-2966-4c6b-9670-111411def906',
|
||||
name: '1920\'s Radioman - This voice is energetic and confident, great for an entertainer or radio host',
|
||||
},
|
||||
{
|
||||
value: 'bf991597-6c13-47e4-8411-91ec2de5c466',
|
||||
name: 'Newslady - This voice is authoritative and educational, perfect for a news anchor',
|
||||
},
|
||||
{
|
||||
value: '00a77add-48d5-4ef6-8157-71e5437b282d',
|
||||
name: 'Calm Lady - This voice is calm and nurturing, perfect for a narrator',
|
||||
},
|
||||
{
|
||||
value: '156fb8d2-335b-4950-9cb3-a2d33befec77',
|
||||
name: 'Helpful Woman - This voice is friendly and conversational, designed for customer support agents and casual conversations',
|
||||
},
|
||||
{
|
||||
value: '36b42fcb-60c5-4bec-b077-cb1a00a92ec6',
|
||||
name: 'Pilot over Intercom - This voice sounds like a British Pilot character speaking over an Intercom',
|
||||
},
|
||||
{
|
||||
value: 'f146dcec-e481-45be-8ad2-96e1e40e7f32',
|
||||
name: 'Reading Man - Male with calm narrational voice.',
|
||||
},
|
||||
{
|
||||
value: '34575e71-908f-4ab6-ab54-b08c95d6597d',
|
||||
name: 'New York Man - This voice is compelling and husky, with a New York accent, perfect for sales pitches and motivational content',
|
||||
},
|
||||
{
|
||||
value: 'a0e99841-438c-4a64-b679-ae501e7d6091',
|
||||
name: 'Barbershop Man - This voice is smooth and relaxing, perfect for a casual conversation',
|
||||
},
|
||||
{
|
||||
value: '638efaaa-4d0c-442e-b701-3fae16aad012',
|
||||
name: 'Indian Man - This voice is smooth with an Indian accent, perfect for a narrator',
|
||||
},
|
||||
{
|
||||
value: '41f3c367-e0a8-4a85-89e0-c27bae9c9b6d',
|
||||
name: 'Australian Customer Support Man - This voice is warm with an Australian accent, perfect for customer support agents',
|
||||
},
|
||||
{
|
||||
value: '421b3369-f63f-4b03-8980-37a44df1d4e8',
|
||||
name: 'Friendly Australian Man - This voice is rich and deep, with an Australian accent, perfect for casual conversations with a friend',
|
||||
},
|
||||
{
|
||||
value: 'b043dea0-a007-4bbe-a708-769dc0d0c569',
|
||||
name: 'Wise Man - This is a deep and deliberate voice, suited for educational content and conversations',
|
||||
},
|
||||
{
|
||||
value: '69267136-1bdc-412f-ad78-0caad210fb40',
|
||||
name: 'Friendly Reading Man - This voice is energetic and friendly, like having your friend read his favorite book to you',
|
||||
},
|
||||
{
|
||||
value: 'a167e0f3-df7e-4d52-a9c3-f949145efdab',
|
||||
name: 'Customer Support Man - This voice is clear and calm, perfect for a call center',
|
||||
},
|
||||
{
|
||||
value: '4f8651b0-bbbd-46ac-8b37-5168c5923303',
|
||||
name: 'Kentucky Woman - This voice is energetic and upbeat, with a slight Kentucky accent, perfect for speeches and rallies',
|
||||
},
|
||||
{
|
||||
value: 'daf747c6-6bc2-4083-bd59-aa94dce23f5d',
|
||||
name: 'Middle Eastern Woman - This voice is clear with a Middle Eastern Accent, perfect for a narrator',
|
||||
},
|
||||
{
|
||||
value: '694f9389-aac1-45b6-b726-9d9369183238',
|
||||
name: 'Sarah - This voice is natural and expressive with an American accent, perfect for a wide range of conversational use cases including customer support, sales, reception, and more.',
|
||||
},
|
||||
{
|
||||
value: '794f9389-aac1-45b6-b726-9d9369183238',
|
||||
name: 'Sarah Curious - This voice is similar to Sarah, but has improved emphasis for questions.',
|
||||
},
|
||||
{
|
||||
value: '21b81c14-f85b-436d-aff5-43f2e788ecf8',
|
||||
name: 'Laidback Woman - This voice is laid back and husky, with a slight Californian accent',
|
||||
},
|
||||
{
|
||||
value: 'a3520a8f-226a-428d-9fcd-b0a4711a6829',
|
||||
name: 'Reflective Woman - This voice is even, full, and reflective, perfect for a young narrator for an audiobook or movie',
|
||||
},
|
||||
{
|
||||
value: '829ccd10-f8b3-43cd-b8a0-4aeaa81f3b30',
|
||||
name: 'Customer Support Lady - This voice is polite and helpful, perfect for customer support agents',
|
||||
},
|
||||
{
|
||||
value: '79a125e8-cd45-4c13-8a67-188112f4dd22',
|
||||
name: 'British Lady - This voice is elegant with a slight British accent, perfect for storytelling and narrating',
|
||||
},
|
||||
{
|
||||
value: 'c8605446-247c-4d39-acd4-8f4c28aa363c',
|
||||
name: 'Wise Lady - This voice is wise and authoritative, perfect for a confident narrator',
|
||||
},
|
||||
{
|
||||
value: '8985388c-1332-4ce7-8d55-789628aa3df4',
|
||||
name: 'Australian Narrator Lady - This voice is even and neutral, with an Australian accent, designed for narrating content and stories',
|
||||
},
|
||||
{
|
||||
value: 'ff1bb1a9-c582-4570-9670-5f46169d0fc8',
|
||||
name: 'Indian Customer Support Lady - This voice is clear and polite, with an Indian accent, suitable for customer support agents',
|
||||
},
|
||||
{
|
||||
value: '820a3788-2b37-4d21-847a-b65d8a68c99a',
|
||||
name: 'Salesman - This voice is smooth and persuasive, perfect for sales pitches and phone conversations',
|
||||
},
|
||||
{
|
||||
value: 'f114a467-c40a-4db8-964d-aaba89cd08fa',
|
||||
name: 'Yogaman - This voice is calm, soothing, and stable, perfect for a yoga instructor',
|
||||
},
|
||||
{
|
||||
value: 'c45bc5ec-dc68-4feb-8829-6e6b2748095d',
|
||||
name: 'Movieman - This voice is deep, resonant, and assertive, perfect for a movie narrator',
|
||||
},
|
||||
{
|
||||
value: '87748186-23bb-4158-a1eb-332911b0b708',
|
||||
name: 'Wizardman - This voice is wise and mysterious, perfect for a Wizard character',
|
||||
},
|
||||
{
|
||||
value: '043cfc81-d69f-4bee-ae1e-7862cb358650',
|
||||
name: 'Australian Woman - This voice is deliberate and confident, with a slight Australian accent, perfect for inspiring characters in videos and stories',
|
||||
},
|
||||
{
|
||||
value: '5619d38c-cf51-4d8e-9575-48f61a280413',
|
||||
name: 'Announcer Man - This voice is deep and inviting, perfect for entertainment and broadcasting content',
|
||||
},
|
||||
{
|
||||
value: '42b39f37-515f-4eee-8546-73e841679c1d',
|
||||
name: 'Wise Guide Man - This voice is deep and deliberate, perfect for inspiring and guiding characters in games and videos',
|
||||
},
|
||||
{
|
||||
value: '565510e8-6b45-45de-8758-13588fbaec73',
|
||||
name: 'Midwestern Man - This voice is neutral and smooth, with a slight midwestern accent, perfect for narrations',
|
||||
},
|
||||
{
|
||||
value: '726d5ae5-055f-4c3d-8355-d9677de68937',
|
||||
name: 'Kentucky Man - This voice is laidback and smooth, with a Kentucky accent, perfect for a casual conversation',
|
||||
},
|
||||
{
|
||||
value: '63ff761f-c1e8-414b-b969-d1833d1c870c',
|
||||
name: 'Confident British Man - This voice is disciplined with a British accent, perfect for a commanding character or narrator',
|
||||
},
|
||||
{
|
||||
value: '98a34ef2-2140-4c28-9c71-663dc4dd7022',
|
||||
name: 'Southern Man - This voice is warm with a Southern accent, perfect for a narrator',
|
||||
},
|
||||
{
|
||||
value: '95856005-0332-41b0-935f-352e296aa0df',
|
||||
name: 'Classy British Man - This voice is light and smooth with a British accent, perfect for casual conversation',
|
||||
},
|
||||
{
|
||||
value: 'ee7ea9f8-c0c1-498c-9279-764d6b56d189',
|
||||
name: 'Polite Man - This voice is polite and conversational, with a slight accent, designed for customer support and casual conversations',
|
||||
},
|
||||
{
|
||||
value: '40104aff-a015-4da1-9912-af950fbec99e',
|
||||
name: 'Alabama Male - This voice has a strong Southern Accent, perfect for conversations and instructional videos',
|
||||
},
|
||||
{
|
||||
value: '13524ffb-a918-499a-ae97-c98c7c4408c4',
|
||||
name: 'Australian Male - This voice is smooth and disciplined, with an Australian Accent, suited for narrating educational content',
|
||||
},
|
||||
{
|
||||
value: '1001d611-b1a8-46bd-a5ca-551b23505334',
|
||||
name: 'Anime Girl - This voice is expressive and has a high pitch, suitable for anime or gaming characters',
|
||||
},
|
||||
{
|
||||
value: 'e3827ec5-697a-4b7c-9704-1a23041bbc51',
|
||||
name: 'Sweet Lady - This voice is sweet and passionate, perfect for a character in a game or book',
|
||||
},
|
||||
{
|
||||
value: 'c2ac25f9-ecc4-4f56-9095-651354df60c0',
|
||||
name: 'Commercial Lady - This voice is inviting, enthusiastic, and relatable, perfect for a commercial or advertisement',
|
||||
},
|
||||
{
|
||||
value: '573e3144-a684-4e72-ac2b-9b2063a50b53',
|
||||
name: 'Teacher Lady - This voice is neutral and clear, perfect for narrating educational content',
|
||||
},
|
||||
{
|
||||
value: '8f091740-3df1-4795-8bd9-dc62d88e5131',
|
||||
name: 'Princess - This voice is light, freindly and has a flourish, perfect for character work in videos and games',
|
||||
},
|
||||
{
|
||||
value: '7360f116-6306-4e9a-b487-1235f35a0f21',
|
||||
name: 'Commercial Man - This voice is upbeat and enthusiastic, perfect for commercials and advertisements',
|
||||
},
|
||||
{
|
||||
value: '03496517-369a-4db1-8236-3d3ae459ddf7',
|
||||
name: 'ASMR Lady - This voice is calming and soft, perfect for guided meditations and soothing content',
|
||||
},
|
||||
{
|
||||
value: '248be419-c632-4f23-adf1-5324ed7dbf1d',
|
||||
name: 'Professional Woman - This voice is neutral and calm, perfect for a call center',
|
||||
},
|
||||
{
|
||||
value: 'bd9120b6-7761-47a6-a446-77ca49132781',
|
||||
name: 'Tutorial Man - This voice is inviting and calming, perfect for tutorials',
|
||||
},
|
||||
{
|
||||
value: '34bde396-9fde-4ebf-ad03-e3a1d1155205',
|
||||
name: 'New York Woman - This voice commands authority, with a New York accent, perfect for a commanding narrator or character',
|
||||
},
|
||||
{
|
||||
value: '11af83e2-23eb-452f-956e-7fee218ccb5c',
|
||||
name: 'Midwestern Woman - This voice is neutral and deliberate, with a midwestern accent, suitable for news broadcasts and narration',
|
||||
},
|
||||
{
|
||||
value: 'ed81fd13-2016-4a49-8fe3-c0d2761695fc',
|
||||
name: 'Sportsman - This voice is energetic and enthusiastic, perfect for a sports broadcaster',
|
||||
},
|
||||
{
|
||||
value: '996a8b96-4804-46f0-8e05-3fd4ef1a87cd',
|
||||
name: 'Storyteller Lady - This voice is neutral and smooth, with a slight Canadian accent, perfect for narrations',
|
||||
},
|
||||
{
|
||||
value: 'fb26447f-308b-471e-8b00-8e9f04284eb5',
|
||||
name: 'Doctor Mischief - This is an expressive character voice, suited to whimsical characters for games and educational content',
|
||||
},
|
||||
{
|
||||
value: '50d6beb4-80ea-4802-8387-6c948fe84208',
|
||||
name: 'The Merchant - This voice is playful and quirky, designed for character work in games and videos',
|
||||
},
|
||||
{
|
||||
value: 'e13cae5c-ec59-4f71-b0a6-266df3c9bb8e',
|
||||
name: 'Madame Mischief - This voice is mischeivious and playful, suitable for voicing characters for kids content and games',
|
||||
},
|
||||
{
|
||||
value: '5c42302c-194b-4d0c-ba1a-8cb485c84ab9',
|
||||
name: 'Female Nurse - This voice is clear and firm, perfect for nurse characters and instructional videos',
|
||||
},
|
||||
{
|
||||
value: 'f9836c6e-a0bd-460e-9d3c-f7299fa60f94',
|
||||
name: 'Southern Woman - This voice is friendly and inviting, with a slight Southern Accent, perfect for conversations and phone calls',
|
||||
},
|
||||
{
|
||||
value: 'a01c369f-6d2d-4185-bc20-b32c225eab70',
|
||||
name: 'British Customer Support Lady - This voice is friendly and polite, with a British accent, perfect for phone conversations',
|
||||
},
|
||||
{
|
||||
value: 'b7d50908-b17c-442d-ad8d-810c63997ed9',
|
||||
name: 'California Girl - This voice is enthusiastic and friendly, perfect for a casual conversation between friends',
|
||||
},
|
||||
{
|
||||
value: 'f785af04-229c-4a7c-b71b-f3194c7f08bb',
|
||||
name: 'John - This voice is natural and empathetic with an American accent, perfect for use cases like demos and customer support calls.',
|
||||
},
|
||||
{
|
||||
value: '729651dc-c6c3-4ee5-97fa-350da1f88600',
|
||||
name: 'Pleasant Man - A pleasant male voice that\'s good for use cases like demos and customer support calls',
|
||||
},
|
||||
{
|
||||
value: '91b4cf29-5166-44eb-8054-30d40ecc8081',
|
||||
name: 'Anna - This voice is natural and expressive with an American accent, perfect for use cases like interviews and customer support calls.',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
28
lib/utils/speech-data/tts-deepgram.js
Normal file
28
lib/utils/speech-data/tts-deepgram.js
Normal file
@@ -0,0 +1,28 @@
|
||||
const TtsDeepgramLanguagesVoiceRaw = require('./tts-model-deepgram');
|
||||
|
||||
const languagesVoices = [];
|
||||
|
||||
TtsDeepgramLanguagesVoiceRaw.forEach((data) => {
|
||||
const lang = languagesVoices.find((l) => {
|
||||
return l.value === data.locale;
|
||||
});
|
||||
|
||||
if (!lang) {
|
||||
languagesVoices.push({
|
||||
value: data.locale,
|
||||
name: data.localeName,
|
||||
voices: TtsDeepgramLanguagesVoiceRaw
|
||||
.filter((d) => {
|
||||
return d.locale === data.locale;
|
||||
})
|
||||
.map((d) => {
|
||||
return {
|
||||
value: d.value,
|
||||
name: `${d.name}`,
|
||||
};
|
||||
}),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = languagesVoices;
|
||||
152
lib/utils/speech-data/tts-languages-playht.js
Normal file
152
lib/utils/speech-data/tts-languages-playht.js
Normal 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'
|
||||
}
|
||||
];
|
||||
25
lib/utils/speech-data/tts-model-cartesia.js
Normal file
25
lib/utils/speech-data/tts-model-cartesia.js
Normal file
@@ -0,0 +1,25 @@
|
||||
module.exports = [
|
||||
{
|
||||
name: 'Sonic',
|
||||
value: 'sonic',
|
||||
languages: ['en', 'fr', 'de', 'es', 'pt', 'zh', 'ja', 'hi', 'it', 'ko', 'nl', 'pl', 'ru', 'sv', 'tr']
|
||||
},
|
||||
{ name: 'Sonic Preview', value: 'sonic-preview', languages: ['en'] },
|
||||
{
|
||||
name: 'Sonic 2024-12-12',
|
||||
value: 'sonic-2024-12-12',
|
||||
languages: ['en', 'fr', 'de', 'es', 'pt', 'zh', 'ja', 'hi', 'it', 'ko', 'nl', 'pl', 'ru', 'sv', 'tr']
|
||||
},
|
||||
{
|
||||
name: 'Sonic 2024-10-19',
|
||||
value: 'sonic-2024-10-19',
|
||||
languages: ['en', 'fr', 'de', 'es', 'pt', 'zh', 'ja', 'hi', 'it', 'ko', 'nl', 'pl', 'ru', 'sv', 'tr']
|
||||
},
|
||||
{ name: 'Sonic English', value: 'sonic-english', languages: ['en'] },
|
||||
{
|
||||
name: 'Sonic Multilingual',
|
||||
value: 'sonic-multilingual',
|
||||
languages: ['en', 'fr', 'de', 'es', 'pt', 'zh', 'ja', 'hi', 'it', 'ko', 'nl', 'pl', 'ru', 'sv', 'tr']
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,14 +1,68 @@
|
||||
module.exports = [
|
||||
{ name: 'Asteria English (US) Female', value: 'aura-asteria-en' },
|
||||
{ name: 'Luna English (US) Female', value: 'aura-luna-en' },
|
||||
{ name: 'Stella English (US) Female', value: 'aura-stella-en' },
|
||||
{ name: 'Stella English (UK) Female', value: 'aura-athena-en' },
|
||||
{ name: 'Hera English (US) Female', value: 'aura-hera-en' },
|
||||
{ name: 'Orion English (US) Male', value: 'aura-orion-en' },
|
||||
{ name: 'Arcas English (US) Male', value: 'aura-arcas-en' },
|
||||
{ name: 'Perseus English (US) Male', value: 'aura-perseus-en' },
|
||||
{ name: 'Angus English (Ireland) Male', value: 'aura-angus-en' },
|
||||
{ name: 'Orpheus English (US) Male', value: 'aura-orpheus-en' },
|
||||
{ name: 'Helios English (UK) Male', value: 'aura-helios-en' },
|
||||
{ name: 'Zeus English (US) Male', value: 'aura-zeus-en' },
|
||||
{
|
||||
locale: 'en-US',
|
||||
localeName: 'English (US)',
|
||||
name: 'Asteria English (US) Female',
|
||||
value: 'aura-asteria-en'
|
||||
},
|
||||
{
|
||||
locale: 'en-US',
|
||||
localeName: 'English (US)',
|
||||
name: 'Luna English (US) Female',
|
||||
value: 'aura-luna-en'
|
||||
},
|
||||
{
|
||||
locale: 'en-US',
|
||||
localeName: 'English (US)',
|
||||
name: 'Stella English (US) Female',
|
||||
value: 'aura-stella-en'
|
||||
},
|
||||
{
|
||||
locale: 'en-GB',
|
||||
localeName: 'English (UK)',
|
||||
name: 'Stella English (UK) Female',
|
||||
value: 'aura-athena-en'
|
||||
},
|
||||
{
|
||||
locale: 'en-US',
|
||||
localeName: 'English (US)',
|
||||
name: 'Hera English (US) Female',
|
||||
value: 'aura-hera-en'
|
||||
},
|
||||
{
|
||||
locale: 'en-US',
|
||||
localeName: 'English (US)',
|
||||
name: 'Orion English (US) Male',
|
||||
value: 'aura-orion-en'
|
||||
},
|
||||
{
|
||||
locale: 'en-US',
|
||||
localeName: 'English (US)',
|
||||
name: 'Arcas English (US) Male',
|
||||
value: 'aura-arcas-en'
|
||||
},
|
||||
{
|
||||
locale: 'en-US',
|
||||
localeName: 'English (US)',
|
||||
name: 'Perseus English (US) Male',
|
||||
value: 'aura-perseus-en'
|
||||
},
|
||||
{
|
||||
locale: 'en-IE',
|
||||
localeName: 'English (Ireland)',
|
||||
name: 'Angus English (Ireland) Male',
|
||||
value: 'aura-angus-en'
|
||||
},
|
||||
{
|
||||
locale: 'en-US',
|
||||
localeName: 'English (US)',
|
||||
name: 'Orpheus English (US) Male',
|
||||
value: 'aura-orpheus-en'
|
||||
},
|
||||
{
|
||||
locale: 'en-US',
|
||||
localeName: 'English (US)',
|
||||
name: 'Zeus English (US) Male',
|
||||
value: 'aura-zeus-en'
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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' },
|
||||
|
||||
5
lib/utils/speech-data/tts-model-rimelabs.js
Normal file
5
lib/utils/speech-data/tts-model-rimelabs.js
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = [
|
||||
{ name: 'Mist', value: 'mist' },
|
||||
{ name: 'V1', value: 'v1' },
|
||||
];
|
||||
|
||||
710
lib/utils/speech-data/tts-playht.js
Normal file
710
lib/utils/speech-data/tts-playht.js
Normal 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,',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
46
lib/utils/speech-data/tts-verbio.js
Normal file
46
lib/utils/speech-data/tts-verbio.js
Normal file
@@ -0,0 +1,46 @@
|
||||
module.exports = [
|
||||
{
|
||||
value: 'es-ES',
|
||||
name: 'Castilian Spanish',
|
||||
voices: [
|
||||
{
|
||||
value: 'david_es_es',
|
||||
name: 'David-Male',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'es-PE',
|
||||
name: 'Peruvian Spanish',
|
||||
voices: [
|
||||
{
|
||||
value: 'miguel_es_pe',
|
||||
name: 'Miguel-Male',
|
||||
},
|
||||
{
|
||||
value: 'luz_es_pe',
|
||||
name: 'Luz-Female',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'pt-BR',
|
||||
name: 'Brazilian Portuguese',
|
||||
voices: [
|
||||
{
|
||||
value: 'bel_pt_br',
|
||||
name: 'Bel-Female',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'ca-ES',
|
||||
name: 'Catalan',
|
||||
voices: [
|
||||
{
|
||||
value: 'anna_ca',
|
||||
name: 'Anna-Female',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -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');
|
||||
@@ -17,11 +18,17 @@ 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 TtsVerbioLanguagesVoices = require('./speech-data/tts-verbio');
|
||||
|
||||
const TtsModelDeepgram = require('./speech-data/tts-model-deepgram');
|
||||
const TtsLanguagesDeepgram = require('./speech-data/tts-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 TtsModelCartesia = require('./speech-data/tts-model-cartesia');
|
||||
|
||||
const SttGoogleLanguagesVoices = require('./speech-data/stt-google');
|
||||
const SttAwsLanguagesVoices = require('./speech-data/stt-aws');
|
||||
@@ -32,7 +39,12 @@ 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 ttsCartesia = require('./speech-data/tts-cartesia');
|
||||
const ttsModelCartesia = require('./speech-data/tts-model-cartesia');
|
||||
|
||||
|
||||
const testSonioxStt = async(logger, credentials) => {
|
||||
const api_key = credentials;
|
||||
@@ -50,6 +62,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;
|
||||
@@ -88,8 +155,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 = {
|
||||
@@ -115,9 +182,10 @@ const testDeepgramStt = async(logger, credentials) => {
|
||||
};
|
||||
|
||||
const testMicrosoftStt = async(logger, credentials) => {
|
||||
const {api_key, region} = credentials;
|
||||
|
||||
const speechConfig = sdk.SpeechConfig.fromSubscription(api_key, region);
|
||||
const {api_key, region, use_custom_stt, custom_stt_endpoint_url} = credentials;
|
||||
const speechConfig = use_custom_stt ? sdk.SpeechConfig.fromEndpoint(
|
||||
new URL(custom_stt_endpoint_url), api_key) :
|
||||
sdk.SpeechConfig.fromSubscription(api_key, region);
|
||||
const audioConfig = sdk.AudioConfig.fromWavFileInput(fs.readFileSync(`${__dirname}/../../data/test_audio.wav`));
|
||||
speechConfig.speechRecognitionLanguage = 'en-US';
|
||||
|
||||
@@ -163,21 +231,34 @@ 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({
|
||||
region,
|
||||
roleArn
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
client = new TranscribeClient({region});
|
||||
}
|
||||
const command = new ListVocabulariesCommand({});
|
||||
const response = await client.send(command);
|
||||
return response;
|
||||
} catch (err) {
|
||||
logger.info({err}, 'testMicrosoftTts - failed to list voices for region ${region}');
|
||||
logger.info({err}, 'testAwsStt - failed to list voices for region ${region}');
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
@@ -190,7 +271,8 @@ const testMicrosoftTts = async(logger, synthAudio, credentials) => {
|
||||
credentials,
|
||||
language: 'en-US',
|
||||
voice: 'en-US-JennyMultilingualNeural',
|
||||
text: 'Hi there and welcome to jambones!'
|
||||
text: 'Hi there and welcome to jambones!',
|
||||
renderForCaching: true
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
@@ -251,9 +333,34 @@ 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
|
||||
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!',
|
||||
renderForCaching: true
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
@@ -270,7 +377,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) {
|
||||
@@ -286,7 +394,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) {
|
||||
@@ -329,6 +438,44 @@ const testWellSaidStt = async(logger, credentials) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
const testVerbioTts = async(logger, synthAudio, credentials) => {
|
||||
try {
|
||||
await synthAudio(
|
||||
{
|
||||
increment: () => {},
|
||||
histogram: () => {}
|
||||
},
|
||||
{
|
||||
vendor: 'verbio',
|
||||
credentials,
|
||||
language: 'en-US',
|
||||
voice: 'tommy_en-us',
|
||||
text: 'Hi there and welcome to jambones!',
|
||||
renderForCaching: true
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
logger.info({err}, 'synth Verbio returned error');
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
const testVerbioStt = async(logger, getVerbioAccessToken, credentials) => {
|
||||
const token = await getVerbioAccessToken(credentials);
|
||||
try {
|
||||
const post = bent('https://us.rest.speechcenter.verbio.com', 'POST', 'json', {
|
||||
'Authorization': `Bearer ${token.access_token}`,
|
||||
'User-Agent': 'jambonz',
|
||||
'Content-Type': 'audio/wav'
|
||||
});
|
||||
const json = await post('/api/v1/recognize?language=en-US&version=V1',
|
||||
fs.readFileSync(`${__dirname}/../../data/test_audio.wav`));
|
||||
logger.debug({json}, 'successfully speech to text from verbio');
|
||||
} catch (err) {
|
||||
logger.info({err}, 'testWellSaidTts returned error');
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const testAssemblyStt = async(logger, credentials) => {
|
||||
const {api_key} = credentials;
|
||||
|
||||
@@ -375,6 +522,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 +544,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');
|
||||
@@ -428,6 +577,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));
|
||||
@@ -445,6 +595,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;
|
||||
@@ -456,11 +610,22 @@ 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 ('cartesia' === 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 ('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;
|
||||
obj.custom_stt_url = o.custom_stt_url;
|
||||
obj.custom_tts_url = o.custom_tts_url;
|
||||
obj.custom_tts_streaming_url = o.custom_tts_streaming_url;
|
||||
} else if ('assemblyai' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = isObscureKey ? obscureKey(o.api_key) : o.api_key;
|
||||
@@ -468,6 +633,11 @@ function decryptCredential(obj, credential, logger, isObscureKey = true) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = isObscureKey ? obscureKey(o.api_key) : o.api_key;
|
||||
obj.model_id = o.model_id;
|
||||
} else if ('verbio' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.client_id = o.client_id;
|
||||
obj.client_secret = isObscureKey ? obscureKey(o.client_secret) : o.client_secret;
|
||||
obj.engine_version = o.engine_version;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -518,10 +688,18 @@ 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':
|
||||
return await getLanguagesVoicesForWhisper(credential, getTtsVoices, logger);
|
||||
case 'verbio':
|
||||
return await getLanguagesVoicesForVerbio(credential, getTtsVoices, logger);
|
||||
case 'speechmatics':
|
||||
return await getLanguagesVoicesForSpeechmatics(credential, getTtsVoices, logger);
|
||||
case 'cartesia':
|
||||
return await getLanguagesVoicesForCartesia(credential, getTtsVoices, logger);
|
||||
default:
|
||||
logger.info(`invalid vendor ${vendor}, return empty result`);
|
||||
throw new Error(`Invalid vendor ${vendor}`);
|
||||
@@ -552,6 +730,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
|
||||
}
|
||||
});
|
||||
@@ -602,7 +781,7 @@ async function getLanguagesVoicesForNuane(credential, getTtsVoices, logger) {
|
||||
}
|
||||
|
||||
async function getLanguagesVoicesForDeepgram(credential) {
|
||||
return tranform(undefined, SttDeepgramLanguagesVoices, TtsModelDeepgram);
|
||||
return tranform(TtsLanguagesDeepgram, SttDeepgramLanguagesVoices, TtsModelDeepgram);
|
||||
}
|
||||
|
||||
async function getLanguagesVoicesForIbm(credential, getTtsVoices, logger) {
|
||||
@@ -633,6 +812,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', {
|
||||
@@ -650,27 +833,35 @@ async function getLanguagesVoicesForElevenlabs(credential) {
|
||||
}).sort((a, b) => a.name.localeCompare(b.name)) : [];
|
||||
|
||||
if (languages && languages.length > 0) {
|
||||
// using if condition to avoid \n character in name
|
||||
const voices = voiceResp ? voiceResp.voices.map((v) => {
|
||||
let name = `${v.name}${v.category !== 'premade' ? ` (${v.category})` : ''} -
|
||||
${v.labels.accent ? ` ${v.labels.accent},` : ''}
|
||||
${v.labels.description ? ` ${v.labels.description},` : ''}
|
||||
${v.labels.age ? ` ${v.labels.age},` : ''}
|
||||
${v.labels.gender ? ` ${v.labels.gender},` : ''}
|
||||
${v.labels['use case'] ? ` ${v.labels['use case']},` : ''}
|
||||
`;
|
||||
let name = `${v.name}${v.category !== 'premade' ? ` (${v.category.trim()})` : ''} - (`;
|
||||
if (v.labels.accent) name += `${v.labels.accent}, `;
|
||||
if (v.labels.description) name += `${v.labels.description}, `;
|
||||
if (v.labels.age) name += `${v.labels.age}, `;
|
||||
if (v.labels.gender) name += `${v.labels.gender}, `;
|
||||
if (v.labels['use case']) name += `${v.labels['use case']}, `;
|
||||
|
||||
const lastIndex = name.lastIndexOf(',');
|
||||
if (lastIndex !== -1) {
|
||||
name = name.substring(0, lastIndex);
|
||||
}
|
||||
name += ')';
|
||||
return {
|
||||
value: v.voice_id,
|
||||
name
|
||||
};
|
||||
}).sort((a, b) => a.name.localeCompare(b.name)) : [];
|
||||
languages[0].voices = voices;
|
||||
for (const language of languages) {
|
||||
language.voices = voices;
|
||||
}
|
||||
}
|
||||
return tranform(languages, undefined, TtsModelElevenLabs);
|
||||
} else {
|
||||
const voices = TtsElevenlabsLanguagesVoices[0].voices;
|
||||
for (const language of TtsElevenlabsLanguagesVoices) {
|
||||
language.voices = voices;
|
||||
}
|
||||
return tranform(TtsElevenlabsLanguagesVoices, undefined, TtsModelElevenLabs);
|
||||
}
|
||||
}
|
||||
@@ -679,7 +870,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,
|
||||
@@ -687,44 +878,103 @@ async function getLanguagesVoicesForPlayHT(credential) {
|
||||
'Accept': 'application/json'
|
||||
});
|
||||
|
||||
const [cloned_voice, voices] = await Promise.all([ get('/api/v2/cloned-voices'), get('/api/v2/voices')]);
|
||||
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 {voice_engine} = 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)}` +
|
||||
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' : '';
|
||||
name += ')';
|
||||
name = name.replaceAll('( ', '(');
|
||||
|
||||
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(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) {
|
||||
@@ -735,6 +985,26 @@ async function getLanguagesVoicesForWhisper(credential) {
|
||||
return tranform(TtsWhisperLanguagesVoices, undefined, TtsModelWhisper);
|
||||
}
|
||||
|
||||
async function getLanguagesVoicesForVerbio(credentials, getTtsVoices, logger) {
|
||||
const stt = SttVerbioLanguagesVoices.reduce((acc, v) => {
|
||||
if (!v.version || (credentials && credentials.engine_version === v.version)) {
|
||||
acc.push(v);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
try {
|
||||
if (credentials) {
|
||||
const data = await getTtsVoices({vendor: 'verbio', credentials});
|
||||
const voices = parseVerbioLanguagesVoices(data);
|
||||
return tranform(voices, stt, undefined);
|
||||
}
|
||||
return tranform(TtsVerbioLanguagesVoices, stt, undefined);
|
||||
} catch (err) {
|
||||
logger.info({err}, 'there is error while fetching verbio speech voices');
|
||||
return tranform(TtsVerbioLanguagesVoices, stt, undefined);
|
||||
}
|
||||
}
|
||||
|
||||
function tranform(tts, stt, models) {
|
||||
return {
|
||||
...(tts && {tts}),
|
||||
@@ -798,7 +1068,7 @@ function parseAwsLanguagesVoices(data) {
|
||||
if (existingLanguage) {
|
||||
existingLanguage.voices.push({
|
||||
value: voice.Id,
|
||||
name: `(${voice.Gender}) ${voice.Name}`
|
||||
name: `${voice.Name} (${voice.Gender})`
|
||||
});
|
||||
} else {
|
||||
acc.push({
|
||||
@@ -806,7 +1076,7 @@ function parseAwsLanguagesVoices(data) {
|
||||
name: voice.LanguageName,
|
||||
voices: [{
|
||||
value: voice.Id,
|
||||
name: `(${voice.Gender}) ${voice.Name}`
|
||||
name: `${voice.Name} (${voice.Gender})`
|
||||
}]
|
||||
});
|
||||
}
|
||||
@@ -862,6 +1132,118 @@ function parseMicrosoftLanguagesVoices(data) {
|
||||
}, []);
|
||||
}
|
||||
|
||||
function parseVerbioLanguagesVoices(data) {
|
||||
return data.voices.reduce((acc, voice) => {
|
||||
const languageCode = voice.language;
|
||||
const existingLanguage = acc.find((lang) => lang.value === languageCode);
|
||||
if (existingLanguage) {
|
||||
existingLanguage.voices.push({
|
||||
value: voice.voice_id,
|
||||
name: voice.name,
|
||||
});
|
||||
} else {
|
||||
acc.push({
|
||||
value: voice.language,
|
||||
name: voice.language,
|
||||
voices: [{
|
||||
value: voice.voice_id,
|
||||
name: voice.name,
|
||||
}]
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
const fetchCartesiaVoices = async(credential) => {
|
||||
if (credential) {
|
||||
const get = bent('https://api.cartesia.ai', 'GET', 'json', {
|
||||
'X-API-Key' : credential.api_key,
|
||||
'Cartesia-Version': '2024-06-10',
|
||||
'Accept': 'application/json'
|
||||
});
|
||||
|
||||
const voices = await get('/voices');
|
||||
return voices;
|
||||
}
|
||||
};
|
||||
|
||||
const testCartesia = async(logger, synthAudio, credentials) => {
|
||||
try {
|
||||
await synthAudio(
|
||||
{
|
||||
increment: () => {},
|
||||
histogram: () => {}
|
||||
},
|
||||
{
|
||||
vendor: 'cartesia',
|
||||
credentials,
|
||||
language: 'en',
|
||||
voice: '694f9389-aac1-45b6-b726-9d9369183238',
|
||||
text: 'Hi there and welcome to jambones!',
|
||||
renderForCaching: true
|
||||
}
|
||||
);
|
||||
// Test if Cartesia can fetch voices
|
||||
await fetchCartesiaVoices(credentials);
|
||||
} catch (err) {
|
||||
logger.info({err}, 'synth cartesia returned error');
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
async function getLanguagesVoicesForCartesia(credential) {
|
||||
if (credential) {
|
||||
const {model_id} = credential;
|
||||
const {languages} = ttsModelCartesia.find((m) => m.value === model_id);
|
||||
const voices = await fetchCartesiaVoices(credential);
|
||||
|
||||
const buildVoice = (d) => (
|
||||
{
|
||||
value: `${d.id}`,
|
||||
name: `${d.name} - ${d.description}`
|
||||
});
|
||||
const languageMap = {
|
||||
en: 'English',
|
||||
fr: 'French',
|
||||
de: 'German',
|
||||
es: 'Spanish',
|
||||
pt: 'Portuguese',
|
||||
zh: 'Chinese',
|
||||
ja: 'Japanese',
|
||||
hi: 'Hindi',
|
||||
it: 'Italian',
|
||||
ko: 'Korean',
|
||||
nl: 'Dutch',
|
||||
pl: 'Polish',
|
||||
ru: 'Russian',
|
||||
sv: 'Swedish',
|
||||
tr: 'Turkish',
|
||||
};
|
||||
const ttsVoices = voices.reduce((acc, voice) => {
|
||||
if (!languages.includes(voice.language)) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
const languageCode = voice.language;
|
||||
const existingLanguage = acc.find((lang) => lang.value === languageCode);
|
||||
if (existingLanguage) {
|
||||
existingLanguage.voices.push(buildVoice(voice));
|
||||
} else {
|
||||
acc.push({
|
||||
value: languageCode,
|
||||
name: languageMap[languageCode],
|
||||
voices: [buildVoice(voice)]
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return tranform(ttsVoices, undefined, TtsModelCartesia);
|
||||
}
|
||||
return tranform(ttsCartesia, undefined, TtsModelCartesia);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
testGoogleTts,
|
||||
testGoogleStt,
|
||||
@@ -879,10 +1261,15 @@ module.exports = {
|
||||
testSonioxStt,
|
||||
testElevenlabs,
|
||||
testPlayHT,
|
||||
testRimelabs,
|
||||
testAssemblyStt,
|
||||
testDeepgramTTS,
|
||||
getSpeechCredential,
|
||||
decryptCredential,
|
||||
testWhisper,
|
||||
getLanguagesAndVoicesForVendor
|
||||
testVerbioTts,
|
||||
testVerbioStt,
|
||||
getLanguagesAndVoicesForVendor,
|
||||
testSpeechmaticsStt,
|
||||
testCartesia
|
||||
};
|
||||
|
||||
3149
package-lock.json
generated
3149
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
13
package.json
13
package.json
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"name": "jambonz-api-server",
|
||||
"version": "0.9.0",
|
||||
"version": "0.9.3",
|
||||
"description": "",
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
"start": "node app.js",
|
||||
"test": "NODE_ENV=test APPLY_JAMBONZ_DB_LIMITS=1 JWT_SECRET=foobarbazzle JAMBONES_MYSQL_HOST=127.0.0.1 JAMBONES_MYSQL_PORT=3360 JAMBONES_MYSQL_USER=jambones_test JAMBONES_MYSQL_PASSWORD=jambones_test JAMBONES_MYSQL_DATABASE=jambones_test JAMBONES_REDIS_HOST=localhost JAMBONES_REDIS_PORT=16379 JAMBONES_TIME_SERIES_HOST=127.0.0.1 JAMBONES_LOGLEVEL=error JAMBONES_CREATE_CALL_URL=http://localhost/v1/createCall K8S=true K8S_FEATURE_SERVER_SERVICE_NAME=127.0.0.1 K8S_FEATURE_SERVER_SERVICE_PORT=3100 node test/ ",
|
||||
"test:encrypt-decrypt": "ENCRYPTION_SECRET=12345 node --test ./test/encrypt-decrypt.test.js",
|
||||
"integration-test": "NODE_ENV=test JAMBONES_AUTH_USE_JWT=1 JAMBONES_TIME_SERIES_HOST=127.0.0.1 AWS_REGION='us-east-1' JAMBONES_CURRENCY=USD JWT_SECRET=foobarbazzle JAMBONES_MYSQL_HOST=127.0.0.1 JAMBONES_MYSQL_PORT=3360 JAMBONES_MYSQL_USER=jambones_test JAMBONES_MYSQL_PASSWORD=jambones_test JAMBONES_MYSQL_DATABASE=jambones_test JAMBONES_REDIS_HOST=localhost JAMBONES_REDIS_PORT=16379 JAMBONES_LOGLEVEL=debug JAMBONES_CREATE_CALL_URL=http://localhost/v1/createCall node test/serve-integration.js",
|
||||
"upgrade-db": "node ./db/upgrade-jambonz-db.js",
|
||||
"coverage": "./node_modules/.bin/nyc --reporter html --report-dir ./coverage npm run test",
|
||||
@@ -28,10 +29,10 @@
|
||||
"@jambonz/db-helpers": "^0.9.3",
|
||||
"@jambonz/lamejs": "^1.2.2",
|
||||
"@jambonz/mw-registrar": "^0.2.7",
|
||||
"@jambonz/realtimedb-helpers": "^0.8.8",
|
||||
"@jambonz/speech-utils": "^0.0.50",
|
||||
"@jambonz/realtimedb-helpers": "^0.8.10",
|
||||
"@jambonz/speech-utils": "^0.2.1",
|
||||
"@jambonz/time-series": "^0.2.8",
|
||||
"@jambonz/verb-specifications": "^0.0.69",
|
||||
"@jambonz/verb-specifications": "^0.0.72",
|
||||
"@soniox/soniox-node": "^1.2.2",
|
||||
"argon2": "^0.40.1",
|
||||
"assemblyai": "^4.3.4",
|
||||
@@ -46,12 +47,14 @@
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"mailgun.js": "^10.2.1",
|
||||
"microsoft-cognitiveservices-speech-sdk": "1.36.0",
|
||||
"mysql2": "^3.9.3",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"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",
|
||||
|
||||
@@ -14,7 +14,7 @@ const {
|
||||
createPhoneNumber,
|
||||
deleteObjectBySid} = require('./utils');
|
||||
const logger = require('../lib/logger');
|
||||
const { addToSortedSet } = require('@jambonz/realtimedb-helpers')({
|
||||
const { addToSortedSet, createHash } = require('@jambonz/realtimedb-helpers')({
|
||||
host: process.env.JAMBONES_REDIS_HOST,
|
||||
port: process.env.JAMBONES_REDIS_PORT || 6379
|
||||
}, logger);
|
||||
@@ -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`, {
|
||||
@@ -314,6 +317,19 @@ test('account tests', async(t) => {
|
||||
});
|
||||
t.ok(result.statusCode === 200 && result.body.length === 0, 'successfully queried account queue info with for an invalid account');
|
||||
|
||||
// query conferences
|
||||
await createHash(`conf:${sid}:conf1`, 'url1');
|
||||
await createHash(`conf:${sid}:conf2`, 'url2');
|
||||
await createHash(`conf:${sid}:conf3`, 'url3');
|
||||
await createHash(`conf:${sid}:conf4`, 'url4');
|
||||
|
||||
result = await request.get(`/Accounts/${sid}/Conferences`, {
|
||||
auth: authAdmin,
|
||||
resolveWithFullResponse: true,
|
||||
json: true,
|
||||
});
|
||||
t.ok(result.statusCode === 200 && result.body.length === 4, 'successfully queried account conferences info for an account');
|
||||
|
||||
/* delete account */
|
||||
result = await request.delete(`/Accounts/${sid}`, {
|
||||
auth: authAdmin,
|
||||
|
||||
@@ -121,6 +121,25 @@ test('application tests', async(t) => {
|
||||
let app_json = JSON.parse(result.app_json);
|
||||
t.ok(app_json[0].verb === 'play', 'successfully retrieved app_json from application')
|
||||
|
||||
/* query one application by name*/
|
||||
result = await request.get(`/Applications`, {
|
||||
qs : {
|
||||
name: 'daveh'
|
||||
},
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
});
|
||||
t.ok(result.length === 1 && result[0].name === 'daveh', 'successfully queried application by name');
|
||||
|
||||
/* query application with invalid name*/
|
||||
result = await request.get(`/Applications`, {
|
||||
qs : {
|
||||
name: 'daveh-invalid'
|
||||
},
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
});
|
||||
t.ok(result.length === 0, 'successfully queried application by invalid name, no results found');
|
||||
|
||||
/* update applications */
|
||||
result = await request.put(`/Applications/${sid}`, {
|
||||
|
||||
132
test/encrypt-decrypt.test.js
Normal file
132
test/encrypt-decrypt.test.js
Normal file
@@ -0,0 +1,132 @@
|
||||
const assert = require('node:assert');
|
||||
const { describe, it, beforeEach, before } = require('node:test');
|
||||
const { obscureBucketCredentialsSensitiveData, isObscureKey, obscureKey } = require('../lib/utils/encrypt-decrypt');
|
||||
const { randomUUID } = require('node:crypto');
|
||||
|
||||
let bucket_credential = {};
|
||||
let service_key = {};
|
||||
|
||||
describe('encrypt-decrypt test suite', { skip: false }, () => {
|
||||
before(() => {
|
||||
process.env.ENCRYPTION_SECRET = "12345";
|
||||
});
|
||||
beforeEach(() => {
|
||||
service_key = {
|
||||
"type": "service_account",
|
||||
"project_id": "voice-gateway",
|
||||
"private_key_id": randomUUID(),
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\nJ6T2sQmBAD8TBm0frkGeyw0FA0aVAASVBJcwggSjBgEAAoabAQDUbiVwgufJm9R3\nv7YpECxRnQgHduX8jnbjtubi6FVqqVx78W3NM/gFNJFBC7NXtpsslqefKM6SmlkK\nNP6XqXy/ppXO8u00se7+cOFhsS6crncnsCeIYPxLFPV8P4BjExi7v88RBdektLeV\nX6sRWhxiYeWe+ORxkyC0KR4IKZjHt7ZHrg0kNQyuNx1KOhJnN3rRUkKSP2zozd1c\n3V+4EfpZjGmlQegXNHzkwUjvTOe6nuyxynWe3smjsSw/3RQda5m674Kh9tnVWEiX\n5KWnfRbWDdEBp5azzOAdeSR5W+qQfS0Jo6blREDQxWfMNmut87m81gmn+DKMhq6k\nV0JVRQStAgMBAAECggEAAf7AsAdI24zUdXkZv98gEuC4S10e2Luoz6Yvm41V3sP6\nWx0mdgY7RB9RW8U4WPnu3GhPGGJj03SyHUuI8Ix15MNM9WGDAaV3S+kRb1LqChLO\nCoNSO/qYPPg4t1iQ9+s7sWTnM93MAXjujmSveHJd7+MrUQOxOdPjB7I/ozMkBXBb\nWYsBIfeOG/7DsC4N4V/hKVXAq9NekGQv85yCUPR/DpuG9vqpztXwaSC1Wihntlu3\nNJYmMave4PbO3OxAekBl70WmukERZo7ksR+34WWse4HXlphaUVbpvnbQdGT1EzXW\nZukanqpfkIwrHme2Ko5NdP0C54pRhg6kpmWszVUMQQKBgQD6yWMuuwdPuFLg0wc2\nnDUeyHZSq/SEMSrb1wNXhL3qhaL2tCjBZwG9CHDuFMhqDLU5F36mEdAsM5MHCzyC\nTJ7VbqvCFz69SRt1CVp76Hu4HO/1Nhxm1GhF+NKSDIbnUg3o4DaC7EUtLpqYXcWj\nsXHEqVEhkrNVQ/JOIfJr42LDfQKBgQDM0U3ghGg9ufe9mb0dOonwtJA/ay/pd08Q\nyq3ue9C3yoQiNf28bP3AGKIjhA6wtd0KTSkQs6ktabGHIM8o/eTrMAMQllKh/3xe\nON7iND8Xz2GFMuIraQ7Mq9RvYWiqqIkVg1GQfJmiQ9wcmGj2PHy25LfjBXfHAYqK\nQ++P/i+s8QKBgD0pRi4MYNEZY+T+skCoQfA69VheJWjj0M8ClgcPEX4Tj1XZRCM+\nqtbeKyR1Hxd19/BvgWyg5YMSJOZP4Dbq1sW4ktzn7F4faTnWySF05k9Vh1PnGXAe\nlzuRXlFOCsx5X3kOzVyKoKhPOFa2b8/nI5bRsD6e12uRAZP6hXO4ZcrFAoGABVJ/\nCpGGP+xgMq4XCvZldTrL8MnxQcjW5iHOKT9QaiY6DsWGZWoTofVB6VhaJV9kcgsV\nQRjaEZMIiPFiULdgRnhF7B1r4kfITI5/xDMFXLIH37U1yVj+iHUCnS5T0PN2NHfo\nG7ARMfU/eALB33ws5XfGC4Et3p78oaEoTX6WcJECgYEAhysSt4qieGRSXn24V0+u\ny/ubU4dysn4SFe8BB4bjgYa8v+6VwucU+nnU4wOwykEgJmzN/ukzpvFN0CkN8eAN\n8xwtjBX9Zc1S90Wf7/7IrQGlUnsSFpDh5TW+oCqVo8JK7UGxJHR1mCvJmYVmSk3c\nD4AMvJ2x/Z5d9NKIAzdET4o=\n-----END PRIVATE KEY-----\n",
|
||||
"client_email": "voicegatewayteam@voice-gateway.iam.gserviceaccount.com",
|
||||
"client_id": "333827616171177028875",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://oauth2.googleapis.com/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/cognigyvoicegatewayteam%40cognigy-voice-gateway.iam.gserviceaccount.com",
|
||||
"universe_domain": "googleapis.com"
|
||||
};
|
||||
bucket_credential = {
|
||||
"vendor": "google",
|
||||
"service_key": "",
|
||||
"connection_string": "",
|
||||
"secret_access_key": ""
|
||||
}
|
||||
});
|
||||
|
||||
describe('obscureBucketCredentialsSensitiveData', { skip: false }, () => {
|
||||
it('should obscure "private_key" - vendor: google', { skip: false }, () => {
|
||||
bucket_credential = {
|
||||
...bucket_credential,
|
||||
"vendor": "google",
|
||||
"service_key": JSON.stringify(service_key),
|
||||
};
|
||||
const result = obscureBucketCredentialsSensitiveData(bucket_credential);
|
||||
const googleCredentials = JSON.parse(result.service_key);
|
||||
assert.strictEqual(googleCredentials.private_key, '-----BEGIN PRIVATE KEY-----\nJ6T2sQXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX');
|
||||
assert.strictEqual(googleCredentials.private_key_id, googleCredentials.private_key_id);
|
||||
assert.strictEqual(googleCredentials.project_id, googleCredentials.project_id);
|
||||
assert.strictEqual(googleCredentials.client_email, googleCredentials.client_email);
|
||||
assert.strictEqual(googleCredentials.client_id, googleCredentials.client_id);
|
||||
assert.strictEqual(googleCredentials.auth_uri, googleCredentials.auth_uri);
|
||||
assert.strictEqual(googleCredentials.token_uri, googleCredentials.token_uri);
|
||||
});
|
||||
|
||||
it('should skip obscure since it is already obscured. vendor: google - "private key"', { skip: false }, () => {
|
||||
service_key.private_key = '-----BEGIN PRIVATE KEY-----\nJ6T2sQXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
|
||||
bucket_credential = {
|
||||
...bucket_credential,
|
||||
"vendor": "google",
|
||||
"service_key": JSON.stringify(service_key),
|
||||
};
|
||||
const result = obscureBucketCredentialsSensitiveData(bucket_credential);
|
||||
const googleCredentials = JSON.parse(result.service_key);
|
||||
assert.strictEqual(googleCredentials.private_key, '-----BEGIN PRIVATE KEY-----\nJ6T2sQXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX');
|
||||
assert.strictEqual(googleCredentials.private_key_id, googleCredentials.private_key_id);
|
||||
assert.strictEqual(googleCredentials.project_id, googleCredentials.project_id);
|
||||
assert.strictEqual(googleCredentials.client_email, googleCredentials.client_email);
|
||||
assert.strictEqual(googleCredentials.client_id, googleCredentials.client_id);
|
||||
assert.strictEqual(googleCredentials.auth_uri, googleCredentials.auth_uri);
|
||||
assert.strictEqual(googleCredentials.token_uri, googleCredentials.token_uri);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isObscureKey', { skip: false }, () => {
|
||||
it('vendor: google - should return true', { skip: false }, () => {
|
||||
service_key.private_key = '-----BEGIN PRIVATE KEY-----\nJ6T2sQXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
|
||||
bucket_credential = {
|
||||
...bucket_credential,
|
||||
"vendor": "google",
|
||||
"service_key": JSON.stringify(service_key),
|
||||
};
|
||||
const result = isObscureKey(bucket_credential);
|
||||
assert.strictEqual(result, true);
|
||||
});
|
||||
it('vendor: google - should return false', { skip: false }, () => {
|
||||
bucket_credential = {
|
||||
...bucket_credential,
|
||||
"vendor": "google",
|
||||
"service_key": JSON.stringify(service_key),
|
||||
};
|
||||
const result = isObscureKey(bucket_credential);
|
||||
assert.strictEqual(result, false);
|
||||
});
|
||||
it('vendor: aws_s3 - should return true', { skip: false }, () => {
|
||||
const obscuredKey = "J6T2sQXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
|
||||
bucket_credential = {
|
||||
...bucket_credential,
|
||||
"vendor": "aws_s3",
|
||||
"secret_access_key": obscuredKey,
|
||||
};
|
||||
const result = isObscureKey(bucket_credential);
|
||||
assert.strictEqual(result, true);
|
||||
});
|
||||
it('vendor: aws_s3 - should return false', { skip: false }, () => {
|
||||
bucket_credential = {
|
||||
...bucket_credential,
|
||||
"vendor": "aws_s3",
|
||||
"service_key": "EFEU2fhcbqiw3211ffw3f1kezhcbqiw3211ffw3f",
|
||||
};
|
||||
const result = isObscureKey(bucket_credential);
|
||||
assert.strictEqual(result, false);
|
||||
});
|
||||
it('vendor: azure - should return true', { skip: false }, () => {
|
||||
const obscuredKey = 'https:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
|
||||
bucket_credential = {
|
||||
...bucket_credential,
|
||||
"vendor": "azure",
|
||||
"connection_string": obscuredKey,
|
||||
};
|
||||
const result = isObscureKey(bucket_credential);
|
||||
assert.strictEqual(result, true);
|
||||
});
|
||||
it('vendor: azure - should return false', { skip: false }, () => {
|
||||
bucket_credential = {
|
||||
...bucket_credential,
|
||||
"vendor": "azure",
|
||||
connection_string: 'https://cognigydevstorage.blob.core.windows.net/voicegateway-test?sp=rw&st=2023-09-10T13:35:44Z&se=2023-09-11T21:35:44Z&spr=https&sv=2022-11-02&sr=c&sig=9WN8Bg5UMOvnV1h1cJpCnTUG%2FnonTbRZ1Q1rbKnDUl4%3D',
|
||||
};
|
||||
const result = isObscureKey(bucket_credential);
|
||||
assert.strictEqual(result, false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
@@ -75,6 +122,44 @@ test('sip gateway tests', async(t) => {
|
||||
});
|
||||
//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,
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
body: {
|
||||
voip_carrier_sid,
|
||||
ipv4: '192.168.1.2',
|
||||
netmask: 32,
|
||||
inbound: true,
|
||||
outbound: true,
|
||||
protocol: 'tls',
|
||||
use_sips_scheme: true
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully created sip gateway ');
|
||||
const sipsSid = result.body.sid;
|
||||
|
||||
/* query one sip gateway */
|
||||
result = await request.get(`/SipGateways/${sipsSid}`, {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
});
|
||||
//console.log(`result: ${JSON.stringify(result)}`);
|
||||
t.ok(result.ipv4 === '192.168.1.2' , 'successfully retrieved voip carrier by sid');
|
||||
t.ok(result.protocol === 'tls' , 'successfully retrieved voip carrier by sid');
|
||||
t.ok(result.use_sips_scheme, 'successfully retrieved voip carrier by sid');
|
||||
|
||||
/* delete sip gateways */
|
||||
result = await request.delete(`/SipGateways/${sipsSid}`, {
|
||||
resolveWithFullResponse: true,
|
||||
simple: false,
|
||||
json: true,
|
||||
auth: authAdmin
|
||||
});
|
||||
//console.log(`result: ${JSON.stringify(result)}`);
|
||||
t.ok(result.statusCode === 204, 'successfully deleted sip gateway');
|
||||
|
||||
await deleteObjectBySid(request, '/VoipCarriers', voip_carrier_sid);
|
||||
|
||||
|
||||
@@ -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,
|
||||
@@ -301,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');
|
||||
@@ -316,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,
|
||||
@@ -325,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');
|
||||
@@ -339,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,
|
||||
@@ -404,6 +478,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,
|
||||
@@ -444,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,
|
||||
@@ -570,6 +691,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 +780,82 @@ 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',
|
||||
label: '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');
|
||||
|
||||
/* add a credential for verbio */
|
||||
result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, {
|
||||
resolveWithFullResponse: true,
|
||||
auth: authUser,
|
||||
json: true,
|
||||
body: {
|
||||
vendor: 'verbio',
|
||||
use_for_tts: true,
|
||||
use_for_stt: true,
|
||||
client_id: 'client:id',
|
||||
client_secret: 'client:secret',
|
||||
engine_version: 'V1'
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully added speech credential for Verbio');
|
||||
const verbioSid = result.body.sid;
|
||||
|
||||
result = await request.get(`/Accounts/${account_sid}/SpeechCredentials/${verbioSid}`, {
|
||||
resolveWithFullResponse: true,
|
||||
simple: false,
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
});
|
||||
t.ok(result.body.engine_version === "V1", 'successfully get verbio speech credential');
|
||||
|
||||
result = await request.put(`/Accounts/${account_sid}/SpeechCredentials/${verbioSid}`, {
|
||||
resolveWithFullResponse: true,
|
||||
auth: authUser,
|
||||
json: true,
|
||||
body: {
|
||||
use_for_tts: true,
|
||||
use_for_stt: true,
|
||||
engine_version: 'V2'
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 204, 'successfully updated speech credential for verbio');
|
||||
|
||||
result = await request.get(`/Accounts/${account_sid}/SpeechCredentials/${verbioSid}`, {
|
||||
resolveWithFullResponse: true,
|
||||
simple: false,
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
});
|
||||
t.ok(result.body.engine_version === "V2", 'successfully Updated verbio speech credential');
|
||||
|
||||
/* delete the credential */
|
||||
result = await request.delete(`/Accounts/${account_sid}/SpeechCredentials/${verbioSid}`, {
|
||||
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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -24,16 +24,24 @@ function makeSynthKey({account_sid = '', vendor, language, voice, engine = '', t
|
||||
test('tts-cache', async(t) => {
|
||||
const app = require('../app');
|
||||
try {
|
||||
// clear cache to start
|
||||
let result = await request.delete('/TtsCache', {
|
||||
auth: authAdmin,
|
||||
resolveWithFullResponse: true,
|
||||
});
|
||||
t.ok(result.statusCode === 204, 'successfully purged cache for start of test');
|
||||
|
||||
// create caches
|
||||
const minRecords = 8;
|
||||
for (const i in Array(minRecords).fill(0)) {
|
||||
await client.set(makeSynthKey({vendor: i, language: i, voice: i, engine: i, text: i}), i);
|
||||
}
|
||||
|
||||
let result = await request.get('/TtsCache', {
|
||||
result = await request.get('/TtsCache', {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
});
|
||||
//console.log(result);
|
||||
|
||||
t.ok(result.size === minRecords, 'get cache correctly');
|
||||
|
||||
@@ -41,7 +49,7 @@ test('tts-cache', async(t) => {
|
||||
auth: authAdmin,
|
||||
resolveWithFullResponse: true,
|
||||
});
|
||||
t.ok(result.statusCode === 204, 'successfully deleted application after removing phone number');
|
||||
t.ok(result.statusCode === 204, 'successfully purged cache');
|
||||
|
||||
result = await request.get('/TtsCache', {
|
||||
auth: authAdmin,
|
||||
|
||||
@@ -207,7 +207,8 @@ test('voip carrier tests', async(t) => {
|
||||
json: true,
|
||||
body: {
|
||||
name: 'twilio',
|
||||
e164_leading_plus: true
|
||||
e164_leading_plus: true,
|
||||
dtmf_type: 'tones'
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully created voip carrier for a service provider');
|
||||
@@ -221,6 +222,7 @@ test('voip carrier tests', async(t) => {
|
||||
});
|
||||
//console.log(result.body);
|
||||
t.ok(result.statusCode === 200, 'successfully retrieved voip carrier for a service provider');
|
||||
//console.log(result.body);
|
||||
sid = result.body[0].voip_carrier_sid;
|
||||
|
||||
await deleteObjectBySid(request, '/VoipCarriers', sid);
|
||||
|
||||
Reference in New Issue
Block a user