mirror of
https://github.com/jambonz/jambonz-api-server.git
synced 2026-01-25 02:08:24 +00:00
Compare commits
156 Commits
v0.8.2-rc1
...
fix/micros
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f904930ae | ||
|
|
c0fab2880b | ||
|
|
ce2fa392a4 | ||
|
|
3b47162d13 | ||
|
|
b765232d4f | ||
|
|
2436bea6ea | ||
|
|
f67abddbd4 | ||
|
|
39fcb17dec | ||
|
|
80418aa7e5 | ||
|
|
b21d10eb3e | ||
|
|
7875eb51b9 | ||
|
|
e2c1383723 | ||
|
|
40de2c5945 | ||
|
|
3a299bc3ca | ||
|
|
70c9407742 | ||
|
|
dba66d58fc | ||
|
|
0ff3d22faf | ||
|
|
187a428a75 | ||
|
|
a4792a521f | ||
|
|
3ac9693735 | ||
|
|
3ad54a0e72 | ||
|
|
bd8fb2f9db | ||
|
|
32b317ae68 | ||
|
|
40e8d08727 | ||
|
|
256ca440a0 | ||
|
|
68d73345ef | ||
|
|
54dd72ff66 | ||
|
|
832a4e8032 | ||
|
|
33c3b99e2e | ||
|
|
8b2a2e196e | ||
|
|
556717a9a4 | ||
|
|
f2c635268f | ||
|
|
c8999a5929 | ||
|
|
7e046ac7f3 | ||
|
|
997ff05f3c | ||
|
|
55d8fdef1c | ||
|
|
7d355f2fac | ||
|
|
c6b8ec1b28 | ||
|
|
10159a0ba6 | ||
|
|
7a558c7349 | ||
|
|
4dbe7af9db | ||
|
|
4ec34faa29 | ||
|
|
e2d6086f9f | ||
|
|
0e056ad296 | ||
|
|
1d69457ddc | ||
|
|
dcfe6cc05d | ||
|
|
a474c2d4cc | ||
|
|
0f244cf6d5 | ||
|
|
92d9468570 | ||
|
|
1b143f6aae | ||
|
|
43344ae14b | ||
|
|
e6168d0a3c | ||
|
|
f725d5f0a1 | ||
|
|
1e9f388f51 | ||
|
|
72d2877ddf | ||
|
|
f15c339a2a | ||
|
|
30ba84d57b | ||
|
|
a4e767e1e4 | ||
|
|
7b805130bb | ||
|
|
a1c302f85c | ||
|
|
bf9ae3b5ce | ||
|
|
4c9af253a3 | ||
|
|
936a9244ba | ||
|
|
0522ae408c | ||
|
|
9c788cdedc | ||
|
|
cbc5e2d6f7 | ||
|
|
f4d6fd14b8 | ||
|
|
b190334839 | ||
|
|
209a58ff51 | ||
|
|
f8720bab9f | ||
|
|
77363d54d1 | ||
|
|
ad483ba0b7 | ||
|
|
02c9a951d4 | ||
|
|
d5f5e3a86f | ||
|
|
62cea3a9e9 | ||
|
|
6d3bfd527e | ||
|
|
9002bacf8f | ||
|
|
92473454d6 | ||
|
|
1c2280af88 | ||
|
|
7d16bdd774 | ||
|
|
79e1bc8d12 | ||
|
|
9d24ef6238 | ||
|
|
042ad9f629 | ||
|
|
7351f0ad68 | ||
|
|
de7b74f898 | ||
|
|
d361f1aeb1 | ||
|
|
f3d002cfca | ||
|
|
3121c2a197 | ||
|
|
b7bdf300c6 | ||
|
|
c96159268e | ||
|
|
8e200251ca | ||
|
|
898f3aec4a | ||
|
|
6f85752352 | ||
|
|
fe7cc9ad58 | ||
|
|
1ffdfebdb2 | ||
|
|
dcf1895920 | ||
|
|
c509b9d277 | ||
|
|
eff8474997 | ||
|
|
b4237beeeb | ||
|
|
0406e42c19 | ||
|
|
533cd2f47d | ||
|
|
742884cc72 | ||
|
|
9fccfa2a73 | ||
|
|
3356b7302a | ||
|
|
9f533ed17c | ||
|
|
a0797a3a4c | ||
|
|
0b33ef0c2c | ||
|
|
71ecf453f8 | ||
|
|
494f1cf784 | ||
|
|
da74e2526a | ||
|
|
e35a03c7ad | ||
|
|
46fb9b8875 | ||
|
|
f9df2b3028 | ||
|
|
32ff023b14 | ||
|
|
f3d3afee73 | ||
|
|
3c8cbd97c5 | ||
|
|
eba9c98412 | ||
|
|
c2065ef787 | ||
|
|
307787526d | ||
|
|
3141646dfd | ||
|
|
cac6e2117d | ||
|
|
6d34d6f886 | ||
|
|
964afc1660 | ||
|
|
d09dca47b9 | ||
|
|
f3ec847474 | ||
|
|
cf7ce675f5 | ||
|
|
34895daf4f | ||
|
|
b06032b5f0 | ||
|
|
3486ff958c | ||
|
|
f79f96b884 | ||
|
|
2aa3d40268 | ||
|
|
148fc49f06 | ||
|
|
02806a109c | ||
|
|
077c791e37 | ||
|
|
4b70c6458a | ||
|
|
aadb0b15f2 | ||
|
|
3997f57365 | ||
|
|
c97874ed1f | ||
|
|
1dcc92a177 | ||
|
|
105aa16ffe | ||
|
|
a574045f8a | ||
|
|
af3d03bef9 | ||
|
|
5b1b50c3a3 | ||
|
|
ba431aeb35 | ||
|
|
36607b505f | ||
|
|
616a0b364d | ||
|
|
1b764b31e6 | ||
|
|
009396becc | ||
|
|
84305e30cc | ||
|
|
9c7f8b4e7b | ||
|
|
b2dce18c7a | ||
|
|
8f93b69af0 | ||
|
|
127b690ae2 | ||
|
|
3ad19eca3c | ||
|
|
efe7e22109 | ||
|
|
7a67ed704c |
60
.github/workflows/docker-publish-dbcreate.yml
vendored
60
.github/workflows/docker-publish-dbcreate.yml
vendored
@@ -2,16 +2,10 @@ name: Docker
|
||||
|
||||
on:
|
||||
push:
|
||||
# Publish `main` as Docker `latest` image.
|
||||
branches:
|
||||
- main
|
||||
|
||||
# Publish `v1.2.3` tags as releases.
|
||||
tags:
|
||||
- v*
|
||||
|
||||
env:
|
||||
IMAGE_NAME: db-create
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
push:
|
||||
@@ -20,32 +14,42 @@ jobs:
|
||||
if: github.event_name == 'push'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Build image
|
||||
run: docker build . --file Dockerfile.db-create --tag $IMAGE_NAME
|
||||
|
||||
- name: Log into registry
|
||||
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
||||
|
||||
- name: Push image
|
||||
- name: prepare tag
|
||||
id: prepare_tag
|
||||
run: |
|
||||
IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME
|
||||
IMAGE_ID=jambonz/db-create
|
||||
|
||||
# Change all uppercase to lowercase
|
||||
IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')
|
||||
# Strip git ref prefix from version
|
||||
VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
|
||||
|
||||
# Strip git ref prefix from version
|
||||
VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
|
||||
# Strip "v" prefix from tag name
|
||||
[[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//')
|
||||
|
||||
# Strip "v" prefix from tag name
|
||||
[[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//')
|
||||
# Use Docker `latest` tag convention
|
||||
[ "$VERSION" == "main" ] && VERSION=latest
|
||||
|
||||
# Use Docker `latest` tag convention
|
||||
[ "$VERSION" == "main" ] && VERSION=latest
|
||||
echo IMAGE_ID=$IMAGE_ID
|
||||
echo VERSION=$VERSION
|
||||
|
||||
echo IMAGE_ID=$IMAGE_ID
|
||||
echo VERSION=$VERSION
|
||||
echo "image_id=$IMAGE_ID" >> $GITHUB_OUTPUT
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
docker tag $IMAGE_NAME $IMAGE_ID:$VERSION
|
||||
docker push $IMAGE_ID:$VERSION
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile.db-create
|
||||
push: true
|
||||
tags: ${{ steps.prepare_tag.outputs.image_id }}:${{ steps.prepare_tag.outputs.version }}
|
||||
build-args: |
|
||||
GITHUB_REPOSITORY=$GITHUB_REPOSITORY
|
||||
GITHUB_REF=$GITHUB_REF
|
||||
|
||||
59
.github/workflows/docker-publish.yml
vendored
59
.github/workflows/docker-publish.yml
vendored
@@ -2,16 +2,10 @@ name: Docker
|
||||
|
||||
on:
|
||||
push:
|
||||
# Publish `main` as Docker `latest` image.
|
||||
branches:
|
||||
- main
|
||||
|
||||
# Publish `v1.2.3` tags as releases.
|
||||
tags:
|
||||
- v*
|
||||
|
||||
env:
|
||||
IMAGE_NAME: api-server
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
push:
|
||||
@@ -20,32 +14,41 @@ jobs:
|
||||
if: github.event_name == 'push'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Build image
|
||||
run: docker build . --file Dockerfile --tag $IMAGE_NAME
|
||||
|
||||
- name: Log into registry
|
||||
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
||||
|
||||
- name: Push image
|
||||
- name: prepare tag
|
||||
id: prepare_tag
|
||||
run: |
|
||||
IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME
|
||||
IMAGE_ID=jambonz/api-server
|
||||
|
||||
# Change all uppercase to lowercase
|
||||
IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')
|
||||
# Strip git ref prefix from version
|
||||
VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
|
||||
|
||||
# Strip git ref prefix from version
|
||||
VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
|
||||
# Strip "v" prefix from tag name
|
||||
[[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//')
|
||||
|
||||
# Strip "v" prefix from tag name
|
||||
[[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//')
|
||||
# Use Docker `latest` tag convention
|
||||
[ "$VERSION" == "main" ] && VERSION=latest
|
||||
|
||||
# Use Docker `latest` tag convention
|
||||
[ "$VERSION" == "main" ] && VERSION=latest
|
||||
echo IMAGE_ID=$IMAGE_ID
|
||||
echo VERSION=$VERSION
|
||||
|
||||
echo IMAGE_ID=$IMAGE_ID
|
||||
echo VERSION=$VERSION
|
||||
echo "image_id=$IMAGE_ID" >> $GITHUB_OUTPUT
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
docker tag $IMAGE_NAME $IMAGE_ID:$VERSION
|
||||
docker push $IMAGE_ID:$VERSION
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.prepare_tag.outputs.image_id }}:${{ steps.prepare_tag.outputs.version }}
|
||||
build-args: |
|
||||
GITHUB_REPOSITORY=$GITHUB_REPOSITORY
|
||||
GITHUB_REF=$GITHUB_REF
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM --platform=linux/amd64 node:18.14.1-alpine3.16 as base
|
||||
FROM --platform=linux/amd64 node:18.15-alpine3.16 as base
|
||||
|
||||
RUN apk --update --no-cache add --virtual .builds-deps build-base python3
|
||||
|
||||
|
||||
@@ -33,6 +33,8 @@ Configuration is provided via environment variables:
|
||||
|K8S| service running as kubernetes service |no|
|
||||
|K8S_FEATURE_SERVER_SERVICE_NAME| feature server name(required for K8S) |no|
|
||||
|K8S_FEATURE_SERVER_SERVICE_PORT| feature server port(required for K8S) |no|
|
||||
|JAMBONZ_RECORD_WS_USERNAME| recording websocket username|no|
|
||||
|JAMBONZ_RECORD_WS_PASSWORD| recording websocket password|no|
|
||||
|
||||
#### Database dependency
|
||||
A mysql database is used to store long-lived objects such as Accounts, Applications, etc. To create the database schema, use or review the scripts in the 'db' folder, particularly:
|
||||
|
||||
91
app.js
91
app.js
@@ -3,16 +3,23 @@ const logger = require('./lib/logger');
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
const helmet = require('helmet');
|
||||
const nocache = require('nocache');
|
||||
const rateLimit = require('express-rate-limit');
|
||||
const cors = require('cors');
|
||||
const passport = require('passport');
|
||||
const routes = require('./lib/routes');
|
||||
const Registrar = require('@jambonz/mw-registrar');
|
||||
|
||||
assert.ok(process.env.JAMBONES_MYSQL_HOST &&
|
||||
process.env.JAMBONES_MYSQL_USER &&
|
||||
process.env.JAMBONES_MYSQL_PASSWORD &&
|
||||
process.env.JAMBONES_MYSQL_DATABASE, 'missing JAMBONES_MYSQL_XXX env vars');
|
||||
assert.ok(process.env.JAMBONES_REDIS_HOST, 'missing JAMBONES_REDIS_HOST env var');
|
||||
if (process.env.JAMBONES_REDIS_SENTINELS) {
|
||||
assert.ok(process.env.JAMBONES_REDIS_SENTINEL_MASTER_NAME,
|
||||
'missing JAMBONES_REDIS_SENTINEL_MASTER_NAME env var, JAMBONES_REDIS_SENTINEL_PASSWORD env var is optional');
|
||||
} else {
|
||||
assert.ok(process.env.JAMBONES_REDIS_HOST, 'missing JAMBONES_REDIS_HOST env var');
|
||||
}
|
||||
assert.ok(process.env.JAMBONES_TIME_SERIES_HOST, 'missing JAMBONES_TIME_SERIES_HOST env var');
|
||||
assert.ok(process.env.ENCRYPTION_SECRET || process.env.JWT_SECRET, 'missing ENCRYPTION_SECRET env var');
|
||||
assert.ok(process.env.JWT_SECRET, 'missing JWT_SECRET env var');
|
||||
@@ -29,21 +36,25 @@ const {
|
||||
logger, process.env.JAMBONES_TIME_SERIES_HOST
|
||||
);
|
||||
const {
|
||||
client,
|
||||
retrieveCall,
|
||||
deleteCall,
|
||||
listCalls,
|
||||
listSortedSets,
|
||||
purgeCalls,
|
||||
retrieveSet,
|
||||
addKey,
|
||||
retrieveKey,
|
||||
deleteKey,
|
||||
incrKey
|
||||
} = require('./lib/helpers/realtimedb-helpers');
|
||||
const {
|
||||
getTtsVoices
|
||||
} = require('@jambonz/speech-utils')({
|
||||
host: process.env.JAMBONES_REDIS_HOST,
|
||||
port: process.env.JAMBONES_REDIS_PORT || 6379
|
||||
}, logger);
|
||||
getTtsVoices,
|
||||
getTtsSize,
|
||||
purgeTtsCache,
|
||||
getAwsAuthToken,
|
||||
synthAudio
|
||||
} = require('@jambonz/speech-utils')({}, logger);
|
||||
const {
|
||||
lookupAppBySid,
|
||||
lookupAccountBySid,
|
||||
@@ -51,7 +62,8 @@ const {
|
||||
lookupAppByPhoneNumber,
|
||||
lookupCarrierBySid,
|
||||
lookupSipGatewayBySid,
|
||||
lookupSmppGatewayBySid
|
||||
lookupSmppGatewayBySid,
|
||||
lookupClientByAccountAndUsername
|
||||
} = require('@jambonz/db-helpers')({
|
||||
host: process.env.JAMBONES_MYSQL_HOST,
|
||||
user: process.env.JAMBONES_MYSQL_USER,
|
||||
@@ -62,22 +74,31 @@ const {
|
||||
}, logger);
|
||||
const PORT = process.env.HTTP_PORT || 3000;
|
||||
const authStrategy = require('./lib/auth')(logger, retrieveKey);
|
||||
const {delayLoginMiddleware} = require('./lib/middleware');
|
||||
const Websocket = require('ws');
|
||||
|
||||
passport.use(authStrategy);
|
||||
|
||||
app.locals = app.locals || {};
|
||||
app.locals = {
|
||||
...app.locals,
|
||||
registrar: new Registrar(logger, client),
|
||||
logger,
|
||||
retrieveCall,
|
||||
deleteCall,
|
||||
listCalls,
|
||||
listSortedSets,
|
||||
purgeCalls,
|
||||
retrieveSet,
|
||||
addKey,
|
||||
incrKey,
|
||||
retrieveKey,
|
||||
deleteKey,
|
||||
getTtsVoices,
|
||||
getTtsSize,
|
||||
getAwsAuthToken,
|
||||
purgeTtsCache,
|
||||
synthAudio,
|
||||
lookupAppBySid,
|
||||
lookupAccountBySid,
|
||||
lookupAccountByPhoneNumber,
|
||||
@@ -85,6 +106,7 @@ app.locals = {
|
||||
lookupCarrierBySid,
|
||||
lookupSipGatewayBySid,
|
||||
lookupSmppGatewayBySid,
|
||||
lookupClientByAccountAndUsername,
|
||||
queryCdrs,
|
||||
queryCdrsSP,
|
||||
queryAlerts,
|
||||
@@ -108,6 +130,12 @@ const limiter = rateLimit({
|
||||
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
|
||||
});
|
||||
|
||||
// Setup websocket for recording audio
|
||||
const recordWsServer = require('./lib/record');
|
||||
const wsServer = new Websocket.Server({ noServer: true });
|
||||
wsServer.setMaxListeners(0);
|
||||
wsServer.on('connection', recordWsServer.bind(null, logger));
|
||||
|
||||
if (process.env.JAMBONES_TRUST_PROXY) {
|
||||
const proxyCount = parseInt(process.env.JAMBONES_TRUST_PROXY);
|
||||
if (!isNaN(proxyCount) && proxyCount > 0) {
|
||||
@@ -122,9 +150,11 @@ if (process.env.JAMBONES_TRUST_PROXY) {
|
||||
app.use(limiter);
|
||||
app.use(helmet());
|
||||
app.use(helmet.hidePoweredBy());
|
||||
app.use(nocache());
|
||||
app.use(passport.initialize());
|
||||
app.use(cors());
|
||||
app.use(express.urlencoded({extended: true}));
|
||||
app.use(delayLoginMiddleware);
|
||||
app.use(unless(['/stripe'], express.json()));
|
||||
app.use('/v1', unless(
|
||||
[
|
||||
@@ -146,7 +176,52 @@ app.use((err, req, res, next) => {
|
||||
});
|
||||
});
|
||||
logger.info(`listening for HTTP traffic on port ${PORT}`);
|
||||
app.listen(PORT);
|
||||
const server = app.listen(PORT);
|
||||
|
||||
|
||||
const isValidWsKey = (hdr) => {
|
||||
const username = process.env.JAMBONZ_RECORD_WS_USERNAME || process.env.JAMBONES_RECORD_WS_USERNAME;
|
||||
const password = process.env.JAMBONZ_RECORD_WS_PASSWORD || process.env.JAMBONES_RECORD_WS_PASSWORD;
|
||||
if (username && password) {
|
||||
if (!hdr) {
|
||||
// auth header is missing
|
||||
return false;
|
||||
}
|
||||
const token = Buffer.from(`${username}:${password}`).toString('base64');
|
||||
const arr = /^Basic (.*)$/.exec(hdr);
|
||||
if (!Array.isArray(arr)) {
|
||||
// malformed auth header
|
||||
return false;
|
||||
}
|
||||
return arr[1] === token;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
server.on('upgrade', (request, socket, head) => {
|
||||
logger.debug({
|
||||
url: request.url,
|
||||
headers: request.headers,
|
||||
}, 'received upgrade request');
|
||||
|
||||
/* verify the path starts with /transcribe */
|
||||
if (!request.url.includes('/record/')) {
|
||||
logger.info(`unhandled path: ${request.url}`);
|
||||
return socket.write('HTTP/1.1 404 Not Found \r\n\r\n', () => socket.destroy());
|
||||
}
|
||||
|
||||
/* verify the api key */
|
||||
if (!isValidWsKey(request.headers['authorization'])) {
|
||||
logger.info(`invalid auth header: ${request.headers['authorization'] || 'authorization header missing'}`);
|
||||
return socket.write('HTTP/1.1 403 Forbidden \r\n\r\n', () => socket.destroy());
|
||||
}
|
||||
|
||||
/* complete the upgrade */
|
||||
wsServer.handleUpgrade(request, socket, head, (ws) => {
|
||||
logger.info(`upgraded to websocket, url: ${request.url}`);
|
||||
wsServer.emit('connection', ws, request.url);
|
||||
});
|
||||
});
|
||||
|
||||
// purge old calls from active call set every 10 mins
|
||||
async function purge() {
|
||||
|
||||
@@ -14,8 +14,12 @@ DROP TABLE IF EXISTS beta_invite_codes;
|
||||
|
||||
DROP TABLE IF EXISTS call_routes;
|
||||
|
||||
DROP TABLE IF EXISTS clients;
|
||||
|
||||
DROP TABLE IF EXISTS dns_records;
|
||||
|
||||
DROP TABLE IF EXISTS lcr;
|
||||
|
||||
DROP TABLE IF EXISTS lcr_carrier_set_entry;
|
||||
|
||||
DROP TABLE IF EXISTS lcr_routes;
|
||||
@@ -50,8 +54,12 @@ DROP TABLE IF EXISTS signup_history;
|
||||
|
||||
DROP TABLE IF EXISTS smpp_addresses;
|
||||
|
||||
DROP TABLE IF EXISTS google_custom_voices;
|
||||
|
||||
DROP TABLE IF EXISTS speech_credentials;
|
||||
|
||||
DROP TABLE IF EXISTS system_information;
|
||||
|
||||
DROP TABLE IF EXISTS users;
|
||||
|
||||
DROP TABLE IF EXISTS smpp_gateways;
|
||||
@@ -124,6 +132,19 @@ application_sid CHAR(36) NOT NULL,
|
||||
PRIMARY KEY (call_route_sid)
|
||||
) COMMENT='a regex-based pattern match for call routing';
|
||||
|
||||
CREATE TABLE clients
|
||||
(
|
||||
client_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
account_sid CHAR(36) NOT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT 1,
|
||||
username VARCHAR(64),
|
||||
password VARCHAR(1024),
|
||||
allow_direct_app_calling BOOLEAN NOT NULL DEFAULT 1,
|
||||
allow_direct_queue_calling BOOLEAN NOT NULL DEFAULT 1,
|
||||
allow_direct_user_calling BOOLEAN NOT NULL DEFAULT 1,
|
||||
PRIMARY KEY (client_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE dns_records
|
||||
(
|
||||
dns_record_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
@@ -136,11 +157,23 @@ PRIMARY KEY (dns_record_sid)
|
||||
CREATE TABLE lcr_routes
|
||||
(
|
||||
lcr_route_sid CHAR(36),
|
||||
lcr_sid CHAR(36) NOT NULL,
|
||||
regex VARCHAR(32) NOT NULL COMMENT 'regex-based pattern match against dialed number, used for LCR routing of PSTN calls',
|
||||
description VARCHAR(1024),
|
||||
priority INTEGER NOT NULL UNIQUE COMMENT 'lower priority routes are attempted first',
|
||||
priority INTEGER NOT NULL COMMENT 'lower priority routes are attempted first',
|
||||
PRIMARY KEY (lcr_route_sid)
|
||||
) COMMENT='Least cost routing table';
|
||||
) COMMENT='An ordered list of digit patterns in an LCR table. The patterns are tested in sequence until one matches';
|
||||
|
||||
CREATE TABLE lcr
|
||||
(
|
||||
lcr_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
name VARCHAR(64) COMMENT 'User-assigned name for this LCR table',
|
||||
is_active BOOLEAN NOT NULL DEFAULT 1,
|
||||
default_carrier_set_entry_sid CHAR(36) COMMENT 'default carrier/route to use when no digit match based results are found.',
|
||||
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.';
|
||||
|
||||
CREATE TABLE password_settings
|
||||
(
|
||||
@@ -248,7 +281,10 @@ CREATE TABLE sbc_addresses
|
||||
sbc_address_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
ipv4 VARCHAR(255) NOT NULL,
|
||||
port INTEGER NOT NULL DEFAULT 5060,
|
||||
tls_port INTEGER,
|
||||
wss_port INTEGER,
|
||||
service_provider_sid CHAR(36),
|
||||
last_updated DATETIME,
|
||||
PRIMARY KEY (sbc_address_sid)
|
||||
);
|
||||
|
||||
@@ -304,9 +340,27 @@ last_tested DATETIME,
|
||||
tts_tested_ok BOOLEAN,
|
||||
stt_tested_ok BOOLEAN,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
label VARCHAR(64),
|
||||
PRIMARY KEY (speech_credential_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE google_custom_voices
|
||||
(
|
||||
google_custom_voice_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
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,
|
||||
PRIMARY KEY (google_custom_voice_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE system_information
|
||||
(
|
||||
domain_name VARCHAR(255),
|
||||
sip_domain_name VARCHAR(255),
|
||||
monitoring_domain_name VARCHAR(255)
|
||||
);
|
||||
|
||||
CREATE TABLE users
|
||||
(
|
||||
user_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
@@ -357,6 +411,7 @@ smpp_inbound_password VARCHAR(64),
|
||||
register_from_user VARCHAR(128),
|
||||
register_from_domain VARCHAR(255),
|
||||
register_public_ip_in_contact BOOLEAN NOT NULL DEFAULT false,
|
||||
register_status VARCHAR(4096),
|
||||
PRIMARY KEY (voip_carrier_sid)
|
||||
) COMMENT='A Carrier or customer PBX that can send or receive calls';
|
||||
|
||||
@@ -385,7 +440,7 @@ PRIMARY KEY (smpp_gateway_sid)
|
||||
CREATE TABLE phone_numbers
|
||||
(
|
||||
phone_number_sid CHAR(36) UNIQUE ,
|
||||
number VARCHAR(132) NOT NULL UNIQUE ,
|
||||
number VARCHAR(132) NOT NULL,
|
||||
voip_carrier_sid CHAR(36),
|
||||
account_sid CHAR(36),
|
||||
application_sid CHAR(36),
|
||||
@@ -398,11 +453,14 @@ CREATE TABLE sip_gateways
|
||||
sip_gateway_sid CHAR(36),
|
||||
ipv4 VARCHAR(128) NOT NULL COMMENT 'ip address or DNS name of the gateway. For gateways providing inbound calling service, ip address is required.',
|
||||
netmask INTEGER NOT NULL DEFAULT 32,
|
||||
port INTEGER NOT NULL DEFAULT 5060 COMMENT 'sip signaling port',
|
||||
port INTEGER COMMENT 'sip signaling port',
|
||||
inbound BOOLEAN NOT NULL COMMENT 'if true, whitelist this IP to allow inbound calls from the gateway',
|
||||
outbound BOOLEAN NOT NULL COMMENT 'if true, include in least-cost routing when placing calls to the PSTN',
|
||||
voip_carrier_sid CHAR(36) NOT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT 1,
|
||||
send_options_ping 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)
|
||||
) COMMENT='A whitelisted sip gateway used for origination/termination';
|
||||
|
||||
@@ -438,10 +496,21 @@ messaging_hook_sid CHAR(36) COMMENT 'webhook to call for inbound SMS/MMS ',
|
||||
app_json TEXT,
|
||||
speech_synthesis_vendor VARCHAR(64) NOT NULL DEFAULT 'google',
|
||||
speech_synthesis_language VARCHAR(12) NOT NULL DEFAULT 'en-US',
|
||||
speech_synthesis_voice VARCHAR(64),
|
||||
speech_synthesis_voice VARCHAR(256),
|
||||
speech_synthesis_label VARCHAR(64),
|
||||
speech_recognizer_vendor VARCHAR(64) NOT NULL DEFAULT 'google',
|
||||
speech_recognizer_language VARCHAR(64) NOT NULL DEFAULT 'en-US',
|
||||
speech_recognizer_label VARCHAR(64),
|
||||
use_for_fallback_speech BOOLEAN DEFAULT false,
|
||||
fallback_speech_synthesis_vendor VARCHAR(64),
|
||||
fallback_speech_synthesis_language VARCHAR(12),
|
||||
fallback_speech_synthesis_voice VARCHAR(256),
|
||||
fallback_speech_synthesis_label VARCHAR(64),
|
||||
fallback_speech_recognizer_vendor VARCHAR(64),
|
||||
fallback_speech_recognizer_language VARCHAR(64),
|
||||
fallback_speech_recognizer_label VARCHAR(64),
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
record_all_calls BOOLEAN NOT NULL DEFAULT false,
|
||||
PRIMARY KEY (application_sid)
|
||||
) COMMENT='A defined set of behaviors to be applied to phone calls ';
|
||||
|
||||
@@ -479,6 +548,9 @@ subspace_client_secret VARCHAR(255),
|
||||
subspace_sip_teleport_id VARCHAR(255),
|
||||
subspace_sip_teleport_destinations VARCHAR(255),
|
||||
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',
|
||||
PRIMARY KEY (account_sid)
|
||||
) COMMENT='An enterprise that uses the platform for comm services';
|
||||
|
||||
@@ -499,9 +571,20 @@ ALTER TABLE call_routes ADD FOREIGN KEY account_sid_idxfk_3 (account_sid) REFERE
|
||||
|
||||
ALTER TABLE call_routes ADD FOREIGN KEY application_sid_idxfk (application_sid) REFERENCES applications (application_sid);
|
||||
|
||||
CREATE INDEX client_sid_idx ON clients (client_sid);
|
||||
ALTER TABLE clients ADD CONSTRAINT account_sid_idxfk_13 FOREIGN KEY account_sid_idxfk_13 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
CREATE INDEX dns_record_sid_idx ON dns_records (dns_record_sid);
|
||||
ALTER TABLE dns_records ADD FOREIGN KEY account_sid_idxfk_4 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
CREATE INDEX lcr_sid_idx ON lcr_routes (lcr_sid);
|
||||
ALTER TABLE lcr_routes ADD FOREIGN KEY lcr_sid_idxfk (lcr_sid) REFERENCES lcr (lcr_sid);
|
||||
|
||||
CREATE INDEX lcr_sid_idx ON lcr (lcr_sid);
|
||||
ALTER TABLE lcr ADD FOREIGN KEY default_carrier_set_entry_sid_idxfk (default_carrier_set_entry_sid) REFERENCES lcr_carrier_set_entry (lcr_carrier_set_entry_sid);
|
||||
|
||||
CREATE INDEX service_provider_sid_idx ON lcr (service_provider_sid);
|
||||
CREATE INDEX account_sid_idx ON lcr (account_sid);
|
||||
CREATE INDEX permission_sid_idx ON permissions (permission_sid);
|
||||
CREATE INDEX predefined_carrier_sid_idx ON predefined_carriers (predefined_carrier_sid);
|
||||
CREATE INDEX predefined_sip_gateway_sid_idx ON predefined_sip_gateways (predefined_sip_gateway_sid);
|
||||
@@ -555,8 +638,6 @@ CREATE INDEX smpp_address_sid_idx ON smpp_addresses (smpp_address_sid);
|
||||
CREATE INDEX service_provider_sid_idx ON smpp_addresses (service_provider_sid);
|
||||
ALTER TABLE smpp_addresses ADD FOREIGN KEY service_provider_sid_idxfk_4 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||
|
||||
CREATE UNIQUE INDEX speech_credentials_idx_1 ON speech_credentials (vendor,account_sid);
|
||||
|
||||
CREATE INDEX speech_credential_sid_idx ON speech_credentials (speech_credential_sid);
|
||||
CREATE INDEX service_provider_sid_idx ON speech_credentials (service_provider_sid);
|
||||
ALTER TABLE speech_credentials ADD FOREIGN KEY service_provider_sid_idxfk_5 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||
@@ -564,6 +645,10 @@ ALTER TABLE speech_credentials ADD FOREIGN KEY service_provider_sid_idxfk_5 (ser
|
||||
CREATE INDEX account_sid_idx ON speech_credentials (account_sid);
|
||||
ALTER TABLE speech_credentials ADD FOREIGN KEY account_sid_idxfk_8 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
CREATE INDEX google_custom_voice_sid_idx ON google_custom_voices (google_custom_voice_sid);
|
||||
CREATE INDEX speech_credential_sid_idx ON google_custom_voices (speech_credential_sid);
|
||||
ALTER TABLE google_custom_voices ADD FOREIGN KEY speech_credential_sid_idxfk (speech_credential_sid) REFERENCES speech_credentials (speech_credential_sid) ON DELETE CASCADE;
|
||||
|
||||
CREATE INDEX user_sid_idx ON users (user_sid);
|
||||
CREATE INDEX email_idx ON users (email);
|
||||
CREATE INDEX phone_idx ON users (phone);
|
||||
@@ -593,6 +678,8 @@ CREATE INDEX smpp_gateway_sid_idx ON smpp_gateways (smpp_gateway_sid);
|
||||
CREATE INDEX voip_carrier_sid_idx ON smpp_gateways (voip_carrier_sid);
|
||||
ALTER TABLE smpp_gateways ADD FOREIGN KEY voip_carrier_sid_idxfk (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
|
||||
|
||||
CREATE UNIQUE INDEX phone_numbers_unique_idx_voip_carrier_number ON phone_numbers (number,voip_carrier_sid);
|
||||
|
||||
CREATE INDEX phone_number_sid_idx ON phone_numbers (phone_number_sid);
|
||||
CREATE INDEX number_idx ON phone_numbers (number);
|
||||
CREATE INDEX voip_carrier_sid_idx ON phone_numbers (voip_carrier_sid);
|
||||
@@ -648,4 +735,4 @@ ALTER TABLE accounts ADD FOREIGN KEY device_calling_application_sid_idxfk (devic
|
||||
|
||||
ALTER TABLE accounts ADD FOREIGN KEY siprec_hook_sid_idxfk (siprec_hook_sid) REFERENCES applications (application_sid);
|
||||
|
||||
SET FOREIGN_KEY_CHECKS=1;
|
||||
SET FOREIGN_KEY_CHECKS=1;
|
||||
|
||||
597
db/jambones.sqs
597
db/jambones.sqs
File diff suppressed because one or more lines are too long
@@ -22,17 +22,25 @@ values ('3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', '38700987-c7a4-4685-a5bb-af378f9
|
||||
|
||||
-- create one service provider and one account
|
||||
insert into service_providers (service_provider_sid, name, root_domain)
|
||||
values ('2708b1b3-2736-40ea-b502-c53d8396247f', 'default service provider', 'sip.jambonz.us');
|
||||
values ('2708b1b3-2736-40ea-b502-c53d8396247f', 'default service provider', 'sip.jambonz.cloud');
|
||||
|
||||
insert into accounts (account_sid, service_provider_sid, name, webhook_secret)
|
||||
values ('9351f46a-678c-43f5-b8a6-d4eb58d131af','2708b1b3-2736-40ea-b502-c53d8396247f', 'default account', 'wh_secret_cJqgtMDPzDhhnjmaJH6Mtk');
|
||||
|
||||
-- create account level api key
|
||||
insert into api_keys (api_key_sid, token, service_provider_sid)
|
||||
values ('3f35518f-5a0d-4c2e-90a5-2407bb3b36fa', '38700987-c7a4-4685-a5bb-af378f9734da', '9351f46a-678c-43f5-b8a6-d4eb58d131af');
|
||||
|
||||
-- create SP level api key
|
||||
insert into api_keys (api_key_sid, token, account_sid)
|
||||
values ('3f35518f-5a0d-4c2e-90a5-2407bb3b36fs', '38700987-c7a4-4685-a5bb-af378f9734ds', '2708b1b3-2736-40ea-b502-c53d8396247f');
|
||||
|
||||
-- create two applications
|
||||
insert into webhooks(webhook_sid, url, method)
|
||||
values
|
||||
('84e3db00-b172-4e46-b54b-a503fdb19e4a', 'https://public-apps.jambonz.us/call-status', 'POST'),
|
||||
('d31568d0-b193-4a05-8ff6-778369bc6efe', 'https://public-apps.jambonz.us/hello-world', 'POST'),
|
||||
('81844b05-714d-4295-8bf3-3b0640a4bf02', 'https://public-apps.jambonz.us/dial-time', 'POST');
|
||||
('84e3db00-b172-4e46-b54b-a503fdb19e4a', 'https://public-apps.jambonz.cloud/call-status', 'POST'),
|
||||
('d31568d0-b193-4a05-8ff6-778369bc6efe', 'https://public-apps.jambonz.cloud/hello-world', 'POST'),
|
||||
('81844b05-714d-4295-8bf3-3b0640a4bf02', 'https://public-apps.jambonz.cloud/dial-time', 'POST');
|
||||
|
||||
insert into applications (application_sid, account_sid, name, call_hook_sid, call_status_hook_sid, speech_synthesis_vendor, speech_synthesis_language, speech_synthesis_voice, speech_recognizer_vendor, speech_recognizer_language)
|
||||
VALUES
|
||||
@@ -79,6 +87,7 @@ VALUES
|
||||
('81a0c8cb-a33e-42da-8f20-99083da6f02f', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.252.254.64', 30, 5060, 1, 0),
|
||||
('eeeef07a-46b8-4ffe-a4f2-04eb32ca889e', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.169.127.128', 30, 5060, 1, 0),
|
||||
('fbb6c194-4b68-4dff-9b42-52412be1c39e', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '177.71.206.192', 30, 5060, 1, 0),
|
||||
('973e7824-0cf3-4645-88e4-d2460ddb8577', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '168.86.128.0', 18, 5060, 1, 0),
|
||||
('3ed1dd12-e1a7-44ff-811a-3cc5dc13dc72', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '<your-domain>.pstn.twilio.com', 32, 5060, 0, 1);
|
||||
|
||||
-- voxbone gateways
|
||||
|
||||
@@ -24,10 +24,9 @@ values ('09e92f3c-9d73-4303-b63f-3668574862ce', '1cf2f4f4-64c4-4249-9a3e-5bb4cb5
|
||||
-- create two applications
|
||||
insert into webhooks(webhook_sid, url, method)
|
||||
values
|
||||
('84e3db00-b172-4e46-b54b-a503fdb19e4a', 'https://public-apps.jambonz.us/call-status', 'POST'),
|
||||
('d31568d0-b193-4a05-8ff6-778369bc6efe', 'https://public-apps.jambonz.us/hello-world', 'POST'),
|
||||
('81844b05-714d-4295-8bf3-3b0640a4bf02', 'https://public-apps.jambonz.us/dial-time', 'POST');
|
||||
|
||||
('84e3db00-b172-4e46-b54b-a503fdb19e4a', 'https://public-apps.jambonz.cloud/call-status', 'POST'),
|
||||
('d31568d0-b193-4a05-8ff6-778369bc6efe', 'https://public-apps.jambonz.cloud/hello-world', 'POST'),
|
||||
('81844b05-714d-4295-8bf3-3b0640a4bf02', 'https://public-apps.jambonz.cloud/dial-time', 'POST');
|
||||
insert into applications (application_sid, account_sid, name, call_hook_sid, call_status_hook_sid, speech_synthesis_vendor, speech_synthesis_language, speech_synthesis_voice, speech_recognizer_vendor, speech_recognizer_language)
|
||||
VALUES
|
||||
('7087fe50-8acb-4f3b-b820-97b573723aab', '9351f46a-678c-43f5-b8a6-d4eb58d131af', 'hello world', 'd31568d0-b193-4a05-8ff6-778369bc6efe', '84e3db00-b172-4e46-b54b-a503fdb19e4a', 'google', 'en-US', 'en-US-Wavenet-C', 'google', 'en-US'),
|
||||
@@ -68,14 +67,12 @@ VALUES
|
||||
('d2ccfcb1-9198-4fe9-a0ca-6e49395837c4', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.172.60.0', 30, 5060, 1, 0),
|
||||
('6b1d0032-4430-41f1-87c6-f22233d394ef', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.244.51.0', 30, 5060, 1, 0),
|
||||
('0de40217-8bd5-4aa8-a9fd-1994282953c6', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.171.127.192', 30, 5060, 1, 0),
|
||||
('48b108e3-1ce7-4f18-a4cb-e41e63688bdf', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.171.127.193', 30, 5060, 1, 0),
|
||||
('d9131a69-fe44-4c2a-ba82-4adc81f628dd', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.171.127.194', 30, 5060, 1, 0),
|
||||
('34a6a311-4bd6-49ca-aa77-edd3cb92c6e1', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.171.127.195', 30, 5060, 1, 0),
|
||||
('37bc0b20-b53c-4c31-95a6-f82b1c3713e3', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '35.156.191.128', 30, 5060, 1, 0),
|
||||
('39791f4e-b612-4882-a37e-e92711a39f3f', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.65.63.192', 30, 5060, 1, 0),
|
||||
('81a0c8cb-a33e-42da-8f20-99083da6f02f', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.252.254.64', 30, 5060, 1, 0),
|
||||
('eeeef07a-46b8-4ffe-a4f2-04eb32ca889e', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.169.127.128', 30, 5060, 1, 0),
|
||||
('fbb6c194-4b68-4dff-9b42-52412be1c39e', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '177.71.206.192', 30, 5060, 1, 0),
|
||||
('973e7824-0cf3-4645-88e4-d2460ddb8577', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '168.86.128.0', 18, 5060, 1, 0),
|
||||
('3ed1dd12-e1a7-44ff-811a-3cc5dc13dc72', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '<your-domain>.pstn.twilio.com', 32, 5060, 0, 1);
|
||||
|
||||
-- voxbone gateways
|
||||
|
||||
@@ -53,6 +53,7 @@ VALUES
|
||||
('81a0c8cb-a33e-42da-8f20-99083da6f02f', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.252.254.64', 30, 5060, 1, 0),
|
||||
('eeeef07a-46b8-4ffe-a4f2-04eb32ca889e', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.169.127.128', 30, 5060, 1, 0),
|
||||
('fbb6c194-4b68-4dff-9b42-52412be1c39e', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '177.71.206.192', 30, 5060, 1, 0),
|
||||
('973e7824-0cf3-4645-88e4-d2460ddb8577', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '168.86.128.0', 18, 5060, 1, 0),
|
||||
('3ed1dd12-e1a7-44ff-811a-3cc5dc13dc72', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '<your-domain>.pstn.twilio.com', 32, 5060, 0, 1);
|
||||
|
||||
-- voxbone gateways
|
||||
|
||||
@@ -87,6 +87,115 @@ const sql = {
|
||||
'ALTER TABLE user_permissions ADD FOREIGN KEY user_sid_idxfk (user_sid) REFERENCES users (user_sid) ON DELETE CASCADE',
|
||||
'ALTER TABLE user_permissions ADD FOREIGN KEY permission_sid_idxfk (permission_sid) REFERENCES permissions (permission_sid)',
|
||||
'ALTER TABLE `users` ADD COLUMN `is_active` BOOLEAN NOT NULL default true',
|
||||
],
|
||||
'8003': [
|
||||
'SET FOREIGN_KEY_CHECKS=0',
|
||||
'ALTER TABLE `voip_carriers` ADD COLUMN `register_status` VARCHAR(4096)',
|
||||
'ALTER TABLE `sbc_addresses` ADD COLUMN `last_updated` DATETIME',
|
||||
'ALTER TABLE `sbc_addresses` ADD COLUMN `tls_port` INTEGER',
|
||||
'ALTER TABLE `sbc_addresses` ADD COLUMN `wss_port` INTEGER',
|
||||
`CREATE TABLE system_information
|
||||
(
|
||||
domain_name VARCHAR(255),
|
||||
sip_domain_name VARCHAR(255),
|
||||
monitoring_domain_name VARCHAR(255)
|
||||
)`,
|
||||
'DROP TABLE IF EXISTS `lcr_routes`',
|
||||
'DROP TABLE IF EXISTS `lcr_carrier_set_entry`',
|
||||
`CREATE TABLE lcr_routes
|
||||
(
|
||||
lcr_route_sid CHAR(36),
|
||||
lcr_sid CHAR(36) NOT NULL,
|
||||
regex VARCHAR(32) NOT NULL COMMENT 'regex-based pattern match against dialed number, used for LCR routing of PSTN calls',
|
||||
description VARCHAR(1024),
|
||||
priority INTEGER NOT NULL COMMENT 'lower priority routes are attempted first',
|
||||
PRIMARY KEY (lcr_route_sid)
|
||||
)`,
|
||||
`CREATE TABLE lcr
|
||||
(
|
||||
lcr_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
name VARCHAR(64) COMMENT 'User-assigned name for this LCR table',
|
||||
is_active BOOLEAN NOT NULL DEFAULT 1,
|
||||
default_carrier_set_entry_sid CHAR(36) COMMENT 'default carrier/route to use when no digit match based results are found.',
|
||||
service_provider_sid CHAR(36),
|
||||
account_sid CHAR(36),
|
||||
PRIMARY KEY (lcr_sid)
|
||||
)`,
|
||||
`CREATE TABLE lcr_carrier_set_entry
|
||||
(
|
||||
lcr_carrier_set_entry_sid CHAR(36),
|
||||
workload INTEGER NOT NULL DEFAULT 1 COMMENT 'represents a proportion of traffic to send through the associated carrier; can be used for load balancing traffic across carriers with a common priority for a destination',
|
||||
lcr_route_sid CHAR(36) NOT NULL,
|
||||
voip_carrier_sid CHAR(36) NOT NULL,
|
||||
priority INTEGER NOT NULL DEFAULT 0 COMMENT 'lower priority carriers are attempted first',
|
||||
PRIMARY KEY (lcr_carrier_set_entry_sid)
|
||||
)`,
|
||||
'CREATE INDEX lcr_sid_idx ON lcr_routes (lcr_sid)',
|
||||
'ALTER TABLE lcr_routes ADD FOREIGN KEY lcr_sid_idxfk (lcr_sid) REFERENCES lcr (lcr_sid)',
|
||||
'CREATE INDEX lcr_sid_idx ON lcr (lcr_sid)',
|
||||
'ALTER TABLE lcr ADD FOREIGN KEY default_carrier_set_entry_sid_idxfk (default_carrier_set_entry_sid) REFERENCES lcr_carrier_set_entry (lcr_carrier_set_entry_sid)',
|
||||
'CREATE INDEX service_provider_sid_idx ON lcr (service_provider_sid)',
|
||||
'CREATE INDEX account_sid_idx ON lcr (account_sid)',
|
||||
'ALTER TABLE lcr_carrier_set_entry ADD FOREIGN KEY lcr_route_sid_idxfk (lcr_route_sid) REFERENCES lcr_routes (lcr_route_sid)',
|
||||
'ALTER TABLE lcr_carrier_set_entry ADD FOREIGN KEY voip_carrier_sid_idxfk_3 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid)',
|
||||
'SET FOREIGN_KEY_CHECKS=1',
|
||||
],
|
||||
'8004': [
|
||||
'alter table accounts add column record_all_calls BOOLEAN NOT NULL DEFAULT false',
|
||||
'alter table accounts add column bucket_credential VARCHAR(8192)',
|
||||
'alter table accounts add column record_format VARCHAR(16) NOT NULL DEFAULT \'mp3\'',
|
||||
'alter table applications add column record_all_calls BOOLEAN NOT NULL DEFAULT false',
|
||||
'alter table phone_numbers DROP INDEX number',
|
||||
'create unique index phone_numbers_unique_idx_voip_carrier_number ON phone_numbers (number,voip_carrier_sid)',
|
||||
`CREATE TABLE clients
|
||||
(
|
||||
client_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
account_sid CHAR(36) NOT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT 1,
|
||||
username VARCHAR(64),
|
||||
password VARCHAR(1024),
|
||||
PRIMARY KEY (client_sid)
|
||||
)`,
|
||||
'CREATE INDEX client_sid_idx ON clients (client_sid)',
|
||||
'ALTER TABLE clients ADD CONSTRAINT account_sid_idxfk_13 FOREIGN KEY account_sid_idxfk_13 (account_sid) REFERENCES accounts (account_sid)',
|
||||
'ALTER TABLE sip_gateways ADD COLUMN protocol ENUM(\'udp\',\'tcp\',\'tls\', \'tls/srtp\') DEFAULT \'udp\''
|
||||
],
|
||||
'8005': [
|
||||
'DROP INDEX speech_credentials_idx_1 ON speech_credentials',
|
||||
'ALTER TABLE speech_credentials ADD COLUMN label VARCHAR(64)',
|
||||
'ALTER TABLE applications ADD COLUMN speech_synthesis_label VARCHAR(64)',
|
||||
'ALTER TABLE applications ADD COLUMN speech_recognizer_label VARCHAR(64)',
|
||||
'ALTER TABLE applications ADD COLUMN use_for_fallback_speech BOOLEAN DEFAULT false',
|
||||
'ALTER TABLE applications ADD COLUMN fallback_speech_synthesis_vendor VARCHAR(64)',
|
||||
'ALTER TABLE applications ADD COLUMN fallback_speech_synthesis_language VARCHAR(12)',
|
||||
'ALTER TABLE applications ADD COLUMN fallback_speech_synthesis_voice VARCHAR(64)',
|
||||
'ALTER TABLE applications ADD COLUMN fallback_speech_synthesis_label VARCHAR(64)',
|
||||
'ALTER TABLE applications ADD COLUMN fallback_speech_recognizer_vendor VARCHAR(64)',
|
||||
'ALTER TABLE applications ADD COLUMN fallback_speech_recognizer_language VARCHAR(64)',
|
||||
'ALTER TABLE applications ADD COLUMN fallback_speech_recognizer_label VARCHAR(64)',
|
||||
'ALTER TABLE sip_gateways ADD COLUMN pad_crypto BOOLEAN NOT NULL DEFAULT 0',
|
||||
'ALTER TABLE sip_gateways MODIFY port INTEGER',
|
||||
`CREATE TABLE google_custom_voices
|
||||
(
|
||||
google_custom_voice_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
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,
|
||||
PRIMARY KEY (google_custom_voice_sid)
|
||||
)
|
||||
`,
|
||||
'CREATE INDEX google_custom_voice_sid_idx ON google_custom_voices (google_custom_voice_sid)',
|
||||
'CREATE INDEX speech_credential_sid_idx ON google_custom_voices (speech_credential_sid)',
|
||||
'ALTER TABLE google_custom_voices ADD FOREIGN KEY speech_credential_sid_idxfk (speech_credential_sid) REFERENCES speech_credentials (speech_credential_sid) ON DELETE CASCADE',
|
||||
'ALTER TABLE clients ADD COLUMN allow_direct_queue_calling BOOLEAN NOT NULL DEFAULT 1',
|
||||
'ALTER TABLE clients ADD COLUMN allow_direct_user_calling BOOLEAN NOT NULL DEFAULT 1',
|
||||
'ALTER TABLE clients ADD COLUMN allow_direct_app_calling BOOLEAN NOT NULL DEFAULT 1',
|
||||
],
|
||||
9000: [
|
||||
'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)',
|
||||
]
|
||||
};
|
||||
|
||||
@@ -116,6 +225,10 @@ const doIt = async() => {
|
||||
if (val < 7006) upgrades.push(...sql['7006']);
|
||||
if (val < 7007) upgrades.push(...sql['7007']);
|
||||
if (val < 8000) upgrades.push(...sql['8000']);
|
||||
if (val < 8003) upgrades.push(...sql['8003']);
|
||||
if (val < 8004) upgrades.push(...sql['8004']);
|
||||
if (val < 8005) upgrades.push(...sql['8005']);
|
||||
if (val < 9000) upgrades.push(...sql['9000']);
|
||||
|
||||
// perform all upgrades
|
||||
logger.info({upgrades}, 'applying schema upgrades..');
|
||||
|
||||
@@ -2,7 +2,7 @@ SET FOREIGN_KEY_CHECKS=0;
|
||||
|
||||
-- create one service provider
|
||||
insert into service_providers (service_provider_sid, name, description, root_domain)
|
||||
values ('2708b1b3-2736-40ea-b502-c53d8396247f', 'jambonz.us', 'jambonz.us service provider', 'sip.yakeeda.com');
|
||||
values ('2708b1b3-2736-40ea-b502-c53d8396247f', 'jambonz.cloud', 'jambonz.cloud service provider', 'sip.yakeeda.com');
|
||||
insert into api_keys (api_key_sid, token)
|
||||
values ('3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', '38700987-c7a4-4685-a5bb-af378f9734de');
|
||||
|
||||
@@ -19,8 +19,8 @@ insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, port, inbound
|
||||
values ('46b727eb-c7dc-44fa-b063-96e48d408e4a', '5145b436-2f38-4029-8d4c-fd8c67831c7a', '3.3.3.3', 5060, 1, 1, 1);
|
||||
|
||||
-- create the test application and test phone number
|
||||
insert into webhooks (webhook_sid, url, method) values ('d9c205c6-a129-443e-a9c0-d1bb437d4bb7', 'https://flows.jambonz.us/testCall', 'POST');
|
||||
insert into webhooks (webhook_sid, url, method) values ('6ac36aeb-6bd0-428a-80a1-aed95640a296', 'https://flows.jambonz.us/callStatus', 'POST');
|
||||
insert into webhooks (webhook_sid, url, method) values ('d9c205c6-a129-443e-a9c0-d1bb437d4bb7', 'https://flows.jambonz.cloud/testCall', 'POST');
|
||||
insert into webhooks (webhook_sid, url, method) values ('6ac36aeb-6bd0-428a-80a1-aed95640a296', 'https://flows.jambonz.cloud/callStatus', 'POST');
|
||||
insert into applications (application_sid, name, service_provider_sid, call_hook_sid, call_status_hook_sid,
|
||||
speech_synthesis_vendor, speech_synthesis_language, speech_synthesis_voice, speech_recognizer_vendor, speech_recognizer_language)
|
||||
values ('7a489343-02ed-471e-8df0-fc5e1b98ce8f', 'Test application', '2708b1b3-2736-40ea-b502-c53d8396247f',
|
||||
|
||||
@@ -1,28 +1,31 @@
|
||||
const logger = require('../logger');
|
||||
|
||||
const {
|
||||
client,
|
||||
retrieveCall,
|
||||
deleteCall,
|
||||
listCalls,
|
||||
listSortedSets,
|
||||
purgeCalls,
|
||||
retrieveSet,
|
||||
addKey,
|
||||
retrieveKey,
|
||||
deleteKey,
|
||||
incrKey,
|
||||
client: redisClient,
|
||||
} = require('@jambonz/realtimedb-helpers')({
|
||||
host: process.env.JAMBONES_REDIS_HOST || 'localhost',
|
||||
port: process.env.JAMBONES_REDIS_PORT || 6379
|
||||
}, logger);
|
||||
} = require('@jambonz/realtimedb-helpers')({}, logger);
|
||||
|
||||
module.exports = {
|
||||
client,
|
||||
retrieveCall,
|
||||
deleteCall,
|
||||
listCalls,
|
||||
listSortedSets,
|
||||
purgeCalls,
|
||||
retrieveSet,
|
||||
addKey,
|
||||
retrieveKey,
|
||||
deleteKey,
|
||||
redisClient,
|
||||
incrKey
|
||||
};
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
const opts = Object.assign({
|
||||
timestamp: () => {
|
||||
return `, "time": "${new Date().toISOString()}"`;
|
||||
}
|
||||
}, {
|
||||
const opts = {
|
||||
level: process.env.JAMBONES_LOGLEVEL || 'info'
|
||||
});
|
||||
|
||||
const logger = require('pino')(opts);
|
||||
};
|
||||
const pino = require('pino');
|
||||
const logger = pino(opts, pino.destination(1, {sync: false}));
|
||||
|
||||
module.exports = logger;
|
||||
|
||||
32
lib/middleware.js
Normal file
32
lib/middleware.js
Normal file
@@ -0,0 +1,32 @@
|
||||
const logger = require('./logger');
|
||||
|
||||
function delayLoginMiddleware(req, res, next) {
|
||||
if (req.path.includes('/login') || req.path.includes('/signin')) {
|
||||
const min = 200;
|
||||
const max = 1000;
|
||||
/* Random delay between 200 - 1000ms */
|
||||
const sendStatusDelay = Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
|
||||
/* the res.json take longer, we decrease the max delay slightly to 0-800ms */
|
||||
const jsonDelay = Math.floor(Math.random() * 800);
|
||||
logger.debug(`delayLoginMiddleware: sendStatus ${sendStatusDelay} - json ${jsonDelay}`);
|
||||
const sendStatus = res.sendStatus;
|
||||
const json = res.json;
|
||||
|
||||
res.sendStatus = function(status) {
|
||||
setTimeout(() => {
|
||||
sendStatus.call(res, status);
|
||||
}, sendStatusDelay);
|
||||
};
|
||||
res.json = function(body) {
|
||||
setTimeout(() => {
|
||||
json.call(res, body);
|
||||
}, jsonDelay);
|
||||
};
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
delayLoginMiddleware
|
||||
};
|
||||
@@ -4,7 +4,7 @@ const {getMysqlConnection} = require('../db');
|
||||
const {promisePool} = require('../db');
|
||||
const { v4: uuid } = require('uuid');
|
||||
|
||||
const {encrypt} = require('../utils/encrypt-decrypt');
|
||||
const {encrypt, decrypt} = require('../utils/encrypt-decrypt');
|
||||
|
||||
const retrieveSql = `SELECT * from accounts acc
|
||||
LEFT JOIN webhooks AS rh
|
||||
@@ -34,7 +34,7 @@ AND effective_end_date IS NULL
|
||||
AND pending=0`;
|
||||
|
||||
const updatePaymentInfoSql = `UPDATE account_subscriptions
|
||||
SET last4 = ?, exp_month = ?, exp_year = ?, card_type = ?
|
||||
SET last4 = ?, stripe_payment_method_id=?, exp_month = ?, exp_year = ?, card_type = ?
|
||||
WHERE account_sid = ?
|
||||
AND effective_end_date IS NULL`;
|
||||
|
||||
@@ -55,6 +55,13 @@ WHERE account_sid = ?
|
||||
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));
|
||||
}
|
||||
};
|
||||
|
||||
function transmogrifyResults(results) {
|
||||
return results.map((row) => {
|
||||
const obj = row.acc;
|
||||
@@ -75,6 +82,8 @@ function transmogrifyResults(results) {
|
||||
else obj.queue_event_hook = null;
|
||||
delete obj.queue_event_hook_sid;
|
||||
|
||||
extractBucketCredential(obj);
|
||||
|
||||
return obj;
|
||||
});
|
||||
}
|
||||
@@ -190,17 +199,18 @@ class Account extends Model {
|
||||
debug(r3, 'Account.activateSubscription - replaced old subscription');
|
||||
|
||||
/* update account.plan to paid, if it isnt already */
|
||||
/* update account.is_active to 1, if account is deactivated */
|
||||
await promisePool.execute(
|
||||
'UPDATE accounts SET plan_type = \'paid\' WHERE account_sid = ?',
|
||||
'UPDATE accounts SET plan_type = \'paid\', is_active = 1 WHERE account_sid = ?',
|
||||
[account_sid]);
|
||||
return true;
|
||||
}
|
||||
|
||||
static async updatePaymentInfo(logger, account_sid, pm) {
|
||||
const {card} = pm;
|
||||
const {id, card} = pm;
|
||||
const last4_encrypted = encrypt(card.last4);
|
||||
await promisePool.execute(updatePaymentInfoSql,
|
||||
[last4_encrypted, card.exp_month, card.exp_year, card.brand, account_sid]);
|
||||
[last4_encrypted, id, card.exp_month, card.exp_year, card.brand, account_sid]);
|
||||
}
|
||||
|
||||
static async provisionPendingSubscription(logger, account_sid, products, payment_method, subscription_id) {
|
||||
@@ -238,7 +248,6 @@ class Account extends Model {
|
||||
}));
|
||||
return account_subscription_sid;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Account.table = 'accounts';
|
||||
@@ -318,6 +327,18 @@ Account.fields = [
|
||||
name: 'siprec_hook_sid',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
name: 'record_all_calls',
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
name: 'record_format',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'bucket_credential',
|
||||
type: 'string'
|
||||
}
|
||||
];
|
||||
|
||||
module.exports = Account;
|
||||
|
||||
@@ -120,6 +120,10 @@ Application.fields = [
|
||||
{
|
||||
name: 'messaging_hook_sid',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
name: 'record_all_calls',
|
||||
type: 'number',
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
70
lib/models/client.js
Normal file
70
lib/models/client.js
Normal file
@@ -0,0 +1,70 @@
|
||||
const Model = require('./model');
|
||||
const {promisePool} = require('../db');
|
||||
|
||||
class Client extends Model {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
static async retrieveAllByAccountSid(account_sid) {
|
||||
const sql = `SELECT * FROM ${this.table} WHERE account_sid = ?`;
|
||||
const [rows] = await promisePool.query(sql, account_sid);
|
||||
return rows;
|
||||
}
|
||||
|
||||
static async retrieveAllByServiceProviderSid(service_provider_sid) {
|
||||
const sql = `SELECT c.client_sid, c.account_sid, c.is_active, c.username, c.hashed_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
|
||||
WHERE sp.service_provider_sid = ?`;
|
||||
const [rows] = await promisePool.query(sql, service_provider_sid);
|
||||
return rows;
|
||||
}
|
||||
|
||||
static async retrieveByAccountSidAndUserName(account_sid, username) {
|
||||
const sql = `SELECT * FROM ${this.table} WHERE account_sid = ? AND username = ?`;
|
||||
const [rows] = await promisePool.query(sql, [account_sid, username]);
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
|
||||
Client.table = 'clients';
|
||||
Client.fields = [
|
||||
{
|
||||
name: 'client_sid',
|
||||
type: 'string',
|
||||
primaryKey: true
|
||||
},
|
||||
{
|
||||
name: 'account_sid',
|
||||
type: 'string',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'is_active',
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
name: 'username',
|
||||
type: 'string',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'password',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'allow_direct_app_calling',
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
name: 'allow_direct_queue_calling',
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
name: 'allow_direct_user_calling',
|
||||
type: 'number'
|
||||
}
|
||||
];
|
||||
|
||||
module.exports = Client;
|
||||
61
lib/models/google-custom-voice.js
Normal file
61
lib/models/google-custom-voice.js
Normal file
@@ -0,0 +1,61 @@
|
||||
const Model = require('./model');
|
||||
const {promisePool} = require('../db');
|
||||
|
||||
class GoogleCustomVoice extends Model {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
static async retrieveAllBySpeechCredentialSid(speech_credential_sid) {
|
||||
const sql = `SELECT * FROM ${this.table} WHERE speech_credential_sid = ?`;
|
||||
const [rows] = await promisePool.query(sql, speech_credential_sid);
|
||||
return rows;
|
||||
}
|
||||
|
||||
static async deleteAllBySpeechCredentialSid(speech_credential_sid) {
|
||||
const sql = `DELETE FROM ${this.table} WHERE speech_credential_sid = ?`;
|
||||
const [rows] = await promisePool.query(sql, speech_credential_sid);
|
||||
return rows;
|
||||
}
|
||||
|
||||
static async retrieveAllByLabel(service_provider_sid, account_sid, label) {
|
||||
let sql;
|
||||
if (account_sid) {
|
||||
sql = `SELECT gcv.* FROM ${this.table} gcv
|
||||
LEFT JOIN speech_credentials sc ON gcv.speech_credential_sid = sc.speech_credential_sid
|
||||
WHERE sc.account_sid = ? OR (sc.account_sid is NULL && sc.service_provider_sid = ?)
|
||||
${label ? 'AND label = ?' : 'AND label is NULL'}`;
|
||||
} else {
|
||||
sql = `SELECT gcv.* FROM ${this.table} gcv
|
||||
LEFT JOIN speech_credentials sc ON gcv.speech_credential_sid = sc.speech_credential_sid
|
||||
WHERE sc.service_provider_sid = ? ${label ? 'AND label = ?' : 'AND label is NULL'}`;
|
||||
}
|
||||
const [rows] = await promisePool.query(sql, [...(account_sid ?
|
||||
[account_sid, service_provider_sid] : [service_provider_sid]), label]);
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
GoogleCustomVoice.table = 'google_custom_voices';
|
||||
GoogleCustomVoice.fields = [
|
||||
{
|
||||
name: 'google_custom_voice_sid',
|
||||
type: 'string',
|
||||
primaryKey: true
|
||||
},
|
||||
{
|
||||
name: 'model',
|
||||
type: 'string',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'reported_usage',
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
required: true
|
||||
}
|
||||
];
|
||||
|
||||
module.exports = GoogleCustomVoice;
|
||||
47
lib/models/lcr-carrier-set-entry.js
Normal file
47
lib/models/lcr-carrier-set-entry.js
Normal file
@@ -0,0 +1,47 @@
|
||||
const Model = require('./model');
|
||||
const {promisePool} = require('../db');
|
||||
|
||||
class LcrCarrierSetEntry extends Model {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
static async retrieveAllByLcrRouteSid(sid) {
|
||||
const sql = `SELECT * FROM ${this.table} WHERE lcr_route_sid = ? ORDER BY priority`;
|
||||
const [rows] = await promisePool.query(sql, sid);
|
||||
return rows;
|
||||
}
|
||||
|
||||
static async deleteByLcrRouteSid(sid) {
|
||||
const sql = `DELETE FROM ${this.table} WHERE lcr_route_sid = ?`;
|
||||
const [rows] = await promisePool.query(sql, sid);
|
||||
return rows.affectedRows;
|
||||
}
|
||||
}
|
||||
|
||||
LcrCarrierSetEntry.table = 'lcr_carrier_set_entry';
|
||||
LcrCarrierSetEntry.fields = [
|
||||
{
|
||||
name: 'lcr_carrier_set_entry_sid',
|
||||
type: 'string',
|
||||
primaryKey: true
|
||||
},
|
||||
{
|
||||
name: 'workload',
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
name: 'lcr_route_sid',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'voip_carrier_sid',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'priority',
|
||||
type: 'number'
|
||||
}
|
||||
];
|
||||
|
||||
module.exports = LcrCarrierSetEntry;
|
||||
54
lib/models/lcr-route.js
Normal file
54
lib/models/lcr-route.js
Normal file
@@ -0,0 +1,54 @@
|
||||
const Model = require('./model');
|
||||
const {promisePool} = require('../db');
|
||||
|
||||
|
||||
class LcrRoutes extends Model {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
static async retrieveAllByLcrSid(sid) {
|
||||
const sql = `SELECT * FROM ${this.table} WHERE lcr_sid = ? ORDER BY priority`;
|
||||
const [rows] = await promisePool.query(sql, sid);
|
||||
return rows;
|
||||
}
|
||||
|
||||
static async deleteByLcrSid(sid) {
|
||||
const sql = `DELETE FROM ${this.table} WHERE lcr_sid = ?`;
|
||||
const [rows] = await promisePool.query(sql, sid);
|
||||
return rows.affectedRows;
|
||||
}
|
||||
|
||||
static async countAllByLcrSid(sid) {
|
||||
const sql = `SELECT COUNT(*) AS count FROM ${this.table} WHERE lcr_sid = ?`;
|
||||
const [rows] = await promisePool.query(sql, sid);
|
||||
return rows.length ? rows[0].count : 0;
|
||||
}
|
||||
}
|
||||
|
||||
LcrRoutes.table = 'lcr_routes';
|
||||
LcrRoutes.fields = [
|
||||
{
|
||||
name: 'lcr_route_sid',
|
||||
type: 'string',
|
||||
primaryKey: true
|
||||
},
|
||||
{
|
||||
name: 'lcr_sid',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'regex',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'priority',
|
||||
type: 'number'
|
||||
}
|
||||
];
|
||||
|
||||
module.exports = LcrRoutes;
|
||||
54
lib/models/lcr.js
Normal file
54
lib/models/lcr.js
Normal file
@@ -0,0 +1,54 @@
|
||||
const Model = require('./model');
|
||||
const {promisePool} = require('../db');
|
||||
|
||||
class Lcr extends Model {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
static async retrieveAllByAccountSid(account_sid) {
|
||||
const sql = `SELECT * FROM ${this.table} WHERE account_sid = ?`;
|
||||
const [rows] = await promisePool.query(sql, account_sid);
|
||||
return rows;
|
||||
}
|
||||
|
||||
static async retrieveAllByServiceProviderSid(sid) {
|
||||
const sql = `SELECT * FROM ${this.table} WHERE service_provider_sid = ?`;
|
||||
const [rows] = await promisePool.query(sql, sid);
|
||||
return rows;
|
||||
}
|
||||
|
||||
static async releaseDefaultEntry(sid) {
|
||||
const sql = `UPDATE ${this.table} SET default_carrier_set_entry_sid = null WHERE lcr_sid = ?`;
|
||||
const [rows] = await promisePool.query(sql, sid);
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
|
||||
Lcr.table = 'lcr';
|
||||
Lcr.fields = [
|
||||
{
|
||||
name: 'lcr_sid',
|
||||
type: 'string',
|
||||
primaryKey: true
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'account_sid',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'service_provider_sid',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'default_carrier_set_entry_sid',
|
||||
type: 'string'
|
||||
}
|
||||
];
|
||||
|
||||
module.exports = Lcr;
|
||||
@@ -1,6 +1,7 @@
|
||||
const Model = require('./model');
|
||||
const {promisePool} = require('../db');
|
||||
const sql = 'SELECT * from phone_numbers WHERE account_sid = ?';
|
||||
const sqlRetrieveAll = 'SELECT * from phone_numbers WHERE account_sid = ? ORDER BY number';
|
||||
const sqlRetrieveOne = 'SELECT * from phone_numbers WHERE phone_number_sid = ? AND account_sid = ? ORDER BY number';
|
||||
const sqlSP = `SELECT *
|
||||
FROM phone_numbers
|
||||
WHERE account_sid IN
|
||||
@@ -8,7 +9,7 @@ WHERE account_sid IN
|
||||
SELECT account_sid
|
||||
FROM accounts
|
||||
WHERE service_provider_sid = ?
|
||||
)`;
|
||||
) ORDER BY number`;
|
||||
|
||||
class PhoneNumber extends Model {
|
||||
constructor() {
|
||||
@@ -16,8 +17,8 @@ class PhoneNumber extends Model {
|
||||
}
|
||||
|
||||
static async retrieveAll(account_sid) {
|
||||
if (!account_sid) return super.retrieveAll();
|
||||
const [rows] = await promisePool.query(sql, account_sid);
|
||||
if (!account_sid) return await super.retrieveAll();
|
||||
const [rows] = await promisePool.query(sqlRetrieveAll, account_sid);
|
||||
return rows;
|
||||
}
|
||||
static async retrieveAllForSP(service_provider_sid) {
|
||||
@@ -30,7 +31,7 @@ class PhoneNumber extends Model {
|
||||
*/
|
||||
static async retrieve(sid, account_sid) {
|
||||
if (!account_sid) return super.retrieve(sid);
|
||||
const [rows] = await promisePool.query(`${sql} AND phone_number_sid = ?`, [account_sid, sid]);
|
||||
const [rows] = await promisePool.query(sqlRetrieveOne, [sid, account_sid]);
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +89,10 @@ ServiceProvider.fields = [
|
||||
{
|
||||
name: 'ms_teams_fqdn',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
name: 'lcr_sid',
|
||||
type: 'string'
|
||||
}
|
||||
|
||||
];
|
||||
|
||||
@@ -51,6 +51,10 @@ SipGateway.fields = [
|
||||
name: 'is_active',
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
name: 'pad_crypto',
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
name: 'account_sid',
|
||||
type: 'string'
|
||||
@@ -58,6 +62,10 @@ SipGateway.fields = [
|
||||
{
|
||||
name: 'application_sid',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'protocol',
|
||||
type: 'string'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const Model = require('./model');
|
||||
const {promisePool} = require('../db');
|
||||
const retrieveSql = 'SELECT * from speech_credentials WHERE account_sid = ?';
|
||||
const retrieveSqlForSP = 'SELECT * from speech_credentials WHERE service_provider_sid = ?';
|
||||
const retrieveSqlForSP = 'SELECT * from speech_credentials WHERE service_provider_sid = ? and account_sid is null';
|
||||
|
||||
class SpeechCredential extends Model {
|
||||
constructor() {
|
||||
@@ -20,6 +20,18 @@ class SpeechCredential extends Model {
|
||||
return rows;
|
||||
}
|
||||
|
||||
static async getSpeechCredentialsByVendorAndLabel(service_provider_sid, account_sid, vendor, label) {
|
||||
let sql;
|
||||
if (account_sid) {
|
||||
sql = `SELECT * FROM speech_credentials WHERE account_sid = ? AND vendor = ? ${label ? 'AND label = ?' : ''}`;
|
||||
} else {
|
||||
sql = `SELECT * FROM speech_credentials WHERE service_provider_sid = ? AND vendor = ?
|
||||
${label ? 'AND label = ?' : ''}`;
|
||||
}
|
||||
const [rows] = await promisePool.query(sql, [account_sid ? account_sid : service_provider_sid, vendor, label]);
|
||||
return rows;
|
||||
}
|
||||
|
||||
static async disableStt(account_sid) {
|
||||
await promisePool.execute('UPDATE speech_credentials SET use_for_stt = 0 WHERE account_sid = ?', [account_sid]);
|
||||
}
|
||||
@@ -86,6 +98,10 @@ SpeechCredential.fields = [
|
||||
{
|
||||
name: 'last_tested',
|
||||
type: 'date'
|
||||
},
|
||||
{
|
||||
name: 'label',
|
||||
type: 'string'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
38
lib/models/system-information.js
Normal file
38
lib/models/system-information.js
Normal file
@@ -0,0 +1,38 @@
|
||||
const Model = require('./model');
|
||||
const { promisePool } = require('../db');
|
||||
class SystemInformation extends Model {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
static async add(body) {
|
||||
let [sysInfo] = await this.retrieveAll();
|
||||
if (sysInfo) {
|
||||
const sql = `UPDATE ${this.table} SET ?`;
|
||||
await promisePool.query(sql, body);
|
||||
} else {
|
||||
const sql = `INSERT INTO ${this.table} SET ?`;
|
||||
await promisePool.query(sql, body);
|
||||
}
|
||||
[sysInfo] = await this.retrieveAll();
|
||||
return sysInfo;
|
||||
}
|
||||
}
|
||||
|
||||
SystemInformation.table = 'system_information';
|
||||
SystemInformation.fields = [
|
||||
{
|
||||
name: 'domain_name',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
name: 'sip_domain_name',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
name: 'monitoring_domain_name',
|
||||
type: 'string',
|
||||
},
|
||||
];
|
||||
|
||||
module.exports = SystemInformation;
|
||||
@@ -11,10 +11,16 @@ class VoipCarrier extends Model {
|
||||
static async retrieveAll(account_sid) {
|
||||
if (!account_sid) return super.retrieveAll();
|
||||
const [rows] = await promisePool.query(retrieveSql, account_sid);
|
||||
if (rows) {
|
||||
rows.map((r) => r.register_status = JSON.parse(r.register_status || '{}'));
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
static async retrieveAllForSP(service_provider_sid) {
|
||||
const [rows] = await promisePool.query(retrieveSqlForSP, service_provider_sid);
|
||||
if (rows) {
|
||||
rows.map((r) => r.register_status = JSON.parse(r.register_status || '{}'));
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
@@ -55,6 +61,10 @@ VoipCarrier.fields = [
|
||||
name: 'requires_register',
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
name: 'register_use_tls',
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
name: 'register_username',
|
||||
type: 'string'
|
||||
@@ -122,6 +132,10 @@ VoipCarrier.fields = [
|
||||
{
|
||||
name: 'register_public_ip_in_contact',
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
name: 'register_status',
|
||||
type: 'string'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
71
lib/record/azure-storage.js
Normal file
71
lib/record/azure-storage.js
Normal file
@@ -0,0 +1,71 @@
|
||||
const { Writable } = require('stream');
|
||||
const { BlobServiceClient } = require('@azure/storage-blob');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const streamBuffers = require('stream-buffers');
|
||||
|
||||
class AzureStorageUploadStream extends Writable {
|
||||
constructor(logger, opts) {
|
||||
super(opts);
|
||||
const blobServiceClient = BlobServiceClient.fromConnectionString(opts.connection_string);
|
||||
this.blockBlobClient = blobServiceClient.getContainerClient(opts.bucketName).getBlockBlobClient(opts.Key);
|
||||
|
||||
this.metadata = opts.metadata;
|
||||
this.blocks = [];
|
||||
|
||||
this.bufferSize = 2 * 1024 * 1024; // Buffer size set to 2MB
|
||||
this.buffer = new streamBuffers.WritableStreamBuffer({
|
||||
initialSize: this.bufferSize,
|
||||
incrementAmount: this.bufferSize
|
||||
});
|
||||
}
|
||||
|
||||
async _write(chunk, encoding, callback) {
|
||||
this.buffer.write(chunk, encoding);
|
||||
|
||||
if (this.buffer.size() >= this.bufferSize) {
|
||||
const blockID = uuidv4().replace(/-/g, '');
|
||||
this.blocks.push(blockID);
|
||||
|
||||
try {
|
||||
const dataToWrite = this.buffer.getContents();
|
||||
await this.blockBlobClient.stageBlock(blockID, dataToWrite, dataToWrite.length);
|
||||
callback();
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
async _final(callback) {
|
||||
// Write any remaining data in buffer
|
||||
if (this.buffer.size() > 0) {
|
||||
const remainingData = this.buffer.getContents();
|
||||
const blockID = uuidv4().replace(/-/g, '');
|
||||
this.blocks.push(blockID);
|
||||
|
||||
try {
|
||||
await this.blockBlobClient.stageBlock(blockID, remainingData, remainingData.length);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await this.blockBlobClient.commitBlockList(this.blocks);
|
||||
// remove all null/undefined props
|
||||
const filteredObj = Object.entries(this.metadata).reduce((acc, [key, val]) => {
|
||||
if (val !== undefined && val !== null) acc[key] = val;
|
||||
return acc;
|
||||
}, {});
|
||||
await this.blockBlobClient.setMetadata(filteredObj);
|
||||
callback();
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AzureStorageUploadStream;
|
||||
61
lib/record/encoder.js
Normal file
61
lib/record/encoder.js
Normal file
@@ -0,0 +1,61 @@
|
||||
const { Transform } = require('stream');
|
||||
const lamejs = require('@jambonz/lamejs');
|
||||
|
||||
class PCMToMP3Encoder extends Transform {
|
||||
constructor(options, logger) {
|
||||
super(options);
|
||||
|
||||
const channels = options.channels || 1;
|
||||
const sampleRate = options.sampleRate || 8000;
|
||||
const bitRate = options.bitRate || 128;
|
||||
|
||||
this.encoder = new lamejs.Mp3Encoder(channels, sampleRate, bitRate);
|
||||
this.channels = channels;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
_transform(chunk, encoding, callback) {
|
||||
try {
|
||||
// Convert chunk buffer into Int16Array for lamejs
|
||||
const samples = new Int16Array(chunk.buffer, chunk.byteOffset, chunk.length / 2);
|
||||
|
||||
// Split input samples into left and right channel arrays if stereo
|
||||
let leftChannel, rightChannel;
|
||||
if (this.channels === 2) {
|
||||
leftChannel = new Int16Array(samples.length / 2);
|
||||
rightChannel = new Int16Array(samples.length / 2);
|
||||
|
||||
for (let i = 0; i < samples.length; i += 2) {
|
||||
leftChannel[i / 2] = samples[i];
|
||||
rightChannel[i / 2] = samples[i + 1];
|
||||
}
|
||||
} else {
|
||||
leftChannel = samples;
|
||||
}
|
||||
|
||||
// Encode the input data
|
||||
const mp3Data = this.encoder.encodeBuffer(leftChannel, rightChannel);
|
||||
|
||||
if (mp3Data.length > 0) {
|
||||
this.push(Buffer.from(mp3Data));
|
||||
}
|
||||
callback();
|
||||
} catch (err) {
|
||||
this.logger.error(
|
||||
{ err },
|
||||
'Error while mp3 transform');
|
||||
}
|
||||
}
|
||||
|
||||
_flush(callback) {
|
||||
// Finalize encoding and flush the internal buffers
|
||||
const mp3Data = this.encoder.flush();
|
||||
|
||||
if (mp3Data.length > 0) {
|
||||
this.push(Buffer.from(mp3Data));
|
||||
}
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PCMToMP3Encoder;
|
||||
62
lib/record/google-storage.js
Normal file
62
lib/record/google-storage.js
Normal file
@@ -0,0 +1,62 @@
|
||||
const { Storage } = require('@google-cloud/storage');
|
||||
const { Writable } = require('stream');
|
||||
const streamBuffers = require('stream-buffers');
|
||||
|
||||
class GoogleStorageUploadStream extends Writable {
|
||||
|
||||
constructor(logger, opts) {
|
||||
super(opts);
|
||||
this.logger = logger;
|
||||
this.metadata = opts.metadata;
|
||||
|
||||
const storage = new Storage(opts.bucketCredential);
|
||||
this.gcsFile = storage.bucket(opts.bucketName).file(opts.Key);
|
||||
this.writeStream = this.gcsFile.createWriteStream();
|
||||
|
||||
this.bufferSize = 2 * 1024 * 1024; // Buffer size set to 2MB
|
||||
this.buffer = new streamBuffers.WritableStreamBuffer({
|
||||
initialSize: this.bufferSize,
|
||||
incrementAmount: this.bufferSize
|
||||
});
|
||||
|
||||
this.writeStream.on('error', (err) => this.logger.error(err));
|
||||
this.writeStream.on('finish', () => {
|
||||
this.logger.info('Google storage Upload completed.');
|
||||
this._addMetadata();
|
||||
});
|
||||
}
|
||||
|
||||
_write(chunk, encoding, callback) {
|
||||
this.buffer.write(chunk, encoding);
|
||||
|
||||
// Write to GCS when buffer reaches desired size
|
||||
if (this.buffer.size() >= this.bufferSize) {
|
||||
const dataToWrite = this.buffer.getContents();
|
||||
this.writeStream.write(dataToWrite, callback);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
_final(callback) {
|
||||
// Write any remaining data in the buffer to GCS
|
||||
if (this.buffer.size() > 0) {
|
||||
const remainingData = this.buffer.getContents();
|
||||
this.writeStream.write(remainingData);
|
||||
}
|
||||
|
||||
this.writeStream.end();
|
||||
this.writeStream.once('finish', callback);
|
||||
}
|
||||
|
||||
async _addMetadata() {
|
||||
try {
|
||||
await this.gcsFile.setMetadata({metadata: this.metadata});
|
||||
this.logger.info('Google storage Upload and metadata setting completed.');
|
||||
} catch (err) {
|
||||
this.logger.error(err, 'Google storage An error occurred while setting metadata');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GoogleStorageUploadStream;
|
||||
6
lib/record/index.js
Normal file
6
lib/record/index.js
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
async function record(logger, socket) {
|
||||
return require('./upload')(logger, socket);
|
||||
}
|
||||
|
||||
module.exports = record;
|
||||
103
lib/record/s3-multipart-upload-stream.js
Normal file
103
lib/record/s3-multipart-upload-stream.js
Normal file
@@ -0,0 +1,103 @@
|
||||
const { Writable } = require('stream');
|
||||
const {
|
||||
S3Client,
|
||||
CreateMultipartUploadCommand,
|
||||
UploadPartCommand,
|
||||
CompleteMultipartUploadCommand,
|
||||
} = require('@aws-sdk/client-s3');
|
||||
|
||||
class S3MultipartUploadStream extends Writable {
|
||||
constructor(logger, opts) {
|
||||
super(opts);
|
||||
this.logger = logger;
|
||||
this.bucketName = opts.bucketName;
|
||||
this.objectKey = opts.Key;
|
||||
this.uploadId = null;
|
||||
this.partNumber = 1;
|
||||
this.multipartETags = [];
|
||||
this.buffer = Buffer.alloc(0);
|
||||
this.minPartSize = 5 * 1024 * 1024; // 5 MB
|
||||
this.s3 = new S3Client(opts.bucketCredential);
|
||||
this.metadata = opts.metadata;
|
||||
}
|
||||
|
||||
async _initMultipartUpload() {
|
||||
const command = new CreateMultipartUploadCommand({
|
||||
Bucket: this.bucketName,
|
||||
Key: this.objectKey,
|
||||
Metadata: this.metadata
|
||||
});
|
||||
const response = await this.s3.send(command);
|
||||
return response.UploadId;
|
||||
}
|
||||
|
||||
async _uploadBuffer() {
|
||||
const uploadPartCommand = new UploadPartCommand({
|
||||
Bucket: this.bucketName,
|
||||
Key: this.objectKey,
|
||||
PartNumber: this.partNumber,
|
||||
UploadId: this.uploadId,
|
||||
Body: this.buffer,
|
||||
});
|
||||
|
||||
const uploadPartResponse = await this.s3.send(uploadPartCommand);
|
||||
this.multipartETags.push({
|
||||
ETag: uploadPartResponse.ETag,
|
||||
PartNumber: this.partNumber,
|
||||
});
|
||||
this.partNumber += 1;
|
||||
}
|
||||
|
||||
async _write(chunk, encoding, callback) {
|
||||
try {
|
||||
if (!this.uploadId) {
|
||||
this.uploadId = await this._initMultipartUpload();
|
||||
}
|
||||
|
||||
this.buffer = Buffer.concat([this.buffer, chunk]);
|
||||
|
||||
if (this.buffer.length >= this.minPartSize) {
|
||||
await this._uploadBuffer();
|
||||
this.buffer = Buffer.alloc(0);
|
||||
}
|
||||
|
||||
callback(null);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
}
|
||||
|
||||
async _finalize(err) {
|
||||
try {
|
||||
if (this.buffer.length > 0) {
|
||||
await this._uploadBuffer();
|
||||
}
|
||||
|
||||
const completeMultipartUploadCommand = new CompleteMultipartUploadCommand({
|
||||
Bucket: this.bucketName,
|
||||
Key: this.objectKey,
|
||||
MultipartUpload: {
|
||||
Parts: this.multipartETags.sort((a, b) => a.PartNumber - b.PartNumber),
|
||||
},
|
||||
UploadId: this.uploadId,
|
||||
});
|
||||
|
||||
await this.s3.send(completeMultipartUploadCommand);
|
||||
this.logger.info('Finished upload to S3');
|
||||
} catch (error) {
|
||||
this.logger.error('Error completing multipart upload:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async _final(callback) {
|
||||
try {
|
||||
await this._finalize();
|
||||
callback(null);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = S3MultipartUploadStream;
|
||||
97
lib/record/upload.js
Normal file
97
lib/record/upload.js
Normal file
@@ -0,0 +1,97 @@
|
||||
const Account = require('../models/account');
|
||||
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;
|
||||
socket.on('message', async function(data, isBinary) {
|
||||
try {
|
||||
if (!isBinary && !socket._recvInitialMetadata) {
|
||||
socket._recvInitialMetadata = true;
|
||||
logger.debug(`initial metadata: ${data}`);
|
||||
const obj = JSON.parse(data.toString());
|
||||
logger.info({ obj }, 'received JSON message from jambonz');
|
||||
const { sampleRate, accountSid, callSid, direction, from, to,
|
||||
callId, applicationSid, originatingSipIp, originatingSipTrunkName } = obj;
|
||||
const account = await Account.retrieve(accountSid);
|
||||
if (account && account.length && account[0].bucket_credential) {
|
||||
const obj = account[0].bucket_credential;
|
||||
// add tags to metadata
|
||||
const metadata = {
|
||||
accountSid,
|
||||
callSid,
|
||||
direction,
|
||||
from,
|
||||
to,
|
||||
callId,
|
||||
applicationSid,
|
||||
originatingSipIp,
|
||||
originatingSipTrunkName,
|
||||
sampleRate: `${sampleRate}`
|
||||
};
|
||||
if (obj.tags && obj.tags.length) {
|
||||
obj.tags.forEach((tag) => {
|
||||
metadata[tag.Key] = tag.Value;
|
||||
});
|
||||
}
|
||||
// create S3 path
|
||||
const day = new Date();
|
||||
let key = `${day.getFullYear()}/${(day.getMonth() + 1).toString().padStart(2, '0')}`;
|
||||
key += `/${day.getDate().toString().padStart(2, '0')}/${callSid}.${account[0].record_format}`;
|
||||
|
||||
// Uploader
|
||||
const uploadStream = getUploader(key, metadata, obj, logger);
|
||||
if (!uploadStream) {
|
||||
logger.info('There is no available record uploader, close the socket.');
|
||||
socket.close();
|
||||
}
|
||||
|
||||
/**encoder */
|
||||
let encoder;
|
||||
if (account[0].record_format === 'wav') {
|
||||
encoder = new wav.Writer({ channels: 2, sampleRate, bitDepth: 16 });
|
||||
} else {
|
||||
// default is mp3
|
||||
encoder = new PCMToMP3Encoder({
|
||||
channels: 2,
|
||||
sampleRate: sampleRate,
|
||||
bitrate: 128
|
||||
}, logger);
|
||||
}
|
||||
|
||||
/* start streaming data */
|
||||
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();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error({ err, data }, 'error parsing message during connection');
|
||||
}
|
||||
});
|
||||
socket.on('error', function(err) {
|
||||
logger.error({ err }, 'record upload: error');
|
||||
});
|
||||
socket.on('close', (data) => {
|
||||
logger.info({ data }, 'record upload: close');
|
||||
});
|
||||
socket.on('end', function(err) {
|
||||
logger.error({ err }, 'record upload: socket closed from jambonz');
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = upload;
|
||||
58
lib/record/utils.js
Normal file
58
lib/record/utils.js
Normal file
@@ -0,0 +1,58 @@
|
||||
const AzureStorageUploadStream = require('./azure-storage');
|
||||
const GoogleStorageUploadStream = require('./google-storage');
|
||||
const S3MultipartUploadStream = require('./s3-multipart-upload-stream');
|
||||
|
||||
const getUploader = (key, metadata, bucket_credential, logger) => {
|
||||
const uploaderOpts = {
|
||||
bucketName: bucket_credential.name,
|
||||
Key: key,
|
||||
metadata
|
||||
};
|
||||
try {
|
||||
switch (bucket_credential.vendor) {
|
||||
case 'aws_s3':
|
||||
uploaderOpts.bucketCredential = {
|
||||
credentials: {
|
||||
accessKeyId: bucket_credential.access_key_id,
|
||||
secretAccessKey: bucket_credential.secret_access_key,
|
||||
},
|
||||
region: bucket_credential.region || 'us-east-1'
|
||||
};
|
||||
return new S3MultipartUploadStream(logger, uploaderOpts);
|
||||
case 's3_compatible':
|
||||
uploaderOpts.bucketCredential = {
|
||||
endpoint: bucket_credential.endpoint,
|
||||
credentials: {
|
||||
accessKeyId: bucket_credential.access_key_id,
|
||||
secretAccessKey: bucket_credential.secret_access_key,
|
||||
},
|
||||
region: 'us-east-1',
|
||||
forcePathStyle: true
|
||||
};
|
||||
return new S3MultipartUploadStream(logger, uploaderOpts);
|
||||
case 'google':
|
||||
const serviceKey = JSON.parse(bucket_credential.service_key);
|
||||
uploaderOpts.bucketCredential = {
|
||||
projectId: serviceKey.project_id,
|
||||
credentials: {
|
||||
client_email: serviceKey.client_email,
|
||||
private_key: serviceKey.private_key
|
||||
}
|
||||
};
|
||||
return new GoogleStorageUploadStream(logger, uploaderOpts);
|
||||
case 'azure':
|
||||
uploaderOpts.connection_string = bucket_credential.connection_string;
|
||||
return new AzureStorageUploadStream(logger, uploaderOpts);
|
||||
default:
|
||||
logger.error(`unknown bucket vendor: ${bucket_credential.vendor}`);
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(`Error creating uploader, vendor: ${bucket_credential.vendor}, reason: ${err.message}`);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getUploader
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
const router = require('express').Router();
|
||||
const assert = require('assert');
|
||||
const request = require('request');
|
||||
const {DbErrorBadRequest, DbErrorForbidden, DbErrorUnprocessableRequest} = require('../../utils/errors');
|
||||
const Account = require('../../models/account');
|
||||
@@ -22,6 +23,8 @@ const {
|
||||
} = require('./utils');
|
||||
const short = require('short-uuid');
|
||||
const VoipCarrier = require('../../models/voip-carrier');
|
||||
const { encrypt } = require('../../utils/encrypt-decrypt');
|
||||
const { testS3Storage, testGoogleStorage, testAzureStorage } = require('../../utils/storage-utils');
|
||||
const translator = short();
|
||||
|
||||
let idx = 0;
|
||||
@@ -38,21 +41,15 @@ const getFsUrl = async(logger, retrieveSet, setName) => {
|
||||
logger.info('No available feature servers to handle createCall API request');
|
||||
return ;
|
||||
}
|
||||
const ip = stripPort(fs[idx++ % fs.length]);
|
||||
logger.info({fs}, `feature servers available for createCall API request, selecting ${ip}`);
|
||||
return `http://${ip}:3000/v1/createCall`;
|
||||
const f = fs[idx++ % fs.length];
|
||||
logger.info({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');
|
||||
}
|
||||
};
|
||||
|
||||
const stripPort = (hostport) => {
|
||||
const arr = /^(.*):(.*)$/.exec(hostport);
|
||||
if (arr) return arr[1];
|
||||
return hostport;
|
||||
};
|
||||
|
||||
const validateUpdateForCarrier = async(req, account_sid) => {
|
||||
const validateRequest = async(req, account_sid) => {
|
||||
try {
|
||||
if (req.user.hasScope('admin')) {
|
||||
return;
|
||||
@@ -63,7 +60,7 @@ const validateUpdateForCarrier = async(req, account_sid) => {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new DbErrorForbidden('insufficient permissions to update account');
|
||||
throw new DbErrorForbidden('insufficient permissions');
|
||||
}
|
||||
|
||||
if (req.user.hasScope('service_provider')) {
|
||||
@@ -75,7 +72,7 @@ const validateUpdateForCarrier = async(req, account_sid) => {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new DbErrorForbidden('insufficient permissions to update account');
|
||||
throw new DbErrorForbidden('insufficient permissions');
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
@@ -89,10 +86,12 @@ router.use('/:sid/Charges', hasAccountPermissions, require('./charges'));
|
||||
router.use('/:sid/SipRealms', hasAccountPermissions, require('./sip-realm'));
|
||||
router.use('/:sid/PredefinedCarriers', hasAccountPermissions, require('./add-from-predefined-carrier'));
|
||||
router.use('/:sid/Limits', hasAccountPermissions, require('./limits'));
|
||||
router.use('/:sid/TtsCache', hasAccountPermissions, require('./tts-cache'));
|
||||
router.get('/:sid/Applications', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
const account_sid = parseAccountSid(req);
|
||||
await validateRequest(req, account_sid);
|
||||
const results = await Application.retrieveAll(null, account_sid);
|
||||
res.status(200).json(results);
|
||||
} catch (err) {
|
||||
@@ -103,6 +102,7 @@ router.get('/:sid/VoipCarriers', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
const account_sid = parseAccountSid(req);
|
||||
await validateRequest(req, account_sid);
|
||||
const results = await VoipCarrier.retrieveAll(account_sid);
|
||||
res.status(200).json(results);
|
||||
} catch (err) {
|
||||
@@ -115,7 +115,7 @@ router.put('/:sid/VoipCarriers/:voip_carrier_sid', async(req, res) => {
|
||||
try {
|
||||
const sid = parseVoipCarrierSid(req);
|
||||
const account_sid = parseAccountSid(req);
|
||||
await validateUpdateForCarrier(req, account_sid);
|
||||
await validateRequest(req, account_sid);
|
||||
|
||||
const rowsAffected = await VoipCarrier.update(sid, req.body);
|
||||
if (rowsAffected === 0) {
|
||||
@@ -133,6 +133,8 @@ router.post('/:sid/VoipCarriers', async(req, res) => {
|
||||
const payload = req.body;
|
||||
try {
|
||||
const account_sid = parseAccountSid(req);
|
||||
await validateRequest(req, account_sid);
|
||||
|
||||
logger.debug({payload}, 'POST /:sid/VoipCarriers');
|
||||
const uuid = await VoipCarrier.make({
|
||||
account_sid,
|
||||
@@ -144,6 +146,88 @@ router.post('/:sid/VoipCarriers', async(req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/:sid/RegisteredSipUsers', async(req, res) => {
|
||||
const {logger, registrar} = req.app.locals;
|
||||
try {
|
||||
const account_sid = parseAccountSid(req);
|
||||
await validateRequest(req, account_sid);
|
||||
const result = await Account.retrieve(account_sid);
|
||||
if (!result || result.length === 0) {
|
||||
throw new DbErrorBadRequest(`account not found for sid ${account_sid}`);
|
||||
}
|
||||
if (!result[0].sip_realm) {
|
||||
throw new DbErrorBadRequest('account does not have sip_realm configuration');
|
||||
}
|
||||
const users = await registrar.getRegisteredUsersForRealm(result[0].sip_realm);
|
||||
res.status(200).json(users.map((u) => `${u}@${result[0].sip_realm}`));
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/:sid/RegisteredSipUsers', async(req, res) => {
|
||||
const {logger, registrar} = req.app.locals;
|
||||
const users = req.body;
|
||||
try {
|
||||
const account_sid = parseAccountSid(req);
|
||||
await validateRequest(req, account_sid);
|
||||
const result = await Account.retrieve(account_sid);
|
||||
if (!result || result.length === 0) {
|
||||
throw new DbErrorBadRequest(`account not found for sid ${account_sid}`);
|
||||
}
|
||||
if (!result[0].sip_realm) {
|
||||
throw new DbErrorBadRequest('account does not have sip_realm configuration');
|
||||
}
|
||||
if (!users || !Array.isArray(users) || users.length === 0) {
|
||||
return res.status(200).json(await registrar.getRegisteredUsersDetailsForRealm(result[0].sip_realm));
|
||||
}
|
||||
const ret = [];
|
||||
for (const u of users) {
|
||||
const user = await registrar.query(`${u}@${result[0].sip_realm}`) || {
|
||||
name: u,
|
||||
contact: null,
|
||||
expiryTime: 0,
|
||||
protocol: null
|
||||
};
|
||||
ret.push({
|
||||
name: u,
|
||||
...user,
|
||||
registered_status: user.expiryTime > 0 ? 'active' : 'inactive',
|
||||
});
|
||||
}
|
||||
res.status(200).json(ret);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/:sid/RegisteredSipUsers/:client', async(req, res) => {
|
||||
const {logger, registrar, lookupClientByAccountAndUsername} = req.app.locals;
|
||||
const client = req.params.client;
|
||||
try {
|
||||
const account_sid = parseAccountSid(req);
|
||||
await validateRequest(req, account_sid);
|
||||
const result = await Account.retrieve(account_sid);
|
||||
if (!result || result.length === 0) {
|
||||
throw new DbErrorBadRequest(`account not found for sid ${account_sid}`);
|
||||
}
|
||||
const user = await registrar.query(`${client}@${result[0].sip_realm}`);
|
||||
const [clientDb] = await lookupClientByAccountAndUsername(account_sid, client);
|
||||
res.status(200).json({
|
||||
name: client,
|
||||
contact: user ? user.contact : null,
|
||||
expiryTime: user ? user.expiryTime : 0,
|
||||
protocol: user ? user.protocol : null,
|
||||
allow_direct_app_calling: clientDb ? clientDb.allow_direct_app_calling : 0,
|
||||
allow_direct_queue_calling: clientDb ? clientDb.allow_direct_queue_calling : 0,
|
||||
allow_direct_user_calling: clientDb ? clientDb.allow_direct_user_calling : 0,
|
||||
registered_status: user ? 'active' : 'inactive'
|
||||
});
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
function coerceNumbers(callInfo) {
|
||||
if (Array.isArray(callInfo)) {
|
||||
return callInfo.map((ci) => {
|
||||
@@ -174,11 +258,15 @@ function validateUpdateCall(opts) {
|
||||
'child_call_hook',
|
||||
'call_status',
|
||||
'listen_status',
|
||||
'transcribe_status',
|
||||
'conf_hold_status',
|
||||
'conf_mute_status',
|
||||
'mute_status',
|
||||
'sip_request',
|
||||
'record'
|
||||
'record',
|
||||
'tag',
|
||||
'dtmf',
|
||||
'conferenceParticipantAction'
|
||||
]
|
||||
.reduce((acc, prop) => (opts[prop] ? ++acc : acc), 0);
|
||||
|
||||
@@ -214,15 +302,34 @@ function validateUpdateCall(opts) {
|
||||
throw new DbErrorBadRequest('invalid conf_mute_status');
|
||||
}
|
||||
if (opts.sip_request &&
|
||||
(!opts.sip_request.method && !opts.sip_request.content_type || !opts.sip_request.content_type)) {
|
||||
throw new DbErrorBadRequest('sip_request requires content_type and content properties');
|
||||
(!opts.sip_request.method || !opts.sip_request.content_type || !opts.sip_request.content)) {
|
||||
throw new DbErrorBadRequest('sip_request requires method, content_type and content properties');
|
||||
}
|
||||
if (opts.record && !opts.record.action) {
|
||||
throw new DbErrorBadRequest('record requires action property');
|
||||
}
|
||||
if (opts.dtmf && !opts.dtmf.digit) {
|
||||
throw new DbErrorBadRequest('invalid dtmf');
|
||||
}
|
||||
if ('startCallRecording' === opts.record?.action && !opts.record.siprecServerURL) {
|
||||
throw new DbErrorBadRequest('record requires siprecServerURL property when starting recording');
|
||||
}
|
||||
if (opts.tag && (typeof opts.tag !== 'object' || Array.isArray(opts.tag) || opts.tag === null)) {
|
||||
throw new DbErrorBadRequest('invalid tag data');
|
||||
}
|
||||
if (opts.conferenceParticipantAction) {
|
||||
if (!['tag', 'untag', 'coach', 'uncoach', 'mute', 'unmute', 'hold', 'unhold']
|
||||
.includes(opts.conferenceParticipantAction.action)) {
|
||||
throw new DbErrorBadRequest(
|
||||
`conferenceParticipantAction invalid action property ${opts.conferenceParticipantAction.action}`);
|
||||
}
|
||||
if ('tag' == opts.conferenceParticipantAction.action && !opts.tag) {
|
||||
throw new DbErrorBadRequest('conferenceParticipantAction requires tag property when action is \'tag\'');
|
||||
}
|
||||
if ('coach' == opts.conferenceParticipantAction.action && !opts.tag) {
|
||||
throw new DbErrorBadRequest('conferenceParticipantAction requires tag property when action is \'coach\'');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validateTo(to) {
|
||||
@@ -259,6 +366,7 @@ async function validateCreateCall(logger, sid, req) {
|
||||
const application = await lookupAppBySid(obj.application_sid);
|
||||
Object.assign(obj, {
|
||||
call_hook: application.call_hook,
|
||||
app_json: application.app_json,
|
||||
call_status_hook: application.call_status_hook,
|
||||
speech_synthesis_vendor: application.speech_synthesis_vendor,
|
||||
speech_synthesis_language: application.speech_synthesis_language,
|
||||
@@ -343,7 +451,7 @@ async function validateCreateMessage(logger, sid, req) {
|
||||
async function validateAdd(req) {
|
||||
/* account-level token can not be used to add accounts */
|
||||
if (req.user.hasAccountAuth) {
|
||||
throw new DbErrorUnprocessableRequest('insufficient permissions to create accounts');
|
||||
throw new DbErrorForbidden('insufficient permissions');
|
||||
}
|
||||
if (req.user.hasServiceProviderAuth && req.user.service_provider_sid) {
|
||||
/* service providers can only create accounts under themselves */
|
||||
@@ -365,7 +473,7 @@ async function validateAdd(req) {
|
||||
|
||||
async function validateUpdate(req, sid) {
|
||||
if (req.user.hasAccountAuth && req.user.account_sid !== sid) {
|
||||
throw new DbErrorUnprocessableRequest('insufficient privileges to update this account');
|
||||
throw new DbErrorForbidden('insufficient privileges');
|
||||
}
|
||||
if (req.user.hasAccountAuth && req.body.sip_realm) {
|
||||
throw new DbErrorBadRequest('use POST /Accounts/:sid/sip_realm/:realm to set or change the sip realm');
|
||||
@@ -377,7 +485,7 @@ async function validateUpdate(req, sid) {
|
||||
throw new DbErrorBadRequest(`account not found for sid ${sid}`);
|
||||
}
|
||||
if (result[0].service_provider_sid !== req.user.service_provider_sid) {
|
||||
throw new DbErrorUnprocessableRequest('cannot update account from different service provider');
|
||||
throw new DbErrorForbidden('insufficient privileges');
|
||||
}
|
||||
}
|
||||
if (req.user.hasScope('admin')) {
|
||||
@@ -397,7 +505,7 @@ async function validateDelete(req, sid) {
|
||||
if (req.user.service_provider_sid && !req.user.hasScope('admin')) {
|
||||
const result = await Account.retrieve(sid);
|
||||
if (result[0].service_provider_sid !== req.user.service_provider_sid) {
|
||||
throw new DbErrorUnprocessableRequest('cannot delete account from different service provider');
|
||||
throw new DbErrorForbidden('insufficient privileges');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -444,6 +552,7 @@ router.get('/:sid', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
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();
|
||||
@@ -458,6 +567,7 @@ router.get('/:sid/WebhookSecret', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
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();
|
||||
@@ -528,6 +638,58 @@ router.delete('/:sid/SubspaceTeleport', async(req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
function encryptBucketCredential(obj) {
|
||||
if (!obj.bucket_credential) return;
|
||||
const {
|
||||
vendor,
|
||||
region,
|
||||
name,
|
||||
access_key_id,
|
||||
secret_access_key,
|
||||
tags,
|
||||
service_key,
|
||||
connection_string,
|
||||
endpoint
|
||||
} = obj.bucket_credential;
|
||||
|
||||
switch (vendor) {
|
||||
case 'aws_s3':
|
||||
assert(access_key_id, 'invalid aws S3 bucket credential: access_key_id is required');
|
||||
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');
|
||||
const awsData = JSON.stringify({vendor, region, name, access_key_id,
|
||||
secret_access_key, tags});
|
||||
obj.bucket_credential = encrypt(awsData);
|
||||
break;
|
||||
case 's3_compatible':
|
||||
assert(access_key_id, 'invalid aws S3 bucket credential: access_key_id is required');
|
||||
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');
|
||||
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');
|
||||
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');
|
||||
const azureData = JSON.stringify({vendor, name, connection_string, tags});
|
||||
obj.bucket_credential = encrypt(azureData);
|
||||
break;
|
||||
case 'none':
|
||||
obj.bucket_credential = null;
|
||||
break;
|
||||
default:
|
||||
throw new DbErrorBadRequest(`unknown storage vendor: ${vendor}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* update
|
||||
*/
|
||||
@@ -535,6 +697,7 @@ router.put('/:sid', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
const sid = parseAccountSid(req);
|
||||
await validateRequest(req, sid);
|
||||
|
||||
// create webhooks if provided
|
||||
const obj = Object.assign({}, req.body);
|
||||
@@ -573,6 +736,8 @@ router.put('/:sid', async(req, res) => {
|
||||
delete obj.registration_hook;
|
||||
delete obj.queue_event_hook;
|
||||
|
||||
encryptBucketCredential(obj);
|
||||
|
||||
const rowsAffected = await Account.update(sid, obj);
|
||||
if (rowsAffected === 0) {
|
||||
return res.status(404).end();
|
||||
@@ -601,6 +766,7 @@ router.delete('/:sid', async(req, res) => {
|
||||
|
||||
try {
|
||||
const sid = parseAccountSid(req);
|
||||
await validateRequest(req, sid);
|
||||
await validateDelete(req, sid);
|
||||
|
||||
const [account] = await promisePool.query('SELECT * FROM accounts WHERE account_sid = ?', sid);
|
||||
@@ -664,6 +830,44 @@ account_subscriptions WHERE account_sid = ?)
|
||||
}
|
||||
});
|
||||
|
||||
/* Test Bucket credential Keys */
|
||||
router.post('/:sid/BucketCredentialTest', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
const account_sid = parseAccountSid(req);
|
||||
await validateRequest(req, account_sid);
|
||||
const {vendor, name, region, access_key_id, secret_access_key, service_key, connection_string, endpoint} = req.body;
|
||||
const ret = {
|
||||
status: 'not tested'
|
||||
};
|
||||
|
||||
switch (vendor) {
|
||||
case 'aws_s3':
|
||||
await testS3Storage(logger, {vendor, name, region, access_key_id, secret_access_key});
|
||||
ret.status = 'ok';
|
||||
break;
|
||||
case 's3_compatible':
|
||||
await testS3Storage(logger, {vendor, name, endpoint, access_key_id, secret_access_key});
|
||||
ret.status = 'ok';
|
||||
break;
|
||||
case 'google':
|
||||
await testGoogleStorage(logger, {vendor, name, service_key});
|
||||
ret.status = 'ok';
|
||||
break;
|
||||
case 'azure':
|
||||
await testAzureStorage(logger, {vendor, name, connection_string});
|
||||
ret.status = 'ok';
|
||||
break;
|
||||
default:
|
||||
throw new DbErrorBadRequest(`Does not support test for ${vendor}`);
|
||||
}
|
||||
return res.status(200).json(ret);
|
||||
}
|
||||
catch (err) {
|
||||
return res.status(200).json({status: 'failed', reason: err.message});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* retrieve account level api keys
|
||||
*/
|
||||
@@ -672,6 +876,8 @@ router.get('/:sid/ApiKeys', async(req, res) => {
|
||||
|
||||
try {
|
||||
const sid = parseAccountSid(req);
|
||||
await validateRequest(req, sid);
|
||||
|
||||
const results = await ApiKey.retrieveAll(sid);
|
||||
res.status(200).json(results);
|
||||
updateLastUsed(logger, sid, req).catch((err) => {});
|
||||
@@ -685,7 +891,7 @@ router.get('/:sid/ApiKeys', async(req, res) => {
|
||||
*/
|
||||
router.post('/:sid/Calls', async(req, res) => {
|
||||
const {retrieveSet, logger} = req.app.locals;
|
||||
const setName = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:active-fs`;
|
||||
const setName = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:fs-service-url`;
|
||||
const serviceUrl = await getFsUrl(logger, retrieveSet, setName);
|
||||
|
||||
if (!serviceUrl) {
|
||||
@@ -694,6 +900,8 @@ router.post('/:sid/Calls', async(req, res) => {
|
||||
|
||||
try {
|
||||
const sid = parseAccountSid(req);
|
||||
await validateRequest(req, sid);
|
||||
|
||||
await validateCreateCall(logger, sid, req);
|
||||
updateLastUsed(logger, sid, req).catch((err) => {});
|
||||
request({
|
||||
@@ -724,10 +932,17 @@ router.post('/:sid/Calls', async(req, res) => {
|
||||
*/
|
||||
router.get('/:sid/Calls', async(req, res) => {
|
||||
const {logger, listCalls} = req.app.locals;
|
||||
|
||||
const {direction, from, to, callStatus} = req.query || {};
|
||||
try {
|
||||
const accountSid = parseAccountSid(req);
|
||||
const calls = await listCalls(accountSid);
|
||||
await validateRequest(req, accountSid);
|
||||
const calls = await listCalls({
|
||||
accountSid,
|
||||
direction,
|
||||
from,
|
||||
to,
|
||||
callStatus
|
||||
});
|
||||
logger.debug(`retrieved ${calls.length} calls for account sid ${accountSid}`);
|
||||
res.status(200).json(coerceNumbers(snakeCase(calls)));
|
||||
updateLastUsed(logger, accountSid, req).catch((err) => {});
|
||||
@@ -744,6 +959,7 @@ router.get('/:sid/Calls/:callSid', async(req, res) => {
|
||||
|
||||
try {
|
||||
const accountSid = parseAccountSid(req);
|
||||
await validateRequest(req, accountSid);
|
||||
const callSid = parseCallSid(req);
|
||||
const callInfo = await retrieveCall(accountSid, callSid);
|
||||
if (callInfo) {
|
||||
@@ -768,6 +984,7 @@ router.delete('/:sid/Calls/:callSid', async(req, res) => {
|
||||
|
||||
try {
|
||||
const accountSid = parseAccountSid(req);
|
||||
await validateRequest(req, accountSid);
|
||||
const callSid = parseCallSid(req);
|
||||
const result = await deleteCall(accountSid, callSid);
|
||||
if (result) {
|
||||
@@ -792,6 +1009,7 @@ const updateCall = async(req, res) => {
|
||||
|
||||
try {
|
||||
const accountSid = parseAccountSid(req);
|
||||
await validateRequest(req, accountSid);
|
||||
const callSid = parseCallSid(req);
|
||||
validateUpdateCall(req.body);
|
||||
const call = await retrieveCall(accountSid, callSid);
|
||||
@@ -832,7 +1050,9 @@ router.post('/:sid/Messages', async(req, res) => {
|
||||
|
||||
try {
|
||||
const account_sid = parseAccountSid(req);
|
||||
const setName = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:active-fs`;
|
||||
await validateRequest(req, account_sid);
|
||||
|
||||
const setName = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:fs-service-url`;
|
||||
const serviceUrl = await getFsUrl(logger, retrieveSet, setName);
|
||||
if (!serviceUrl) res.json({msg: 'no available feature servers at this time'}).status(480);
|
||||
await validateCreateMessage(logger, account_sid, req);
|
||||
@@ -865,4 +1085,23 @@ router.post('/:sid/Messages', async(req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* retrieve info for a group of queues under an account
|
||||
*/
|
||||
router.get('/:sid/Queues', async(req, res) => {
|
||||
const {logger, listSortedSets} = req.app.locals;
|
||||
const { search } = req.query || {};
|
||||
try {
|
||||
const accountSid = parseAccountSid(req);
|
||||
await validateRequest(req, accountSid);
|
||||
const queues = search ? await listSortedSets(accountSid, search) : await listSortedSets(accountSid);
|
||||
logger.debug(`retrieved ${queues.length} queues for account sid ${accountSid}`);
|
||||
res.status(200).json(queues);
|
||||
updateLastUsed(logger, accountSid, req).catch((err) => {});
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const router = require('express').Router();
|
||||
const {DbErrorBadRequest, DbErrorUnprocessableRequest} = require('../../utils/errors');
|
||||
const {DbErrorBadRequest, DbErrorUnprocessableRequest, DbErrorForbidden} = require('../../utils/errors');
|
||||
const Application = require('../../models/application');
|
||||
const Account = require('../../models/account');
|
||||
const Webhook = require('../../models/webhook');
|
||||
@@ -13,6 +13,36 @@ const preconditions = {
|
||||
'update': validateUpdate
|
||||
};
|
||||
|
||||
const validateRequest = async(req, account_sid) => {
|
||||
try {
|
||||
if (req.user.hasScope('admin')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.user.hasScope('account')) {
|
||||
if (account_sid === req.user.account_sid) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new DbErrorForbidden('insufficient permissions');
|
||||
}
|
||||
|
||||
if (req.user.hasScope('service_provider')) {
|
||||
const [r] = await promisePool.execute(
|
||||
'SELECT service_provider_sid from accounts WHERE account_sid = ?', [account_sid]
|
||||
);
|
||||
|
||||
if (r.length === 1 && r[0].service_provider_sid === req.user.service_provider_sid) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new DbErrorForbidden('insufficient permissions');
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/* only user-level tokens can add applications */
|
||||
async function validateAdd(req) {
|
||||
if (req.user.account_sid) {
|
||||
@@ -23,7 +53,7 @@ async function validateAdd(req) {
|
||||
if (!req.body.account_sid) throw new DbErrorBadRequest('missing required field: \'account_sid\'');
|
||||
const result = await Account.retrieve(req.body.account_sid, req.user.service_provider_sid);
|
||||
if (result.length === 0) {
|
||||
throw new DbErrorBadRequest('insufficient privileges to create an application under the specified account');
|
||||
throw new DbErrorForbidden('insufficient privileges');
|
||||
}
|
||||
}
|
||||
if (req.body.call_hook && typeof req.body.call_hook !== 'object') {
|
||||
@@ -35,12 +65,26 @@ async function validateAdd(req) {
|
||||
}
|
||||
|
||||
async function validateUpdate(req, sid) {
|
||||
if (req.user.account_sid) {
|
||||
const app = await Application.retrieve(sid);
|
||||
if (!app || !app.length || app[0].account_sid !== req.user.account_sid) {
|
||||
throw new DbErrorBadRequest('you may not update or delete an application associated with a different account');
|
||||
const app = await Application.retrieve(sid);
|
||||
if (req.user.hasAccountAuth) {
|
||||
if (!app || 0 === app.length || app[0].account_sid !== req.user.account_sid) {
|
||||
throw new DbErrorForbidden('insufficient privileges');
|
||||
}
|
||||
}
|
||||
|
||||
if (req.user.hasServiceProviderAuth) {
|
||||
const [r] = await promisePool.execute(
|
||||
'SELECT service_provider_sid from accounts WHERE account_sid = ?', [app[0].account_sid]
|
||||
);
|
||||
|
||||
if (r.length === 1 && r[0].service_provider_sid === req.user.service_provider_sid) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new DbErrorForbidden('insufficient permissions');
|
||||
}
|
||||
|
||||
|
||||
if (req.body.call_hook && typeof req.body.call_hook !== 'object') {
|
||||
throw new DbErrorBadRequest('\'call_hook\' must be an object when updating an application');
|
||||
}
|
||||
@@ -50,13 +94,24 @@ async function validateUpdate(req, sid) {
|
||||
}
|
||||
|
||||
async function validateDelete(req, sid) {
|
||||
const result = await Application.retrieve(sid);
|
||||
if (req.user.hasAccountAuth) {
|
||||
const result = await Application.retrieve(sid);
|
||||
if (!result || 0 === result.length) throw new DbErrorBadRequest('application does not exist');
|
||||
if (result[0].account_sid !== req.user.account_sid) {
|
||||
throw new DbErrorUnprocessableRequest('cannot delete application owned by a different account');
|
||||
throw new DbErrorUnprocessableRequest('insufficient permissions');
|
||||
}
|
||||
}
|
||||
if (req.user.hasServiceProviderAuth) {
|
||||
const [r] = await promisePool.execute(
|
||||
'SELECT service_provider_sid from accounts WHERE account_sid = ?', [result[0].account_sid]
|
||||
);
|
||||
|
||||
if (r.length === 1 && r[0].service_provider_sid === req.user.service_provider_sid) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new DbErrorForbidden('insufficient permissions');
|
||||
}
|
||||
const assignedPhoneNumbers = await Application.getForeignKeyReferences('phone_numbers.application_sid', sid);
|
||||
if (assignedPhoneNumbers > 0) throw new DbErrorUnprocessableRequest('cannot delete application with phone numbers');
|
||||
}
|
||||
@@ -117,6 +172,7 @@ router.get('/:sid', async(req, res) => {
|
||||
const account_sid = req.user.hasAccountAuth ? req.user.account_sid : null;
|
||||
const results = await Application.retrieve(application_sid, service_provider_sid, account_sid);
|
||||
if (results.length === 0) return res.status(404).end();
|
||||
await validateRequest(req, results[0].account_sid);
|
||||
return res.status(200).json(results[0]);
|
||||
}
|
||||
catch (err) {
|
||||
|
||||
88
lib/routes/api/clients.js
Normal file
88
lib/routes/api/clients.js
Normal file
@@ -0,0 +1,88 @@
|
||||
const router = require('express').Router();
|
||||
const decorate = require('./decorate');
|
||||
const sysError = require('../error');
|
||||
const Client = require('../../models/client');
|
||||
const Account = require('../../models/account');
|
||||
const { DbErrorBadRequest, DbErrorForbidden } = require('../../utils/errors');
|
||||
const { encrypt, decrypt, obscureKey } = require('../../utils/encrypt-decrypt');
|
||||
|
||||
const commonCheck = async(req) => {
|
||||
if (req.user.hasAccountAuth) {
|
||||
req.body.account_sid = req.user.account_sid;
|
||||
} else if (req.user.hasServiceProviderAuth && req.body.account_sid) {
|
||||
const accounts = await Account.retrieve(req.body.account_sid, req.user.service_provider_sid);
|
||||
if (accounts.length === 0) {
|
||||
throw new DbErrorForbidden('insufficient permissions');
|
||||
}
|
||||
}
|
||||
|
||||
if (req.body.password) {
|
||||
req.body.password = encrypt(req.body.password);
|
||||
}
|
||||
};
|
||||
|
||||
const validateAdd = async(req) => {
|
||||
await commonCheck(req);
|
||||
|
||||
const clients = await Client.retrieveByAccountSidAndUserName(req.body.account_sid, req.body.username);
|
||||
if (clients.length) {
|
||||
throw new DbErrorBadRequest('the client\'s username already exists');
|
||||
}
|
||||
};
|
||||
|
||||
const validateUpdate = async(req, sid) => {
|
||||
await commonCheck(req);
|
||||
|
||||
const clients = await Client.retrieveByAccountSidAndUserName(req.body.account_sid, req.body.username);
|
||||
if (clients.length && clients[0].client_sid !== sid) {
|
||||
throw new DbErrorBadRequest('the client\'s username already exists');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const preconditions = {
|
||||
add: validateAdd,
|
||||
update: validateUpdate,
|
||||
};
|
||||
|
||||
decorate(router, Client, ['add', 'update', 'delete'], preconditions);
|
||||
|
||||
router.get('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
const results = req.user.hasAdminAuth ?
|
||||
await Client.retrieveAll() : req.user.hasAccountAuth ?
|
||||
await Client.retrieveAllByAccountSid(req.user.hasAccountAuth ? req.user.account_sid : null) :
|
||||
await Client.retrieveAllByServiceProviderSid(req.user.service_provider_sid);
|
||||
const ret = results.map((c) => {
|
||||
c.password = obscureKey(decrypt(c.password), 1);
|
||||
return c;
|
||||
});
|
||||
res.status(200).json(ret);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/:sid', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
const results = await Client.retrieve(req.params.sid);
|
||||
if (results.length === 0) return res.sendStatus(404);
|
||||
const client = results[0];
|
||||
client.password = obscureKey(decrypt(client.password), 1);
|
||||
if (req.user.hasAccountAuth && client.account_sid !== req.user.account_sid) {
|
||||
return res.sendStatus(404);
|
||||
} else if (req.user.hasServiceProviderAuth) {
|
||||
const accounts = await Account.retrieve(client.account_sid, req.user.service_provider_sid);
|
||||
if (!accounts.length) {
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
}
|
||||
return res.status(200).json(client);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -6,6 +6,7 @@ const {validateEmail, emailSimpleText} = require('../../utils/email-utils');
|
||||
const {promisePool} = require('../../db');
|
||||
const {cacheClient} = require('../../helpers');
|
||||
const sysError = require('../error');
|
||||
const assert = require('assert');
|
||||
const sql = `SELECT * from users user
|
||||
LEFT JOIN accounts AS acc
|
||||
ON acc.account_sid = user.account_sid
|
||||
@@ -26,7 +27,8 @@ function createOauthEmailText(provider) {
|
||||
}
|
||||
|
||||
function createResetEmailText(link) {
|
||||
const baseUrl = 'http://localhost:3001';
|
||||
assert(process.env.JAMBONZ_BASE_URL, 'process.env.JAMBONZ_BASE_URL is missing');
|
||||
const baseUrl = process.env.JAMBONZ_BASE_URL;
|
||||
|
||||
return `Hi there!
|
||||
|
||||
@@ -46,7 +48,7 @@ function createResetEmailText(link) {
|
||||
router.post('/', async(req, res) => {
|
||||
const {logger, addKey} = req.app.locals;
|
||||
const {email} = req.body;
|
||||
const {user_sid} = req.user;
|
||||
|
||||
let obj;
|
||||
try {
|
||||
if (!email || !validateEmail(email)) {
|
||||
@@ -55,11 +57,16 @@ router.post('/', async(req, res) => {
|
||||
|
||||
const [r] = await promisePool.query({sql, nestTables: true}, email);
|
||||
if (0 === r.length) {
|
||||
return res.status(400).json({error: 'email does not exist'});
|
||||
logger.info('user not found');
|
||||
return res.status(400).json({error: 'failed to reset your password'});
|
||||
}
|
||||
obj = r[0];
|
||||
if (!obj.acc.is_active) {
|
||||
return res.status(400).json({error: 'you may not reset the password of an inactive account'});
|
||||
if (!obj.user.is_active) {
|
||||
logger.info(obj.user.name, 'user is inactive');
|
||||
return res.status(400).json({error: 'failed to reset your password'});
|
||||
} else if (obj.acc.account_sid !== null && !obj.acc.is_active) {
|
||||
logger.info(obj.acc.account_sid, 'account is inactive');
|
||||
return res.status(400).json({error: 'failed to reset your password'});
|
||||
}
|
||||
res.sendStatus(204);
|
||||
} catch (err) {
|
||||
@@ -81,7 +88,7 @@ router.post('/', async(req, res) => {
|
||||
emailSimpleText(logger, email, 'Reset password request', createResetEmailText(link));
|
||||
}
|
||||
|
||||
const redisKey = cacheClient.generateRedisKey('jwt', user_sid, 'v2');
|
||||
const redisKey = cacheClient.generateRedisKey('jwt', obj.user.user_sid, 'v2');
|
||||
await cacheClient.delete(redisKey);
|
||||
});
|
||||
|
||||
|
||||
77
lib/routes/api/google-custom-voices.js
Normal file
77
lib/routes/api/google-custom-voices.js
Normal file
@@ -0,0 +1,77 @@
|
||||
const router = require('express').Router();
|
||||
const GoogleCustomVoice = require('../../models/google-custom-voice');
|
||||
const SpeechCredential = require('../../models/speech-credential');
|
||||
const decorate = require('./decorate');
|
||||
const {DbErrorBadRequest, DbErrorForbidden} = require('../../utils/errors');
|
||||
const sysError = require('../error');
|
||||
|
||||
const validateCredentialPermission = async(req) => {
|
||||
const credential = await SpeechCredential.retrieve(req.body.speech_credential_sid);
|
||||
if (!credential || credential.length === 0) {
|
||||
throw new DbErrorBadRequest('Invalid speech_credential_sid');
|
||||
}
|
||||
const cred = credential[0];
|
||||
|
||||
if (req.user.hasServiceProviderAuth && cred.service_provider_sid !== req.user.service_provider_sid) {
|
||||
throw new DbErrorForbidden('Insufficient privileges');
|
||||
}
|
||||
if (req.user.hasAccountAuth && cred.account_sid !== req.user.account_sid) {
|
||||
throw new DbErrorForbidden('Insufficient privileges');
|
||||
}
|
||||
};
|
||||
|
||||
const validateAdd = async(req) => {
|
||||
if (!req.body.speech_credential_sid) {
|
||||
throw new DbErrorBadRequest('missing speech_credential_sid');
|
||||
}
|
||||
|
||||
await validateCredentialPermission(req);
|
||||
};
|
||||
|
||||
const validateUpdate = async(req) => {
|
||||
if (req.body.speech_credential_sid) {
|
||||
await validateCredentialPermission(req);
|
||||
}
|
||||
};
|
||||
|
||||
const preconditions = {
|
||||
add: validateAdd,
|
||||
update: validateUpdate,
|
||||
};
|
||||
|
||||
decorate(router, GoogleCustomVoice, ['add', 'retrieve', 'update', 'delete'], preconditions);
|
||||
|
||||
router.get('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
const account_sid = req.user.account_sid || req.query.account_sid;
|
||||
const service_provider_sid = req.user.service_provider_sid || req.query.service_provider_sid;
|
||||
const speech_credential_sid = req.query.speech_credential_sid;
|
||||
const label = req.query.label;
|
||||
try {
|
||||
let results = [];
|
||||
if (speech_credential_sid) {
|
||||
const [cred] = await SpeechCredential.retrieve(speech_credential_sid);
|
||||
if (!cred) {
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
if (account_sid && cred.account_sid && cred.account_sid !== account_sid) {
|
||||
throw new DbErrorForbidden('Insufficient privileges');
|
||||
}
|
||||
if (service_provider_sid && cred.service_provider_sid && cred.service_provider_sid !== service_provider_sid) {
|
||||
throw new DbErrorForbidden('Insufficient privileges');
|
||||
}
|
||||
results = await GoogleCustomVoice.retrieveAllBySpeechCredentialSid(speech_credential_sid);
|
||||
} else {
|
||||
if (!account_sid && !service_provider_sid) {
|
||||
throw new DbErrorBadRequest('missing account_sid or service_provider_sid in query parameters');
|
||||
}
|
||||
results = await GoogleCustomVoice.retrieveAllByLabel(service_provider_sid, account_sid, label);
|
||||
}
|
||||
res.status(200).json(results);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
||||
@@ -16,6 +16,8 @@ const isAdminScope = (req, res, next) => {
|
||||
// };
|
||||
|
||||
api.use('/BetaInviteCodes', isAdminScope, require('./beta-invite-codes'));
|
||||
api.use('/SystemInformation', isAdminScope, require('./system-information'));
|
||||
api.use('/TtsCache', isAdminScope, require('./tts-cache'));
|
||||
api.use('/ServiceProviders', require('./service-providers'));
|
||||
api.use('/VoipCarriers', require('./voip-carriers'));
|
||||
api.use('/Webhooks', require('./webhooks'));
|
||||
@@ -45,6 +47,13 @@ api.use('/Invoices', require('./invoices'));
|
||||
api.use('/InviteCodes', require('./invite-codes'));
|
||||
api.use('/PredefinedCarriers', require('./predefined-carriers'));
|
||||
api.use('/PasswordSettings', require('./password-settings'));
|
||||
// Least Cost Routing
|
||||
api.use('/Lcrs', require('./lcrs'));
|
||||
api.use('/LcrRoutes', require('./lcr-routes'));
|
||||
api.use('/LcrCarrierSetEntries', require('./lcr-carrier-set-entries'));
|
||||
api.use('/Clients', require('./clients'));
|
||||
// Google Custom Voices
|
||||
api.use('/GoogleCustomVoices', require('./google-custom-voices'));
|
||||
|
||||
// messaging
|
||||
api.use('/Smpps', require('./smpps')); // our smpp server info
|
||||
|
||||
65
lib/routes/api/lcr-carrier-set-entries.js
Normal file
65
lib/routes/api/lcr-carrier-set-entries.js
Normal file
@@ -0,0 +1,65 @@
|
||||
const router = require('express').Router();
|
||||
const LcrCarrierSetEntry = require('../../models/lcr-carrier-set-entry');
|
||||
const LcrRoute = require('../../models/lcr-route');
|
||||
const decorate = require('./decorate');
|
||||
const {DbErrorBadRequest} = require('../../utils/errors');
|
||||
const sysError = require('../error');
|
||||
|
||||
const validateAdd = async(req) => {
|
||||
const {lookupCarrierBySid} = req.app.locals;
|
||||
if (!req.body.lcr_route_sid) {
|
||||
throw new DbErrorBadRequest('missing lcr_route_sid');
|
||||
}
|
||||
// check lcr_route_sid is exist
|
||||
const lcrRoute = await LcrRoute.retrieve(req.body.lcr_route_sid);
|
||||
if (lcrRoute.length === 0) {
|
||||
throw new DbErrorBadRequest('unknown lcr_route_sid');
|
||||
}
|
||||
// check voip_carrier_sid is exist
|
||||
if (!req.body.voip_carrier_sid) {
|
||||
throw new DbErrorBadRequest('missing voip_carrier_sid');
|
||||
}
|
||||
const carrier = await lookupCarrierBySid(req.body.voip_carrier_sid);
|
||||
if (!carrier) {
|
||||
throw new DbErrorBadRequest('unknown voip_carrier_sid');
|
||||
}
|
||||
};
|
||||
|
||||
const validateUpdate = async(req) => {
|
||||
const {lookupCarrierBySid} = req.app.locals;
|
||||
if (req.body.lcr_route_sid) {
|
||||
const lcrRoute = await LcrRoute.retrieve(req.body.lcr_route_sid);
|
||||
if (lcrRoute.length === 0) {
|
||||
throw new DbErrorBadRequest('unknown lcr_route_sid');
|
||||
}
|
||||
}
|
||||
|
||||
// check voip_carrier_sid is exist
|
||||
if (req.body.voip_carrier_sid) {
|
||||
const carrier = await lookupCarrierBySid(req.body.voip_carrier_sid);
|
||||
if (!carrier) {
|
||||
throw new DbErrorBadRequest('unknown voip_carrier_sid');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const preconditions = {
|
||||
add: validateAdd,
|
||||
update: validateUpdate,
|
||||
};
|
||||
|
||||
decorate(router, LcrCarrierSetEntry, ['add', 'retrieve', 'update', 'delete'], preconditions);
|
||||
|
||||
router.get('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
const lcr_route_sid = req.query.lcr_route_sid;
|
||||
try {
|
||||
const results = await LcrCarrierSetEntry.retrieveAllByLcrRouteSid(lcr_route_sid);
|
||||
res.status(200).json(results);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
||||
96
lib/routes/api/lcr-routes.js
Normal file
96
lib/routes/api/lcr-routes.js
Normal file
@@ -0,0 +1,96 @@
|
||||
const router = require('express').Router();
|
||||
const LcrRoute = require('../../models/lcr-route');
|
||||
const Lcr = require('../../models/lcr');
|
||||
const LcrCarrierSetEntry = require('../../models/lcr-carrier-set-entry');
|
||||
const decorate = require('./decorate');
|
||||
const {DbErrorBadRequest} = require('../../utils/errors');
|
||||
const sysError = require('../error');
|
||||
|
||||
const validateAdd = async(req) => {
|
||||
// check if lcr sid is available
|
||||
if (!req.body.lcr_sid) {
|
||||
throw new DbErrorBadRequest('missing parameter lcr_sid');
|
||||
}
|
||||
|
||||
const lcr = await Lcr.retrieve(req.body.lcr_sid);
|
||||
if (lcr.length === 0) {
|
||||
throw new DbErrorBadRequest('unknown lcr_sid');
|
||||
}
|
||||
};
|
||||
|
||||
const validateUpdate = async(req) => {
|
||||
if (req.body.lcr_sid) {
|
||||
const lcr = await Lcr.retrieve(req.body.lcr_sid);
|
||||
if (lcr.length === 0) {
|
||||
throw new DbErrorBadRequest('unknown lcr_sid');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const validateDelete = async(req, sid) => {
|
||||
// delete all lcr carrier set entries
|
||||
await LcrCarrierSetEntry.deleteByLcrRouteSid(sid);
|
||||
};
|
||||
|
||||
const checkUserScope = async(req, lcr_sid) => {
|
||||
if (!lcr_sid) {
|
||||
throw new DbErrorBadRequest('missing lcr_sid');
|
||||
}
|
||||
|
||||
if (req.user.hasAdminAuth) return;
|
||||
|
||||
const lcrList = await Lcr.retrieve(lcr_sid);
|
||||
if (lcrList.length === 0) throw new DbErrorBadRequest('unknown lcr_sid');
|
||||
const lcr = lcrList[0];
|
||||
if (req.user.hasAccountAuth) {
|
||||
if (!lcr.account_sid || lcr.account_sid !== req.user.account_sid) {
|
||||
throw new DbErrorBadRequest('unknown lcr_sid');
|
||||
}
|
||||
}
|
||||
|
||||
if (req.user.hasServiceProviderAuth) {
|
||||
if (!lcr.service_provider_sid || lcr.service_provider_sid !== req.user.service_provider_sid) {
|
||||
throw new DbErrorBadRequest('unknown lcr_sid');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const preconditions = {
|
||||
add: validateAdd,
|
||||
update: validateUpdate,
|
||||
delete: validateDelete,
|
||||
};
|
||||
|
||||
decorate(router, LcrRoute, ['add', 'update', 'delete'], preconditions);
|
||||
|
||||
router.get('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
const lcr_sid = req.query.lcr_sid;
|
||||
try {
|
||||
await checkUserScope(req, lcr_sid);
|
||||
const results = await LcrRoute.retrieveAllByLcrSid(lcr_sid);
|
||||
for (const r of results) {
|
||||
r.lcr_carrier_set_entries = await LcrCarrierSetEntry.retrieveAllByLcrRouteSid(r.lcr_route_sid);
|
||||
}
|
||||
res.status(200).json(results);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/:sid', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
const lcr_route_sid = req.params.sid;
|
||||
try {
|
||||
const results = await LcrRoute.retrieve(lcr_route_sid);
|
||||
if (results.length === 0) return res.sendStatus(404);
|
||||
const route = results[0];
|
||||
route.lcr_carrier_set_entries = await LcrCarrierSetEntry.retrieveAllByLcrRouteSid(route.lcr_route_sid);
|
||||
res.status(200).json(route);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
||||
225
lib/routes/api/lcrs.js
Normal file
225
lib/routes/api/lcrs.js
Normal file
@@ -0,0 +1,225 @@
|
||||
const router = require('express').Router();
|
||||
const Lcr = require('../../models/lcr');
|
||||
const LcrCarrierSetEntry = require('../../models/lcr-carrier-set-entry');
|
||||
const LcrRoutes = require('../../models/lcr-route');
|
||||
const decorate = require('./decorate');
|
||||
const {DbErrorBadRequest} = require('../../utils/errors');
|
||||
const sysError = require('../error');
|
||||
const ServiceProvider = require('../../models/service-provider');
|
||||
|
||||
const validateAssociatedTarget = async(req, sid) => {
|
||||
const {lookupAccountBySid} = req.app.locals;
|
||||
if (req.body.account_sid) {
|
||||
// Add only for account
|
||||
req.body.service_provider_sid = null;
|
||||
const account = await lookupAccountBySid(req.body.account_sid);
|
||||
if (!account) throw new DbErrorBadRequest('unknown account_sid');
|
||||
const lcr = await Lcr.retrieveAllByAccountSid(req.body.account_sid);
|
||||
if (lcr.length > 0 && (!sid || sid !== lcr[0].lcr_sid)) {
|
||||
throw new DbErrorBadRequest(`Account: ${account.name} already has an active call routing table.`);
|
||||
}
|
||||
} else if (req.body.service_provider_sid) {
|
||||
const serviceProviders = await ServiceProvider.retrieve(req.body.service_provider_sid);
|
||||
if (serviceProviders.length === 0) throw new DbErrorBadRequest('unknown service_provider_sid');
|
||||
const serviceProvider = serviceProviders[0];
|
||||
const lcr = await Lcr.retrieveAllByServiceProviderSid(req.body.service_provider_sid);
|
||||
if (lcr.length > 0 && (!sid || sid !== lcr[0].lcr_sid)) {
|
||||
throw new DbErrorBadRequest(`Service Provider: ${serviceProvider.name} already
|
||||
has an active call routing table.`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const validateAdd = async(req) => {
|
||||
if (req.user.hasAccountAuth) {
|
||||
// Account just create LCR for himself
|
||||
req.body.account_sid = req.user.account_sid;
|
||||
} else if (req.user.hasServiceProviderAuth) {
|
||||
// SP just can create LCR for himself
|
||||
req.body.service_provider_sid = req.user.service_provider_sid;
|
||||
req.body.account_sid = null;
|
||||
}
|
||||
|
||||
await validateAssociatedTarget(req);
|
||||
// check if lcr_carrier_set_entry is available
|
||||
if (req.body.lcr_carrier_set_entry) {
|
||||
const e = await LcrCarrierSetEntry.retrieve(req.body.lcr_carrier_set_entry);
|
||||
if (e.length === 0) throw new DbErrorBadRequest('unknown lcr_carrier_set_entry');
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const validateUserPermissionForExistingEntity = async(req, sid) => {
|
||||
const r = await Lcr.retrieve(sid);
|
||||
if (r.length === 0) {
|
||||
throw new DbErrorBadRequest('unknown lcr_sid');
|
||||
}
|
||||
const lcr = r[0];
|
||||
if (req.user.hasAccountAuth) {
|
||||
if (lcr.account_sid != req.user.account_sid) {
|
||||
throw new DbErrorBadRequest('unknown lcr_sid');
|
||||
}
|
||||
} else if (req.user.hasServiceProviderAuth) {
|
||||
if (lcr.service_provider_sid != req.user.service_provider_sid) {
|
||||
throw new DbErrorBadRequest('unknown lcr_sid');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const validateUpdate = async(req, sid) => {
|
||||
await validateUserPermissionForExistingEntity(req, sid);
|
||||
await validateAssociatedTarget(req, sid);
|
||||
};
|
||||
|
||||
const validateDelete = async(req, sid) => {
|
||||
if (req.user.hasAccountAuth) {
|
||||
/* can only delete Lcr for the user's account */
|
||||
const r = await Lcr.retrieve(sid);
|
||||
const lcr = r.length > 0 ? r[0] : null;
|
||||
if (!lcr || (req.user.account_sid && lcr.account_sid != req.user.account_sid)) {
|
||||
throw new DbErrorBadRequest('unknown lcr_sid');
|
||||
}
|
||||
}
|
||||
await Lcr.releaseDefaultEntry(sid);
|
||||
// fetch lcr route
|
||||
const lcr_routes = await LcrRoutes.retrieveAllByLcrSid(sid);
|
||||
// delete all lcr carrier set entries
|
||||
for (const e of lcr_routes) {
|
||||
await LcrCarrierSetEntry.deleteByLcrRouteSid(e.lcr_route_sid);
|
||||
}
|
||||
|
||||
// delete all lcr routes
|
||||
await LcrRoutes.deleteByLcrSid(sid);
|
||||
};
|
||||
|
||||
const preconditions = {
|
||||
add: validateAdd,
|
||||
update: validateUpdate,
|
||||
delete: validateDelete
|
||||
};
|
||||
|
||||
decorate(router, Lcr, ['add', 'update', 'delete'], preconditions);
|
||||
|
||||
const validateLcrBatchAdd = async(lcr_sid, body, lookupCarrierBySid) => {
|
||||
for (const lcr_route of body) {
|
||||
lcr_route.lcr_sid = lcr_sid;
|
||||
if (!lcr_route.lcr_carrier_set_entries || lcr_route.lcr_carrier_set_entries.length === 0) {
|
||||
throw new DbErrorBadRequest('Lcr Route batch process require lcr_carrier_set_entries');
|
||||
}
|
||||
for (const entry of lcr_route.lcr_carrier_set_entries) {
|
||||
// check voip_carrier_sid is exist
|
||||
if (!entry.voip_carrier_sid) {
|
||||
throw new DbErrorBadRequest('One of lcr_carrier_set_entries is missing voip_carrier_sid');
|
||||
}
|
||||
const carrier = await lookupCarrierBySid(entry.voip_carrier_sid);
|
||||
if (!carrier) {
|
||||
throw new DbErrorBadRequest('unknown voip_carrier_sid');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const addNewLcrRoute = async(lcr_route) => {
|
||||
const lcr_sid = lcr_route.lcr_sid;
|
||||
const lcr_carrier_set_entries = lcr_route.lcr_carrier_set_entries;
|
||||
delete lcr_route.lcr_carrier_set_entries;
|
||||
const lcr_route_sid = await LcrRoutes.make(lcr_route);
|
||||
for (const entry of lcr_carrier_set_entries) {
|
||||
entry.lcr_route_sid = lcr_route_sid;
|
||||
const lcr_carrier_set_entry_sid = await LcrCarrierSetEntry.make(entry);
|
||||
if (lcr_route.priority === 9999) {
|
||||
// this is default lcr set entry
|
||||
const [lcr] = await Lcr.retrieve(lcr_sid);
|
||||
if (lcr) {
|
||||
lcr.default_carrier_set_entry_sid = lcr_carrier_set_entry_sid;
|
||||
delete lcr.lcr_sid;
|
||||
await Lcr.update(lcr_sid, lcr);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
router.post('/:sid/Routes', async(req, res) => {
|
||||
const results = await Lcr.retrieve(req.params.sid);
|
||||
if (results.length === 0) return res.sendStatus(404);
|
||||
const {logger, lookupCarrierBySid} = req.app.locals;
|
||||
try {
|
||||
const body = req.body;
|
||||
await validateLcrBatchAdd(req.params.sid, body, lookupCarrierBySid);
|
||||
for (const lcr_route of body) {
|
||||
await addNewLcrRoute(lcr_route, lookupCarrierBySid);
|
||||
}
|
||||
res.sendStatus(204);
|
||||
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/:sid/Routes', async(req, res) => {
|
||||
const results = await Lcr.retrieve(req.params.sid);
|
||||
if (results.length === 0) return res.sendStatus(404);
|
||||
const {logger, lookupCarrierBySid} = req.app.locals;
|
||||
try {
|
||||
const body = req.body;
|
||||
await validateLcrBatchAdd(req.params.sid, body, lookupCarrierBySid);
|
||||
for (const lcr_route of body) {
|
||||
if (lcr_route.lcr_route_sid) {
|
||||
const lcr_route_sid = lcr_route.lcr_route_sid;
|
||||
delete lcr_route.lcr_route_sid;
|
||||
const lcr_carrier_set_entries = lcr_route.lcr_carrier_set_entries;
|
||||
delete lcr_route.lcr_carrier_set_entries;
|
||||
await LcrRoutes.update(lcr_route_sid, lcr_route);
|
||||
for (const entry of lcr_carrier_set_entries) {
|
||||
const lcr_carrier_set_entry_sid = entry.lcr_carrier_set_entry_sid;
|
||||
delete entry.lcr_carrier_set_entry_sid;
|
||||
await LcrCarrierSetEntry.update(lcr_carrier_set_entry_sid, entry);
|
||||
}
|
||||
} else {
|
||||
// Route is not available yet, let create it now
|
||||
await addNewLcrRoute(lcr_route, lookupCarrierBySid);
|
||||
}
|
||||
}
|
||||
res.sendStatus(204);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
const results = req.user.hasAdminAuth ?
|
||||
await Lcr.retrieveAll() : req.user.hasAccountAuth ?
|
||||
await Lcr.retrieveAllByAccountSid(req.user.hasAccountAuth ? req.user.account_sid : null) :
|
||||
await Lcr.retrieveAllByServiceProviderSid(req.user.service_provider_sid);
|
||||
|
||||
for (const lcr of results) {
|
||||
lcr.number_routes = await LcrRoutes.countAllByLcrSid(lcr.lcr_sid);
|
||||
}
|
||||
res.status(200).json(results);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/:sid', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
const results = await Lcr.retrieve(req.params.sid);
|
||||
if (results.length === 0) return res.sendStatus(404);
|
||||
const lcr = results[0];
|
||||
if (req.user.hasAccountAuth && lcr.account_sid !== req.user.account_sid) {
|
||||
return res.sendStatus(404);
|
||||
} else if (req.user.hasServiceProviderAuth && lcr.service_provider_sid !== req.user.service_provider_sid) {
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
lcr.number_routes = await LcrRoutes.countAllByLcrSid(lcr.lcr_sid);
|
||||
return res.status(200).json(lcr);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -13,11 +13,9 @@ WHERE up.permission_sid = p.permission_sid
|
||||
AND up.user_sid = ?
|
||||
`;
|
||||
const retrieveSql = 'SELECT * from users where name = ?';
|
||||
const tokenSql = 'SELECT token from api_keys where account_sid IS NULL AND service_provider_sid IS NULL';
|
||||
|
||||
|
||||
router.post('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
const {logger, incrKey, retrieveKey} = req.app.locals;
|
||||
const {username, password} = req.body;
|
||||
if (!username || !password) {
|
||||
logger.info('Bad POST to /login is missing username or password');
|
||||
@@ -31,15 +29,30 @@ router.post('/', async(req, res) => {
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
logger.info({r}, 'successfully retrieved user account');
|
||||
const isCorrect = await verifyPassword(r[0].hashed_password, password);
|
||||
if (!isCorrect) return res.sendStatus(403);
|
||||
const force_change = !!r[0].force_change;
|
||||
const [t] = await promisePool.query(tokenSql);
|
||||
if (t.length === 0) {
|
||||
logger.error('Database has no admin token provisioned...run reset_admin_password');
|
||||
return res.sendStatus(500);
|
||||
|
||||
const maxLoginAttempts = process.env.LOGIN_ATTEMPTS_MAX_RETRIES || 6;
|
||||
const loginAttempsBlocked = await retrieveKey(`login:${r[0].user_sid}`) >= maxLoginAttempts;
|
||||
|
||||
if (loginAttempsBlocked) {
|
||||
logger.info(`User ${r[0].user_sid} was blocked due to excessive login attempts with incorrect credentials.`);
|
||||
return res.status(403)
|
||||
.json({error: 'Maximum login attempts reached. Please try again later or reset your password.'});
|
||||
}
|
||||
|
||||
const isCorrect = await verifyPassword(r[0].hashed_password, password);
|
||||
if (!isCorrect) {
|
||||
const attempTime = process.env.LOGIN_ATTEMPTS_TIME || 1800;
|
||||
const newAttempt = await incrKey(`login:${r[0].user_sid}`, attempTime)
|
||||
.catch((err) => logger.error({err}, 'Error adding logging attempt to redis'));
|
||||
if (newAttempt >= maxLoginAttempts) {
|
||||
logger.info(`User ${r[0].user_sid} is now blocked due to excessive login attempts with incorrect credentials.`);
|
||||
return res.status(403)
|
||||
.json({error: `Maximum login attempts reached. Please try again in ${attempTime} seconds.`});
|
||||
}
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
const force_change = !!r[0].force_change;
|
||||
|
||||
const [p] = await promisePool.query(retrievePemissionsSql, r[0].user_sid);
|
||||
const permissions = p.map((x) => x.name);
|
||||
const obj = {user_sid: r[0].user_sid, scope: 'admin', force_change, permissions};
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
const router = require('express').Router();
|
||||
const {DbErrorUnprocessableRequest, DbErrorBadRequest} = require('../../utils/errors');
|
||||
const {DbErrorBadRequest, DbErrorForbidden} = require('../../utils/errors');
|
||||
const PhoneNumber = require('../../models/phone-number');
|
||||
const VoipCarrier = require('../../models/voip-carrier');
|
||||
const Account = require('../../models/account');
|
||||
const decorate = require('./decorate');
|
||||
const {promisePool} = require('../../db');
|
||||
const {e164} = require('../../utils/phone-number-utils');
|
||||
const preconditions = {
|
||||
'add': validateAdd,
|
||||
@@ -21,6 +23,10 @@ async function validateAdd(req) {
|
||||
req.body.account_sid = req.user.account_sid;
|
||||
}
|
||||
|
||||
if (req.user.hasServiceProviderAuth) {
|
||||
req.body.service_provider_sid = req.user.service_provider_sid;
|
||||
}
|
||||
|
||||
if (!req.body.number) throw new DbErrorBadRequest('number is required');
|
||||
const formattedNumber = e164(req.body.number);
|
||||
req.body.number = formattedNumber;
|
||||
@@ -42,11 +48,11 @@ async function checkInUse(req, sid) {
|
||||
const phoneNumber = await PhoneNumber.retrieve(sid);
|
||||
if (req.user.hasAccountAuth) {
|
||||
if (phoneNumber && phoneNumber.length && phoneNumber[0].account_sid !== req.user.account_sid) {
|
||||
throw new DbErrorUnprocessableRequest('cannot delete a phone number that belongs to another account');
|
||||
throw new DbErrorForbidden('insufficient privileges');
|
||||
}
|
||||
}
|
||||
if (!req.user.hasAccountAuth && phoneNumber.account_sid) {
|
||||
throw new DbErrorUnprocessableRequest('cannot delete phone number that is assigned to an account');
|
||||
throw new DbErrorForbidden('insufficient privileges');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,10 +64,23 @@ async function validateUpdate(req, sid) {
|
||||
const phoneNumber = await PhoneNumber.retrieve(sid);
|
||||
if (req.user.hasAccountAuth) {
|
||||
if (phoneNumber && phoneNumber.length && phoneNumber[0].account_sid !== req.user.account_sid) {
|
||||
throw new DbErrorUnprocessableRequest('cannot operate on a phone number that belongs to another account');
|
||||
throw new DbErrorForbidden('insufficient privileges');
|
||||
}
|
||||
}
|
||||
if (req.user.hasServiceProviderAuth) {
|
||||
let service_provider_sid;
|
||||
|
||||
if (!phoneNumber[0].service_provider_sid) {
|
||||
const [r] = await Account.retrieve(phoneNumber[0].account_sid);
|
||||
service_provider_sid = r.service_provider_sid;
|
||||
} else {
|
||||
service_provider_sid = phoneNumber[0].service_provider_sid;
|
||||
}
|
||||
|
||||
if (phoneNumber && phoneNumber.length && service_provider_sid !== req.user.service_provider_sid) {
|
||||
throw new DbErrorForbidden('insufficient privileges');
|
||||
}
|
||||
}
|
||||
// TODO: if we are assigning to an account, verify it exists
|
||||
|
||||
// TODO: if we are assigning to an application, verify it is associated to the same account
|
||||
@@ -75,7 +94,9 @@ decorate(router, PhoneNumber, ['add', 'update', 'delete'], preconditions);
|
||||
router.get('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
const results = await PhoneNumber.retrieveAll(req.user.hasAccountAuth ? req.user.account_sid : null);
|
||||
const results = req.user.hasServiceProviderAuth ?
|
||||
await PhoneNumber.retrieveAllForSP(req.user.service_provider_sid) :
|
||||
await PhoneNumber.retrieveAll(req.user.hasAccountAuth ? req.user.account_sid : null);
|
||||
res.status(200).json(results);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
@@ -90,6 +111,18 @@ router.get('/:sid', async(req, res) => {
|
||||
const account_sid = req.user.hasAccountAuth ? req.user.account_sid : null;
|
||||
const results = await PhoneNumber.retrieve(sid, account_sid);
|
||||
if (results.length === 0) return res.status(404).end();
|
||||
|
||||
if (req.user.hasServiceProviderAuth && results.length === 1) {
|
||||
const account_sid = results[0].account_sid;
|
||||
const [r] = await promisePool.execute(
|
||||
'SELECT service_provider_sid from accounts WHERE account_sid = ?', [account_sid]);
|
||||
if (r.length === 1 && r[0].service_provider_sid !== req.user.service_provider_sid) {
|
||||
throw new DbErrorBadRequest('insufficient privileges');
|
||||
}
|
||||
}
|
||||
if (req.user.hasAccountAuth && results.length > 1) {
|
||||
return res.status(200).json(results.filter((r) => r.phone_number_sid === sid)[0]);
|
||||
}
|
||||
return res.status(200).json(results[0]);
|
||||
}
|
||||
catch (err) {
|
||||
|
||||
@@ -2,6 +2,17 @@ const router = require('express').Router();
|
||||
const sysError = require('../error');
|
||||
const {DbErrorBadRequest} = require('../../utils/errors');
|
||||
const {getHomerApiKey, getHomerSipTrace, getHomerPcap} = require('../../utils/homer-utils');
|
||||
const {getJaegerTrace} = require('../../utils/jaeger-utils');
|
||||
const Account = require('../../models/account');
|
||||
const {
|
||||
getS3Object,
|
||||
getGoogleStorageObject,
|
||||
getAzureStorageObject,
|
||||
deleteS3Object,
|
||||
deleteGoogleStorageObject,
|
||||
deleteAzureStorageObject
|
||||
} = require('../../utils/storage-utils');
|
||||
|
||||
const parseAccountSid = (url) => {
|
||||
const arr = /Accounts\/([^\/]*)/.exec(url);
|
||||
if (arr) return arr[1];
|
||||
@@ -18,7 +29,7 @@ router.get('/', async(req, res) => {
|
||||
logger.debug({opts: req.query}, 'GET /RecentCalls');
|
||||
const account_sid = parseAccountSid(req.originalUrl);
|
||||
const service_provider_sid = account_sid ? null : parseServiceProviderSid(req.originalUrl);
|
||||
const {page, count, trunk, direction, days, answered, start, end} = req.query || {};
|
||||
const {page, count, trunk, direction, days, answered, start, end, filter} = req.query || {};
|
||||
if (!page || page < 1) throw new DbErrorBadRequest('missing or invalid "page" query arg');
|
||||
if (!count || count < 25 || count > 500) throw new DbErrorBadRequest('missing or invalid "count" query arg');
|
||||
|
||||
@@ -33,6 +44,7 @@ router.get('/', async(req, res) => {
|
||||
answered,
|
||||
start: days ? undefined : start,
|
||||
end: days ? undefined : end,
|
||||
filter
|
||||
});
|
||||
res.status(200).json(data);
|
||||
}
|
||||
@@ -47,6 +59,7 @@ router.get('/', async(req, res) => {
|
||||
answered,
|
||||
start: days ? undefined : start,
|
||||
end: days ? undefined : end,
|
||||
filter
|
||||
});
|
||||
res.status(200).json(data);
|
||||
}
|
||||
@@ -72,12 +85,12 @@ router.get('/:call_id', async(req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/:call_id/pcap', async(req, res) => {
|
||||
router.get('/:call_id/:method/pcap', async(req, res) => {
|
||||
const {logger} = req.app.locals;
|
||||
try {
|
||||
const token = await getHomerApiKey(logger);
|
||||
if (!token) return res.sendStatus(400, {msg: 'getHomerApiKey: Failed to get Homer API token; check server config'});
|
||||
const stream = await getHomerPcap(logger, token, [req.params.call_id]);
|
||||
const stream = await getHomerPcap(logger, token, [req.params.call_id], req.params.method);
|
||||
if (!stream) {
|
||||
logger.info(`getHomerApiKey: unable to get sip traces from Homer for ${req.params.call_id}`);
|
||||
return res.sendStatus(404);
|
||||
@@ -93,4 +106,100 @@ router.get('/:call_id/pcap', async(req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/trace/:trace_id', async(req, res) => {
|
||||
const {logger} = req.app.locals;
|
||||
const {trace_id} = req.params;
|
||||
try {
|
||||
const obj = await getJaegerTrace(logger, trace_id);
|
||||
if (!obj) {
|
||||
logger.info(`/RecentCalls: unable to get spans from jaeger for ${trace_id}`);
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
res.status(200).json(obj.result);
|
||||
} catch (err) {
|
||||
logger.error({err}, `/RecentCalls error retrieving jaeger trace ${trace_id}`);
|
||||
res.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/:call_sid/record/:year/:month/:day/:format', async(req, res) => {
|
||||
const {logger} = req.app.locals;
|
||||
const {call_sid, year, month, day, format} = req.params;
|
||||
|
||||
try {
|
||||
const account_sid = parseAccountSid(req.originalUrl);
|
||||
const r = await Account.retrieve(account_sid);
|
||||
if (r.length === 0 || !r[0].bucket_credential) return res.sendStatus(404);
|
||||
const {bucket_credential} = r[0];
|
||||
const getOptions = {
|
||||
...bucket_credential,
|
||||
key: `${year}/${month}/${day}/${call_sid}.${format || 'mp3'}`
|
||||
};
|
||||
let stream;
|
||||
switch (bucket_credential.vendor) {
|
||||
case 'aws_s3':
|
||||
case 's3_compatible':
|
||||
stream = await getS3Object(logger, getOptions);
|
||||
break;
|
||||
case 'google':
|
||||
stream = await getGoogleStorageObject(logger, getOptions);
|
||||
break;
|
||||
case 'azure':
|
||||
stream = await getAzureStorageObject(logger, getOptions);
|
||||
break;
|
||||
default:
|
||||
logger.error(`There is no handler for fetching record from ${bucket_credential.vendor}`);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
res.set({
|
||||
'Content-Type': `audio/${format || 'mp3'}`
|
||||
});
|
||||
if (stream) {
|
||||
stream.pipe(res);
|
||||
} else {
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error({err}, ` error retrieving recording ${call_sid}`);
|
||||
res.sendStatus(404);
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/:call_sid/record/:year/:month/:day/:format', async(req, res) => {
|
||||
const {logger} = req.app.locals;
|
||||
const {call_sid, year, month, day, format} = req.params;
|
||||
|
||||
try {
|
||||
const account_sid = parseAccountSid(req.originalUrl);
|
||||
const r = await Account.retrieve(account_sid);
|
||||
if (r.length === 0 || !r[0].bucket_credential) return res.sendStatus(404);
|
||||
const {bucket_credential} = r[0];
|
||||
|
||||
const deleteOptions = {
|
||||
...bucket_credential,
|
||||
key: `${year}/${month}/${day}/${call_sid}.${format || 'mp3'}`
|
||||
};
|
||||
|
||||
switch (bucket_credential.vendor) {
|
||||
case 'aws_s3':
|
||||
case 's3_compatible':
|
||||
await deleteS3Object(logger, deleteOptions);
|
||||
break;
|
||||
case 'google':
|
||||
await deleteGoogleStorageObject(logger, deleteOptions);
|
||||
break;
|
||||
case 'azure':
|
||||
await deleteAzureStorageObject(logger, deleteOptions);
|
||||
break;
|
||||
default:
|
||||
logger.error(`There is no handler for deleting record from ${bucket_credential.vendor}`);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
res.sendStatus(204);
|
||||
} catch (err) {
|
||||
logger.error({err}, ` error deleting recording ${call_sid}`);
|
||||
res.sendStatus(404);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -16,8 +16,9 @@ const insertUserSql = `INSERT into users
|
||||
(user_sid, account_sid, name, email, provider, provider_userid, email_validated)
|
||||
values (?, ?, ?, ?, ?, ?, 1)`;
|
||||
const insertUserLocalSql = `INSERT into users
|
||||
(user_sid, account_sid, name, email, email_activation_code, email_validated, provider, hashed_password)
|
||||
values (?, ?, ?, ?, ?, 0, 'local', ?)`;
|
||||
(user_sid, account_sid, name, email, email_activation_code, email_validated, provider,
|
||||
hashed_password, service_provider_sid)
|
||||
values (?, ?, ?, ?, ?, 0, 'local', ?, ?)`;
|
||||
const insertAccountSql = `INSERT into accounts
|
||||
(account_sid, service_provider_sid, name, is_active, webhook_secret, trial_end_date)
|
||||
values (?, ?, ?, ?, ?, CURDATE() + INTERVAL 21 DAY)`;
|
||||
@@ -36,7 +37,7 @@ const insertSignupHistorySql = `INSERT into signup_history
|
||||
values (?, ?)`;
|
||||
|
||||
const addLocalUser = async(logger, user_sid, account_sid,
|
||||
name, email, email_activation_code, passwordHash) => {
|
||||
name, email, email_activation_code, passwordHash, service_provider_sid) => {
|
||||
const [r] = await promisePool.execute(insertUserLocalSql,
|
||||
[
|
||||
user_sid,
|
||||
@@ -44,7 +45,8 @@ const addLocalUser = async(logger, user_sid, account_sid,
|
||||
name,
|
||||
email,
|
||||
email_activation_code,
|
||||
passwordHash
|
||||
passwordHash,
|
||||
service_provider_sid
|
||||
]);
|
||||
debug({r}, 'Result from adding user');
|
||||
};
|
||||
@@ -145,7 +147,7 @@ router.post('/', async(req, res) => {
|
||||
const user = await doGithubAuth(logger, req.body);
|
||||
logger.info({user}, 'retrieved user details from github');
|
||||
Object.assign(userProfile, {
|
||||
name: user.name,
|
||||
name: user.email,
|
||||
email: user.email,
|
||||
email_validated: user.email_validated,
|
||||
avatar_url: user.avatar_url,
|
||||
@@ -157,7 +159,7 @@ router.post('/', async(req, res) => {
|
||||
const user = await doGoogleAuth(logger, req.body);
|
||||
logger.info({user}, 'retrieved user details from google');
|
||||
Object.assign(userProfile, {
|
||||
name: user.name || user.email,
|
||||
name: user.email || user.email,
|
||||
email: user.email,
|
||||
email_validated: user.verified_email,
|
||||
picture: user.picture,
|
||||
@@ -170,7 +172,7 @@ router.post('/', async(req, res) => {
|
||||
logger.info({user}, 'retrieved user details for local provider');
|
||||
debug({user}, 'retrieved user details for local provider');
|
||||
Object.assign(userProfile, {
|
||||
name: user.name,
|
||||
name: user.email,
|
||||
email: user.email,
|
||||
provider: 'local',
|
||||
email_activation_code: user.email_activation_code
|
||||
@@ -280,7 +282,8 @@ router.post('/', async(req, res) => {
|
||||
const passwordHash = await generateHashedPassword(req.body.password);
|
||||
debug(`hashed password: ${passwordHash}`);
|
||||
await addLocalUser(logger, userProfile.user_sid, userProfile.account_sid,
|
||||
userProfile.name, userProfile.email, userProfile.email_activation_code, passwordHash);
|
||||
userProfile.name, userProfile.email, userProfile.email_activation_code,
|
||||
passwordHash, req.body.service_provider_sid);
|
||||
debug('added local user');
|
||||
}
|
||||
else {
|
||||
@@ -293,17 +296,25 @@ router.post('/', async(req, res) => {
|
||||
const callStatusSid = uuid();
|
||||
const helloWordSid = uuid();
|
||||
const dialTimeSid = uuid();
|
||||
const echoSid = uuid();
|
||||
|
||||
/* 3 webhooks */
|
||||
await promisePool.execute(insertWebookSql, [callStatusSid, 'https://public-apps.jambonz.us/call-status', 'POST']);
|
||||
await promisePool.execute(insertWebookSql, [helloWordSid, 'https://public-apps.jambonz.us/hello-world', 'POST']);
|
||||
await promisePool.execute(insertWebookSql, [dialTimeSid, 'https://public-apps.jambonz.us/dial-time', 'POST']);
|
||||
/* 4 webhooks */
|
||||
await promisePool.execute(insertWebookSql,
|
||||
[callStatusSid, 'https://public-apps.jambonz.cloud/call-status', 'POST']);
|
||||
await promisePool.execute(insertWebookSql,
|
||||
[helloWordSid, 'https://public-apps.jambonz.cloud/hello-world', 'POST']);
|
||||
await promisePool.execute(insertWebookSql,
|
||||
[dialTimeSid, 'https://public-apps.jambonz.cloud/dial-time', 'POST']);
|
||||
await promisePool.execute(insertWebookSql,
|
||||
[echoSid, 'https://public-apps.jambonz.cloud/echo', 'POST']);
|
||||
|
||||
/* 2 applications */
|
||||
await promisePool.execute(insertApplicationSql, [uuid(), userProfile.account_sid, 'hello world',
|
||||
helloWordSid, callStatusSid, 'google', 'en-US', 'en-US-Wavenet-C', 'google', 'en-US']);
|
||||
await promisePool.execute(insertApplicationSql, [uuid(), userProfile.account_sid, 'dial time clock',
|
||||
dialTimeSid, callStatusSid, 'google', 'en-US', 'en-US-Wavenet-C', 'google', 'en-US']);
|
||||
await promisePool.execute(insertApplicationSql, [uuid(), userProfile.account_sid, 'simple echo test',
|
||||
echoSid, callStatusSid, 'google', 'en-US', 'en-US-Wavenet-C', 'google', 'en-US']);
|
||||
|
||||
Object.assign(userProfile, {
|
||||
pristine: true,
|
||||
@@ -327,7 +338,7 @@ router.post('/', async(req, res) => {
|
||||
|
||||
await addLocalUser(logger, userProfile.user_sid, userProfile.account_sid,
|
||||
userProfile.name, userProfile.email, userProfile.email_activation_code,
|
||||
passwordHash);
|
||||
passwordHash, req.body.service_provider_sid);
|
||||
|
||||
/* note: we deactivate the old user once the new email is validated */
|
||||
}
|
||||
@@ -349,6 +360,8 @@ router.post('/', async(req, res) => {
|
||||
const token = jwt.sign({
|
||||
user_sid: userProfile.user_sid,
|
||||
account_sid: userProfile.account_sid,
|
||||
service_provider_sid: req.body.service_provider_sid,
|
||||
scope: 'account',
|
||||
email: userProfile.email,
|
||||
name: userProfile.name
|
||||
}, process.env.JWT_SECRET, { expiresIn });
|
||||
|
||||
@@ -2,25 +2,46 @@ const router = require('express').Router();
|
||||
const Sbc = require('../../models/sbc');
|
||||
const decorate = require('./decorate');
|
||||
const sysError = require('../error');
|
||||
//const {DbErrorBadRequest} = require('../../utils/errors');
|
||||
//const {promisePool} = require('../../db');
|
||||
const {DbErrorBadRequest} = require('../../utils/errors');
|
||||
const {promisePool} = require('../../db');
|
||||
|
||||
decorate(router, Sbc, ['add', 'delete']);
|
||||
const validate = (req, res) => {
|
||||
if (req.user.hasScope('admin')) return;
|
||||
res.status(403).json({
|
||||
status: 'fail',
|
||||
message: 'insufficient privileges'
|
||||
});
|
||||
};
|
||||
|
||||
const preconditions = {
|
||||
'add': validate,
|
||||
'delete': validate
|
||||
};
|
||||
|
||||
decorate(router, Sbc, ['add', 'delete'], preconditions);
|
||||
|
||||
/* list */
|
||||
router.get('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
const service_provider_sid = req.query.service_provider_sid;
|
||||
/*
|
||||
let service_provider_sid = req.query.service_provider_sid;
|
||||
|
||||
if (req.user.hasAccountAuth) {
|
||||
const [r] = await promisePool.query('SELECT * from accounts WHERE account_sid = ?', req.user.account_sid);
|
||||
if (0 === r.length) throw new Error('invalid account_sid');
|
||||
if (0 === r.length) throw new DbErrorBadRequest('invalid account_sid');
|
||||
|
||||
service_provider_sid = r[0].service_provider_sid;
|
||||
}
|
||||
if (!service_provider_sid) throw new DbErrorBadRequest('missing service_provider_sid in query');
|
||||
*/
|
||||
const results = await Sbc.retrieveAll(service_provider_sid);
|
||||
|
||||
if (req.user.hasServiceProviderAuth) {
|
||||
service_provider_sid = req.user.service_provider_sid;
|
||||
}
|
||||
|
||||
/** generally, we have a global set of SBCs that all accounts use.
|
||||
* However, we can have a set of SBCs that are specific for use by a service provider.
|
||||
*/
|
||||
let results = await Sbc.retrieveAll(service_provider_sid);
|
||||
if (results.length === 0) results = await Sbc.retrieveAll();
|
||||
res.status(200).json(results);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const router = require('express').Router();
|
||||
const {promisePool} = require('../../db');
|
||||
const {DbErrorUnprocessableRequest, DbErrorForbidden} = require('../../utils/errors');
|
||||
const {DbErrorForbidden} = require('../../utils/errors');
|
||||
const Webhook = require('../../models/webhook');
|
||||
const ServiceProvider = require('../../models/service-provider');
|
||||
const Account = require('../../models/account');
|
||||
@@ -8,7 +8,11 @@ const VoipCarrier = require('../../models/voip-carrier');
|
||||
const Application = require('../../models/application');
|
||||
const PhoneNumber = require('../../models/phone-number');
|
||||
const ApiKey = require('../../models/api-key');
|
||||
const {hasServiceProviderPermissions, parseServiceProviderSid, parseVoipCarrierSid} = require('./utils');
|
||||
const {
|
||||
hasServiceProviderPermissions,
|
||||
parseServiceProviderSid,
|
||||
parseVoipCarrierSid,
|
||||
} = require('./utils');
|
||||
const sysError = require('../error');
|
||||
const decorate = require('./decorate');
|
||||
const preconditions = {
|
||||
@@ -42,18 +46,11 @@ async function validateRetrieve(req) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.user.hasScope('service_provider')) {
|
||||
if (service_provider_sid === req.user.service_provider_sid) return ;
|
||||
if (req.user.hasScope('service_provider') || req.user.hasScope('account')) {
|
||||
if (service_provider_sid === req.user.service_provider_sid) return;
|
||||
}
|
||||
|
||||
if (req.user.hasScope('account')) {
|
||||
/* allow account users to retrieve service provider data from parent SP */
|
||||
const sid = req.user.account_sid;
|
||||
const [r] = await promisePool.execute('SELECT service_provider_sid from accounts WHERE account_sid = ?', [sid]);
|
||||
if (r.length === 1 && r[0].service_provider_sid === req.user.service_provider_sid) return;
|
||||
}
|
||||
|
||||
throw new DbErrorForbidden('insufficient permissions to update service provider');
|
||||
throw new DbErrorForbidden('insufficient permissions');
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
@@ -84,14 +81,10 @@ async function noActiveAccountsOrUsers(req, sid) {
|
||||
}
|
||||
const activeAccounts = await ServiceProvider.getForeignKeyReferences('accounts.service_provider_sid', sid);
|
||||
const activeUsers = await ServiceProvider.getForeignKeyReferences('users.service_provider_sid', sid);
|
||||
if (activeAccounts > 0 && activeUsers > 0) throw new DbErrorUnprocessableRequest(
|
||||
'cannot delete service provider with active accounts or users'
|
||||
);
|
||||
if (activeAccounts > 0 && activeUsers > 0) throw new DbErrorForbidden('insufficient privileges');
|
||||
|
||||
if (activeAccounts > 0) throw new DbErrorUnprocessableRequest('cannot delete service provider with active accounts');
|
||||
if (activeUsers > 0) throw new DbErrorUnprocessableRequest(
|
||||
'cannot delete service provider with active service provider level users'
|
||||
);
|
||||
if (activeAccounts > 0) throw new DbErrorForbidden('insufficient privileges');
|
||||
if (activeUsers > 0) throw new DbErrorForbidden('insufficient privileges');
|
||||
|
||||
/* ok we can delete -- no active accounts. remove carriers and speech credentials */
|
||||
await promisePool.execute('DELETE from speech_credentials WHERE service_provider_sid = ?', [sid]);
|
||||
@@ -122,6 +115,7 @@ router.get('/:sid/Accounts', async(req, res) => {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/:sid/Applications', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
@@ -250,6 +244,7 @@ router.get('/', async(req, res) => {
|
||||
router.get('/:sid', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
await validateRetrieve(req);
|
||||
const sid = parseServiceProviderSid(req);
|
||||
const results = await ServiceProvider.retrieve(sid);
|
||||
if (results.length === 0) return res.status(404).end();
|
||||
|
||||
@@ -6,8 +6,14 @@ const {verifyPassword} = require('../../utils/password-utils');
|
||||
const {cacheClient} = require('../../helpers');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const sysError = require('../error');
|
||||
const retrievePermissionsSql = `
|
||||
SELECT p.name
|
||||
FROM permissions p, user_permissions up
|
||||
WHERE up.permission_sid = p.permission_sid
|
||||
AND up.user_sid = ?
|
||||
`;
|
||||
|
||||
const validateRequest = async(req) => {
|
||||
const validateRequest = (req) => {
|
||||
const {email, password} = req.body || {};
|
||||
|
||||
/* check required properties are there */
|
||||
@@ -53,6 +59,7 @@ router.post('/', async(req, res) => {
|
||||
email: user.email,
|
||||
phone: user.phone,
|
||||
account_sid: user.account_sid,
|
||||
service_provider_sid: a[0].service_provider_sid,
|
||||
force_change: !!user.force_change,
|
||||
provider: user.provider,
|
||||
provider_userid: user.provider_userid,
|
||||
@@ -65,12 +72,22 @@ router.post('/', async(req, res) => {
|
||||
pristine: false
|
||||
});
|
||||
|
||||
const [p] = await promisePool.query(retrievePermissionsSql, user.user_sid);
|
||||
const permissions = p.map((x) => x.name);
|
||||
|
||||
const expiresIn = parseInt(process.env.JWT_EXPIRES_IN || 60) * 60;
|
||||
// generate a json web token for this session
|
||||
const token = jwt.sign({
|
||||
const payload = {
|
||||
scope: 'account',
|
||||
permissions,
|
||||
user_sid: userProfile.user_sid,
|
||||
account_sid: userProfile.account_sid
|
||||
}, process.env.JWT_SECRET, { expiresIn });
|
||||
account_sid: userProfile.account_sid,
|
||||
service_provider_sid: userProfile.service_provider_sid
|
||||
};
|
||||
const token = jwt.sign(payload,
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn }
|
||||
);
|
||||
|
||||
logger.debug({
|
||||
user_sid: userProfile.user_sid,
|
||||
|
||||
@@ -1,11 +1,46 @@
|
||||
const router = require('express').Router();
|
||||
const SipGateway = require('../../models/sip-gateway');
|
||||
const {DbErrorBadRequest, DbErrorUnprocessableRequest} = require('../../utils/errors');
|
||||
const {DbErrorBadRequest, DbErrorForbidden} = require('../../utils/errors');
|
||||
//const {parseSipGatewaySid} = require('./utils');
|
||||
const decorate = require('./decorate');
|
||||
const sysError = require('../error');
|
||||
|
||||
const checkUserScope = async(req, voip_carrier_sid) => {
|
||||
const {lookupCarrierBySid} = req.app.locals;
|
||||
if (!voip_carrier_sid) {
|
||||
throw new DbErrorBadRequest('missing voip_carrier_sid');
|
||||
}
|
||||
|
||||
if (req.user.hasAdminAuth) return;
|
||||
if (req.user.hasAccountAuth) {
|
||||
const carrier = await lookupCarrierBySid(voip_carrier_sid);
|
||||
if (!carrier) throw new DbErrorBadRequest('invalid voip_carrier_sid');
|
||||
|
||||
if ((!carrier.service_provider_sid || carrier.service_provider_sid === req.user.service_provider_sid) &&
|
||||
(!carrier.account_sid || carrier.account_sid === req.user.account_sid)) {
|
||||
|
||||
if (req.method !== 'GET' && !carrier.account_sid) {
|
||||
throw new DbErrorForbidden('insufficient privileges');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (req.user.hasServiceProviderAuth) {
|
||||
const carrier = await lookupCarrierBySid(voip_carrier_sid);
|
||||
if (!carrier) {
|
||||
throw new DbErrorBadRequest('invalid voip_carrier_sid');
|
||||
}
|
||||
|
||||
if (carrier.service_provider_sid === req.user.service_provider_sid) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new DbErrorForbidden('insufficient privileges');
|
||||
};
|
||||
|
||||
const validate = async(req, sid) => {
|
||||
const {lookupCarrierBySid, lookupSipGatewayBySid} = req.app.locals;
|
||||
const {lookupSipGatewayBySid} = req.app.locals;
|
||||
let voip_carrier_sid;
|
||||
|
||||
if (sid) {
|
||||
@@ -17,13 +52,7 @@ 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 (req.hasAccountAuth) {
|
||||
const carrier = await lookupCarrierBySid(voip_carrier_sid);
|
||||
if (!carrier) throw new DbErrorBadRequest('invalid voip_carrier_sid');
|
||||
if (carrier.account_sid !== req.user.account_sid) {
|
||||
throw new DbErrorUnprocessableRequest('user can not add gateway for voip_carrier belonging to other account');
|
||||
}
|
||||
}
|
||||
await checkUserScope(req, voip_carrier_sid);
|
||||
};
|
||||
|
||||
const preconditions = {
|
||||
@@ -39,6 +68,7 @@ router.get('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
const voip_carrier_sid = req.query.voip_carrier_sid;
|
||||
try {
|
||||
await checkUserScope(req, voip_carrier_sid);
|
||||
if (!voip_carrier_sid) {
|
||||
logger.info('GET /SipGateways missing voip_carrier_sid param');
|
||||
return res.status(400).json({message: 'missing voip_carrier_sid query param'});
|
||||
|
||||
@@ -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})`;
|
||||
|
||||
@@ -1,11 +1,38 @@
|
||||
const router = require('express').Router();
|
||||
const SmppGateway = require('../../models/smpp-gateway');
|
||||
const {DbErrorBadRequest, DbErrorUnprocessableRequest} = require('../../utils/errors');
|
||||
const {DbErrorBadRequest, DbErrorForbidden} = require('../../utils/errors');
|
||||
const decorate = require('./decorate');
|
||||
const sysError = require('../error');
|
||||
|
||||
const checkUserScope = async(req, voip_carrier_sid) => {
|
||||
const {lookupCarrierBySid} = req.app.locals;
|
||||
if (!voip_carrier_sid) {
|
||||
throw new DbErrorBadRequest('missing voip_carrier_sid');
|
||||
}
|
||||
|
||||
if (req.user.hasAdminAuth) return;
|
||||
|
||||
if (req.user.hasAccountAuth) {
|
||||
const carrier = await lookupCarrierBySid(voip_carrier_sid);
|
||||
if (!carrier) throw new DbErrorBadRequest('invalid voip_carrier_sid');
|
||||
|
||||
if ((!carrier.service_provider_sid || carrier.service_provider_sid === req.user.service_provider_sid) &&
|
||||
(!carrier.account_sid || carrier.account_sid === req.user.account_sid)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (req.user.hasServiceProviderAuth) {
|
||||
const carrier = await lookupCarrierBySid(voip_carrier_sid);
|
||||
if (!carrier) throw new DbErrorBadRequest('invalid voip_carrier_sid');
|
||||
if (carrier.service_provider_sid === req.user.service_provider_sid) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new DbErrorForbidden('insufficient privileges');
|
||||
};
|
||||
|
||||
const validate = async(req, sid) => {
|
||||
const {lookupCarrierBySid, lookupSmppGatewayBySid} = req.app.locals;
|
||||
const {lookupSmppGatewayBySid} = req.app.locals;
|
||||
let voip_carrier_sid;
|
||||
|
||||
if (sid) {
|
||||
@@ -17,13 +44,8 @@ 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 (req.hasAccountAuth) {
|
||||
const carrier = await lookupCarrierBySid(voip_carrier_sid);
|
||||
if (!carrier) throw new DbErrorBadRequest('invalid voip_carrier_sid');
|
||||
if (carrier.account_sid !== req.user.account_sid) {
|
||||
throw new DbErrorUnprocessableRequest('user can not add gateway for voip_carrier belonging to other account');
|
||||
}
|
||||
}
|
||||
|
||||
await checkUserScope(req, voip_carrier_sid);
|
||||
};
|
||||
|
||||
const preconditions = {
|
||||
@@ -39,6 +61,7 @@ router.get('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
const voip_carrier_sid = req.query.voip_carrier_sid;
|
||||
try {
|
||||
await checkUserScope(req, voip_carrier_sid);
|
||||
if (!voip_carrier_sid) {
|
||||
logger.info('GET /SmppGateways missing voip_carrier_sid param');
|
||||
return res.status(400).json({message: 'missing voip_carrier_sid query param'});
|
||||
|
||||
@@ -14,20 +14,14 @@ const getFsUrl = async(logger, retrieveSet, setName, provider) => {
|
||||
logger.info('No available feature servers to handle createCall API request');
|
||||
return ;
|
||||
}
|
||||
const ip = stripPort(fs[idx++ % fs.length]);
|
||||
logger.info({fs}, `feature servers available for createCall API request, selecting ${ip}`);
|
||||
return `http://${ip}:3000/v1/messaging/${provider}`;
|
||||
const f = fs[idx++ % fs.length];
|
||||
logger.info({fs}, `feature servers available for createCall API request, selecting ${f}`);
|
||||
return `${f}/v1/messaging/${provider}`;
|
||||
} catch (err) {
|
||||
logger.error({err}, 'getFsUrl: error retreving feature servers from redis');
|
||||
}
|
||||
};
|
||||
|
||||
const stripPort = (hostport) => {
|
||||
const arr = /^(.*):(.*)$/.exec(hostport);
|
||||
if (arr) return arr[1];
|
||||
return hostport;
|
||||
};
|
||||
|
||||
const doSendResponse = async(res, respondFn, body) => {
|
||||
if (typeof respondFn === 'number') res.sendStatus(respondFn);
|
||||
else if (typeof respondFn !== 'function') res.sendStatus(200);
|
||||
@@ -44,7 +38,7 @@ router.post('/:provider', async(req, res) => {
|
||||
lookupAppByPhoneNumber,
|
||||
logger
|
||||
} = req.app.locals;
|
||||
const setName = `${process.env.JAMBONES_CLUSTER_ID || 'default'}:active-fs`;
|
||||
const setName = `${process.env.JAMBONES_CLUSTER_ID || 'default'}:fs-service-url`;
|
||||
logger.debug({path: req.path, body: req.body}, 'incomingSMS from carrier');
|
||||
|
||||
// search for provider module
|
||||
|
||||
@@ -5,7 +5,11 @@ const SpeechCredential = require('../../models/speech-credential');
|
||||
const sysError = require('../error');
|
||||
const {decrypt, encrypt} = require('../../utils/encrypt-decrypt');
|
||||
const {parseAccountSid, parseServiceProviderSid, parseSpeechCredentialSid} = require('./utils');
|
||||
const {DbErrorUnprocessableRequest} = require('../../utils/errors');
|
||||
const {decryptCredential, testWhisper, testDeepgramTTS,
|
||||
getLanguagesAndVoicesForVendor,
|
||||
testPlayHT,
|
||||
testRimelabs} = require('../../utils/speech-utils');
|
||||
const {DbErrorUnprocessableRequest, DbErrorForbidden, DbErrorBadRequest} = require('../../utils/errors');
|
||||
const {
|
||||
testGoogleTts,
|
||||
testGoogleStt,
|
||||
@@ -19,18 +23,86 @@ const {
|
||||
testDeepgramStt,
|
||||
testSonioxStt,
|
||||
testIbmTts,
|
||||
testIbmStt
|
||||
testIbmStt,
|
||||
testElevenlabs,
|
||||
testAssemblyStt
|
||||
} = require('../../utils/speech-utils');
|
||||
const {promisePool} = require('../../db');
|
||||
|
||||
const obscureKey = (key) => {
|
||||
const key_spoiler_length = 6;
|
||||
const key_spoiler_char = 'X';
|
||||
const validateAdd = async(req) => {
|
||||
const account_sid = parseAccountSid(req);
|
||||
const service_provider_sid = parseServiceProviderSid(req);
|
||||
|
||||
if (key.length <= key_spoiler_length) {
|
||||
return key;
|
||||
if (service_provider_sid) {
|
||||
if (req.user.hasServiceProviderAuth && service_provider_sid !== req.user.service_provider_sid) {
|
||||
throw new DbErrorForbidden('Insufficient privileges');
|
||||
}
|
||||
if (req.user.hasAccountAuth && service_provider_sid !== req.user.service_provider_sid &&
|
||||
req.body.account_sid !== req.user.account_sid) {
|
||||
throw new DbErrorForbidden('Insufficient privileges');
|
||||
}
|
||||
}
|
||||
|
||||
return `${key.slice(0, key_spoiler_length)}${key_spoiler_char.repeat(key.length - key_spoiler_length)}`;
|
||||
if (account_sid) {
|
||||
if (req.user.hasAccountAuth && account_sid !== req.user.account_sid) {
|
||||
throw new DbErrorForbidden('Insufficient privileges');
|
||||
}
|
||||
|
||||
const [r] = await promisePool.execute(
|
||||
'SELECT service_provider_sid from accounts WHERE account_sid = ?', [account_sid]
|
||||
);
|
||||
|
||||
if (req.user.hasServiceProviderAuth && r[0].service_provider_sid !== req.user.service_provider_sid) {
|
||||
throw new DbErrorForbidden('Insufficient privileges');
|
||||
}
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
const validateRetrieveUpdateDelete = async(req, speech_credentials) => {
|
||||
if (req.user.hasServiceProviderAuth && speech_credentials[0].service_provider_sid !== req.user.service_provider_sid) {
|
||||
throw new DbErrorForbidden('Insufficient privileges');
|
||||
}
|
||||
|
||||
if (req.user.hasAccountAuth && speech_credentials[0].account_sid !== req.user.account_sid) {
|
||||
throw new DbErrorForbidden('Insufficient privileges');
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
const validateRetrieveList = async(req) => {
|
||||
const service_provider_sid = parseServiceProviderSid(req);
|
||||
|
||||
if (service_provider_sid) {
|
||||
if ((req.user.hasServiceProviderAuth || req.user.hasAccountAuth) &&
|
||||
service_provider_sid !== req.user.service_provider_sid) {
|
||||
throw new DbErrorForbidden('Insufficient privileges');
|
||||
}
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
const validateTest = async(req, speech_credentials) => {
|
||||
if (req.user.hasAdminAuth) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!req.user.hasAdminAuth && speech_credentials.service_provider_sid !== req.user.service_provider_sid) {
|
||||
throw new DbErrorForbidden('Insufficient privileges');
|
||||
}
|
||||
|
||||
if (speech_credentials.service_provider_sid === req.user.service_provider_sid) {
|
||||
if (req.user.hasServiceProviderAuth) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.user.hasAccountAuth && (!speech_credentials.account_sid ||
|
||||
speech_credentials.account_sid === req.user.account_sid)) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new DbErrorForbidden('Insufficient privileges');
|
||||
}
|
||||
};
|
||||
|
||||
const encryptCredential = (obj) => {
|
||||
@@ -41,15 +113,20 @@ const encryptCredential = (obj) => {
|
||||
secret_access_key,
|
||||
aws_region,
|
||||
api_key,
|
||||
role_arn,
|
||||
region,
|
||||
client_id,
|
||||
secret,
|
||||
nuance_tts_uri,
|
||||
nuance_stt_uri,
|
||||
deepgram_stt_uri,
|
||||
deepgram_stt_use_tls,
|
||||
use_custom_tts,
|
||||
custom_tts_endpoint,
|
||||
custom_tts_endpoint_url,
|
||||
use_custom_stt,
|
||||
custom_stt_endpoint,
|
||||
custom_stt_endpoint_url,
|
||||
tts_api_key,
|
||||
tts_region,
|
||||
stt_api_key,
|
||||
@@ -58,7 +135,12 @@ const encryptCredential = (obj) => {
|
||||
instance_id,
|
||||
custom_stt_url,
|
||||
custom_tts_url,
|
||||
auth_token = ''
|
||||
auth_token = '',
|
||||
cobalt_server_uri,
|
||||
model_id,
|
||||
user_id,
|
||||
voice_engine,
|
||||
options
|
||||
} = obj;
|
||||
|
||||
switch (vendor) {
|
||||
@@ -74,22 +156,33 @@ 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':
|
||||
assert(region, 'invalid azure speech credential: region is required');
|
||||
assert(api_key, 'invalid azure speech credential: api_key is required');
|
||||
if (!custom_tts_endpoint_url && !custom_stt_endpoint_url) {
|
||||
assert(region, 'invalid azure speech credential: region is required');
|
||||
assert(api_key, 'invalid azure speech credential: api_key is required');
|
||||
}
|
||||
const azureData = JSON.stringify({
|
||||
region,
|
||||
api_key,
|
||||
...(region && {region}),
|
||||
...(api_key && {api_key}),
|
||||
use_custom_tts,
|
||||
custom_tts_endpoint,
|
||||
custom_tts_endpoint_url,
|
||||
use_custom_stt,
|
||||
custom_stt_endpoint
|
||||
custom_stt_endpoint,
|
||||
custom_stt_endpoint_url
|
||||
});
|
||||
return encrypt(azureData);
|
||||
|
||||
@@ -106,8 +199,11 @@ const encryptCredential = (obj) => {
|
||||
return encrypt(nuanceData);
|
||||
|
||||
case 'deepgram':
|
||||
assert(api_key, 'invalid deepgram speech credential: api_key is required');
|
||||
const deepgramData = JSON.stringify({api_key});
|
||||
// API key is optional if onprem
|
||||
if (!deepgram_stt_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});
|
||||
return encrypt(deepgramData);
|
||||
|
||||
case 'ibm':
|
||||
@@ -124,6 +220,41 @@ const encryptCredential = (obj) => {
|
||||
const sonioxData = JSON.stringify({api_key});
|
||||
return encrypt(sonioxData);
|
||||
|
||||
case 'cobalt':
|
||||
assert(cobalt_server_uri, 'invalid cobalt speech credential: cobalt_server_uri is required');
|
||||
const cobaltData = JSON.stringify({cobalt_server_uri});
|
||||
return encrypt(cobaltData);
|
||||
|
||||
case 'elevenlabs':
|
||||
assert(api_key, 'invalid elevenLabs speech credential: api_key is required');
|
||||
assert(model_id, 'invalid elevenLabs speech credential: model_id is required');
|
||||
const elevenlabsData = JSON.stringify({api_key, model_id, options});
|
||||
return encrypt(elevenlabsData);
|
||||
|
||||
case 'playht':
|
||||
assert(api_key, 'invalid playht speech credential: api_key is required');
|
||||
assert(user_id, 'invalid playht speech credential: user_id is required');
|
||||
assert(voice_engine, 'invalid voice_engine speech credential: voice_engine is required');
|
||||
const playhtData = JSON.stringify({api_key, user_id, voice_engine, options});
|
||||
return encrypt(playhtData);
|
||||
|
||||
case 'rimelabs':
|
||||
assert(api_key, 'invalid rimelabs speech credential: api_key is required');
|
||||
assert(model_id, 'invalid rimelabs speech credential: model_id is required');
|
||||
const rimelabsData = JSON.stringify({api_key, model_id, options});
|
||||
return encrypt(rimelabsData);
|
||||
|
||||
case 'assemblyai':
|
||||
assert(api_key, 'invalid assemblyai speech credential: api_key is required');
|
||||
const assemblyaiData = JSON.stringify({api_key});
|
||||
return encrypt(assemblyaiData);
|
||||
|
||||
case 'whisper':
|
||||
assert(api_key, 'invalid whisper speech credential: api_key is required');
|
||||
assert(model_id, 'invalid whisper speech credential: model_id is required');
|
||||
const whisperData = JSON.stringify({api_key, model_id});
|
||||
return encrypt(whisperData);
|
||||
|
||||
default:
|
||||
if (vendor.startsWith('custom:')) {
|
||||
const customData = JSON.stringify({auth_token, custom_stt_url, custom_tts_url});
|
||||
@@ -141,11 +272,14 @@ router.post('/', async(req, res) => {
|
||||
use_for_stt,
|
||||
use_for_tts,
|
||||
vendor,
|
||||
label
|
||||
} = req.body;
|
||||
const account_sid = req.user.account_sid || req.body.account_sid;
|
||||
const service_provider_sid = req.user.service_provider_sid ||
|
||||
req.body.service_provider_sid || parseServiceProviderSid(req);
|
||||
|
||||
await validateAdd(req);
|
||||
|
||||
if (!account_sid) {
|
||||
if (!req.user.hasServiceProviderAuth && !req.user.hasAdminAuth) {
|
||||
logger.error('POST /SpeechCredentials invalid credentials');
|
||||
@@ -153,11 +287,21 @@ router.post('/', async(req, res) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Check if vendor and label is already used for account or SP
|
||||
if (label) {
|
||||
const existingSpeech = await SpeechCredential.getSpeechCredentialsByVendorAndLabel(
|
||||
service_provider_sid, account_sid, vendor, label);
|
||||
if (existingSpeech.length > 0) {
|
||||
throw new DbErrorUnprocessableRequest(`Label ${label} is already in use for another speech credential`);
|
||||
}
|
||||
}
|
||||
|
||||
const encrypted_credential = encryptCredential(req.body);
|
||||
const uuid = await SpeechCredential.make({
|
||||
account_sid,
|
||||
service_provider_sid,
|
||||
vendor,
|
||||
label,
|
||||
use_for_tts,
|
||||
use_for_stt,
|
||||
credential: encrypted_credential
|
||||
@@ -175,8 +319,11 @@ router.get('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
|
||||
try {
|
||||
const account_sid = parseAccountSid(req) || req.user.account_sid;
|
||||
const service_provider_sid = parseServiceProviderSid(req) || req.user.service_provider_sid;
|
||||
const account_sid = parseAccountSid(req) ? parseAccountSid(req) : req.user.account_sid;
|
||||
const service_provider_sid = parseServiceProviderSid(req);
|
||||
|
||||
await validateRetrieveList(req);
|
||||
|
||||
const credsAccount = account_sid ? await SpeechCredential.retrieveAll(account_sid) : [];
|
||||
const credsSP = service_provider_sid ?
|
||||
await SpeechCredential.retrieveAllForSP(service_provider_sid) :
|
||||
@@ -190,65 +337,16 @@ router.get('/', async(req, res) => {
|
||||
|
||||
res.status(200).json(creds.map((c) => {
|
||||
const {credential, ...obj} = c;
|
||||
if ('google' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
const key_header = '-----BEGIN PRIVATE KEY-----\n';
|
||||
const obscured = {
|
||||
...o,
|
||||
private_key: `${key_header}${obscureKey(o.private_key.slice(key_header.length, o.private_key.length))}`
|
||||
};
|
||||
obj.service_key = obscured;
|
||||
}
|
||||
else if ('aws' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.access_key_id = o.access_key_id;
|
||||
obj.secret_access_key = obscureKey(o.secret_access_key);
|
||||
obj.aws_region = o.aws_region;
|
||||
logger.info({obj, o}, 'retrieving aws speech credential');
|
||||
}
|
||||
else if ('microsoft' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = obscureKey(o.api_key);
|
||||
obj.region = o.region;
|
||||
obj.use_custom_tts = o.use_custom_tts;
|
||||
obj.custom_tts_endpoint = o.custom_tts_endpoint;
|
||||
obj.use_custom_stt = o.use_custom_stt;
|
||||
obj.custom_stt_endpoint = o.custom_stt_endpoint;
|
||||
logger.info({obj, o}, 'retrieving azure speech credential');
|
||||
}
|
||||
else if ('wellsaid' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = obscureKey(o.api_key);
|
||||
}
|
||||
else if ('nuance' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.client_id = o.client_id;
|
||||
obj.secret = o.secret ? obscureKey(o.secret) : null;
|
||||
}
|
||||
else if ('deepgram' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = obscureKey(o.api_key);
|
||||
}
|
||||
else if ('ibm' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.tts_api_key = obscureKey(o.tts_api_key);
|
||||
obj.tts_region = o.tts_region;
|
||||
obj.stt_api_key = obscureKey(o.stt_api_key);
|
||||
obj.stt_region = o.stt_region;
|
||||
obj.instance_id = o.instance_id;
|
||||
} else if ('nvidia' == obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.riva_server_uri = o.riva_server_uri;
|
||||
}
|
||||
else if ('soniox' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = obscureKey(o.api_key);
|
||||
}
|
||||
else if (obj.vendor.startsWith('custom:')) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.auth_token = obscureKey(o.auth_token);
|
||||
obj.custom_stt_url = o.custom_stt_url;
|
||||
obj.custom_tts_url = o.custom_tts_url;
|
||||
|
||||
decryptCredential(obj, credential, logger);
|
||||
|
||||
if (req.user.hasAccountAuth && obj.account_sid === null) {
|
||||
delete obj.api_key;
|
||||
delete obj.secret_access_key;
|
||||
delete obj.secret;
|
||||
delete obj.auth_token;
|
||||
delete obj.stt_api_key;
|
||||
delete obj.tts_api_key;
|
||||
}
|
||||
return obj;
|
||||
}));
|
||||
@@ -266,67 +364,21 @@ router.get('/:sid', async(req, res) => {
|
||||
const sid = parseSpeechCredentialSid(req);
|
||||
const cred = await SpeechCredential.retrieve(sid);
|
||||
if (0 === cred.length) return res.sendStatus(404);
|
||||
|
||||
await validateRetrieveUpdateDelete(req, cred);
|
||||
|
||||
const {credential, ...obj} = cred[0];
|
||||
if ('google' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
const key_header = '-----BEGIN PRIVATE KEY-----\n';
|
||||
const obscured = {
|
||||
...o,
|
||||
private_key: `${key_header}${obscureKey(o.private_key.slice(key_header.length, o.private_key.length))}`
|
||||
};
|
||||
obj.service_key = JSON.stringify(obscured);
|
||||
}
|
||||
else if ('aws' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.access_key_id = o.access_key_id;
|
||||
obj.secret_access_key = obscureKey(o.secret_access_key);
|
||||
obj.aws_region = o.aws_region;
|
||||
}
|
||||
else if ('microsoft' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = obscureKey(o.api_key);
|
||||
obj.region = o.region;
|
||||
obj.use_custom_tts = o.use_custom_tts;
|
||||
obj.custom_tts_endpoint = o.custom_tts_endpoint;
|
||||
obj.use_custom_stt = o.use_custom_stt;
|
||||
obj.custom_stt_endpoint = o.custom_stt_endpoint;
|
||||
}
|
||||
else if ('wellsaid' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = obscureKey(o.api_key);
|
||||
}
|
||||
else if ('nuance' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.client_id = o.client_id;
|
||||
obj.secret = o.secret ? obscureKey(o.secret) : null;
|
||||
obj.nuance_tts_uri = o.nuance_tts_uri;
|
||||
obj.nuance_stt_uri = o.nuance_stt_uri;
|
||||
}
|
||||
else if ('deepgram' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = obscureKey(o.api_key);
|
||||
}
|
||||
else if ('ibm' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.tts_api_key = obscureKey(o.tts_api_key);
|
||||
obj.tts_region = o.tts_region;
|
||||
obj.stt_api_key = obscureKey(o.stt_api_key);
|
||||
obj.stt_region = o.stt_region;
|
||||
obj.instance_id = o.instance_id;
|
||||
} else if ('nvidia' == obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.riva_server_uri = o.riva_server_uri;
|
||||
}
|
||||
else if ('soniox' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = obscureKey(o.api_key);
|
||||
}
|
||||
else if (obj.vendor.startsWith('custom:')) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.auth_token = obscureKey(o.auth_token);
|
||||
obj.custom_stt_url = o.custom_stt_url;
|
||||
obj.custom_tts_url = o.custom_tts_url;
|
||||
decryptCredential(obj, credential, logger);
|
||||
|
||||
if (req.user.hasAccountAuth && obj.account_sid === null) {
|
||||
delete obj.api_key;
|
||||
delete obj.secret_access_key;
|
||||
delete obj.secret;
|
||||
delete obj.auth_token;
|
||||
delete obj.stt_api_key;
|
||||
delete obj.tts_api_key;
|
||||
}
|
||||
|
||||
res.status(200).json(obj);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
@@ -340,6 +392,8 @@ router.delete('/:sid', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
const sid = parseSpeechCredentialSid(req);
|
||||
const cred = await SpeechCredential.retrieve(sid);
|
||||
await validateRetrieveUpdateDelete(req, cred);
|
||||
const count = await SpeechCredential.remove(sid);
|
||||
if (0 === count) return res.sendStatus(404);
|
||||
res.sendStatus(204);
|
||||
@@ -372,14 +426,27 @@ router.put('/:sid', async(req, res) => {
|
||||
/* update the credential if provided */
|
||||
try {
|
||||
const cred = await SpeechCredential.retrieve(sid);
|
||||
|
||||
await validateRetrieveUpdateDelete(req, cred);
|
||||
|
||||
if (1 === cred.length) {
|
||||
const {credential, vendor} = cred[0];
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
const {
|
||||
use_custom_tts,
|
||||
custom_tts_endpoint,
|
||||
custom_tts_endpoint_url,
|
||||
use_custom_stt,
|
||||
custom_stt_endpoint
|
||||
custom_stt_endpoint,
|
||||
custom_stt_endpoint_url,
|
||||
custom_stt_url,
|
||||
custom_tts_url,
|
||||
cobalt_server_uri,
|
||||
model_id,
|
||||
voice_engine,
|
||||
options,
|
||||
deepgram_stt_uri,
|
||||
deepgram_stt_use_tls,
|
||||
} = req.body;
|
||||
|
||||
const newCred = {
|
||||
@@ -389,13 +456,23 @@ router.put('/:sid', async(req, res) => {
|
||||
aws_region,
|
||||
use_custom_tts,
|
||||
custom_tts_endpoint,
|
||||
custom_tts_endpoint_url,
|
||||
use_custom_stt,
|
||||
custom_stt_endpoint,
|
||||
custom_stt_endpoint_url,
|
||||
stt_region,
|
||||
tts_region,
|
||||
riva_server_uri,
|
||||
nuance_stt_uri,
|
||||
nuance_tts_uri
|
||||
nuance_tts_uri,
|
||||
custom_stt_url,
|
||||
custom_tts_url,
|
||||
cobalt_server_uri,
|
||||
model_id,
|
||||
voice_engine,
|
||||
options,
|
||||
deepgram_stt_uri,
|
||||
deepgram_stt_use_tls,
|
||||
};
|
||||
logger.info({o, newCred}, 'updating speech credential with this new credential');
|
||||
obj.credential = encryptCredential(newCred);
|
||||
@@ -424,12 +501,15 @@ router.put('/:sid', async(req, res) => {
|
||||
* Test a credential
|
||||
*/
|
||||
router.get('/:sid/test', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
const {logger, synthAudio} = req.app.locals;
|
||||
try {
|
||||
const sid = parseSpeechCredentialSid(req);
|
||||
const creds = await SpeechCredential.retrieve(sid);
|
||||
|
||||
if (!creds || 0 === creds.length) return res.sendStatus(404);
|
||||
|
||||
await validateTest(req, creds[0]);
|
||||
|
||||
const cred = creds[0];
|
||||
const credential = JSON.parse(decrypt(cred.credential));
|
||||
const results = {
|
||||
@@ -469,12 +549,13 @@ router.get('/:sid/test', async(req, res) => {
|
||||
}
|
||||
}
|
||||
else if (cred.vendor === 'aws') {
|
||||
const {getTtsVoices, getAwsAuthToken} = req.app.locals;
|
||||
if (cred.use_for_tts) {
|
||||
const {getTtsVoices} = req.app.locals;
|
||||
try {
|
||||
await testAwsTts(logger, getTtsVoices, {
|
||||
accessKeyId: credential.access_key_id,
|
||||
secretAccessKey: credential.secret_access_key,
|
||||
roleArn: credential.role_arn,
|
||||
region: credential.aws_region || process.env.AWS_REGION
|
||||
});
|
||||
results.tts.status = 'ok';
|
||||
@@ -486,9 +567,10 @@ router.get('/:sid/test', async(req, res) => {
|
||||
}
|
||||
if (cred.use_for_stt) {
|
||||
try {
|
||||
await testAwsStt(logger, {
|
||||
await testAwsStt(logger, getAwsAuthToken, {
|
||||
accessKeyId: credential.access_key_id,
|
||||
secretAccessKey: credential.secret_access_key,
|
||||
roleArn: credential.role_arn,
|
||||
region: credential.aws_region || process.env.AWS_REGION
|
||||
});
|
||||
results.stt.status = 'ok';
|
||||
@@ -505,18 +587,22 @@ router.get('/:sid/test', async(req, res) => {
|
||||
region,
|
||||
use_custom_tts,
|
||||
custom_tts_endpoint,
|
||||
custom_tts_endpoint_url,
|
||||
use_custom_stt,
|
||||
custom_stt_endpoint
|
||||
custom_stt_endpoint,
|
||||
custom_stt_endpoint_url
|
||||
} = credential;
|
||||
if (cred.use_for_tts) {
|
||||
try {
|
||||
await testMicrosoftTts(logger, {
|
||||
await testMicrosoftTts(logger, synthAudio, {
|
||||
api_key,
|
||||
region,
|
||||
use_custom_tts,
|
||||
custom_tts_endpoint,
|
||||
custom_tts_endpoint_url,
|
||||
use_custom_stt,
|
||||
custom_stt_endpoint
|
||||
custom_stt_endpoint,
|
||||
custom_stt_endpoint_url
|
||||
});
|
||||
results.tts.status = 'ok';
|
||||
SpeechCredential.ttsTestResult(sid, true);
|
||||
@@ -589,7 +675,17 @@ router.get('/:sid/test', async(req, res) => {
|
||||
}
|
||||
else if (cred.vendor === 'deepgram') {
|
||||
const {api_key} = credential;
|
||||
if (cred.use_for_stt) {
|
||||
if (cred.use_for_tts) {
|
||||
try {
|
||||
await testDeepgramTTS(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 && api_key) {
|
||||
try {
|
||||
await testDeepgramStt(logger, {api_key});
|
||||
results.stt.status = 'ok';
|
||||
@@ -645,6 +741,68 @@ router.get('/:sid/test', async(req, res) => {
|
||||
SpeechCredential.sttTestResult(sid, false);
|
||||
}
|
||||
}
|
||||
} else if (cred.vendor === 'elevenlabs') {
|
||||
const {api_key, model_id} = credential;
|
||||
if (cred.use_for_tts) {
|
||||
try {
|
||||
await testElevenlabs(logger, {api_key, model_id});
|
||||
results.tts.status = 'ok';
|
||||
SpeechCredential.ttsTestResult(sid, true);
|
||||
} catch (err) {
|
||||
results.tts = {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 === '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);
|
||||
}
|
||||
}
|
||||
} else if (cred.vendor === 'assemblyai') {
|
||||
const {api_key} = credential;
|
||||
if (cred.use_for_stt) {
|
||||
try {
|
||||
await testAssemblyStt(logger, {api_key});
|
||||
results.stt.status = 'ok';
|
||||
SpeechCredential.sttTestResult(sid, true);
|
||||
} catch (err) {
|
||||
results.stt = {status: 'fail', reason: err.message};
|
||||
SpeechCredential.sttTestResult(sid, false);
|
||||
}
|
||||
}
|
||||
} else if (cred.vendor === 'whisper') {
|
||||
if (cred.use_for_tts) {
|
||||
try {
|
||||
await testWhisper(logger, synthAudio, credential);
|
||||
results.tts.status = 'ok';
|
||||
SpeechCredential.ttsTestResult(sid, true);
|
||||
} catch (err) {
|
||||
results.tts = {status: 'fail', reason: err.message};
|
||||
SpeechCredential.ttsTestResult(sid, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.status(200).json(results);
|
||||
@@ -654,4 +812,34 @@ router.get('/:sid/test', async(req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Fetch speech voices and languages
|
||||
*/
|
||||
|
||||
router.get('/speech/supportedLanguagesAndVoices', async(req, res) => {
|
||||
const {logger, getTtsVoices} = req.app.locals;
|
||||
try {
|
||||
const {vendor, label} = req.query;
|
||||
if (!vendor) {
|
||||
throw new DbErrorBadRequest('vendor is required');
|
||||
}
|
||||
const account_sid = req.user.account_sid || req.body.account_sid;
|
||||
const service_provider_sid = req.user.service_provider_sid ||
|
||||
req.body.service_provider_sid || parseServiceProviderSid(req);
|
||||
|
||||
const credentials = 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;
|
||||
try {
|
||||
const data = await getLanguagesAndVoicesForVendor(logger, vendor, cred, getTtsVoices);
|
||||
res.status(200).json(data);
|
||||
} catch (err) {
|
||||
throw new DbErrorUnprocessableRequest(err.message);
|
||||
}
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
14
lib/routes/api/system-information.js
Normal file
14
lib/routes/api/system-information.js
Normal file
@@ -0,0 +1,14 @@
|
||||
const router = require('express').Router();
|
||||
const SystemInformation = require('../../models/system-information');
|
||||
|
||||
router.post('/', async(req, res) => {
|
||||
const sysInfo = await SystemInformation.add(req.body);
|
||||
res.status(201).json(sysInfo);
|
||||
});
|
||||
|
||||
router.get('/', async(req, res) => {
|
||||
const [sysInfo] = await SystemInformation.retrieveAll();
|
||||
res.status(200).json(sysInfo || {});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
147
lib/routes/api/tts-cache.js
Normal file
147
lib/routes/api/tts-cache.js
Normal file
@@ -0,0 +1,147 @@
|
||||
const router = require('express').Router();
|
||||
const {
|
||||
parseAccountSid
|
||||
} = require('./utils');
|
||||
const SpeechCredential = require('../../models/speech-credential');
|
||||
const fs = require('fs');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const {DbErrorBadRequest} = require('../../utils/errors');
|
||||
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;
|
||||
const account_sid = parseAccountSid(req);
|
||||
if (account_sid) {
|
||||
await purgeTtsCache({account_sid});
|
||||
} else {
|
||||
await purgeTtsCache();
|
||||
}
|
||||
res.sendStatus(204);
|
||||
});
|
||||
|
||||
router.get('/', async(req, res) => {
|
||||
const {getTtsSize} = req.app.locals;
|
||||
const account_sid = parseAccountSid(req);
|
||||
let size = 0;
|
||||
if (account_sid) {
|
||||
size = await getTtsSize(`tts:${account_sid}:*`);
|
||||
} else {
|
||||
size = await getTtsSize();
|
||||
}
|
||||
res.status(200).json({size});
|
||||
});
|
||||
|
||||
router.post('/Synthesize', async(req, res) => {
|
||||
const {logger, synthAudio} = req.app.locals;
|
||||
try {
|
||||
const accountSid = parseAccountSid(req);
|
||||
const body = req.body;
|
||||
const encodingMp3 = req.body.encodingMp3 || false;
|
||||
if (!body.speech_credential_sid || !body.text || !body.language || !body.voice) {
|
||||
throw new DbErrorBadRequest('speech_credential_sid, text, language, voice are all required');
|
||||
}
|
||||
|
||||
const result = await Account.retrieve(accountSid);
|
||||
if (!result || result.length === 0 || !result[0].is_active) {
|
||||
throw new DbErrorBadRequest(`Account not found for sid ${accountSid}`);
|
||||
}
|
||||
const credentials = await SpeechCredential.retrieve(body.speech_credential_sid);
|
||||
if (!credentials || credentials.length === 0) {
|
||||
throw new
|
||||
DbErrorBadRequest(`There is no available speech credential for ${body.speech_credential_sid}`);
|
||||
}
|
||||
const {credential, ...obj} = credentials[0];
|
||||
|
||||
decryptCredential(obj, credential, logger, false);
|
||||
const cred = getSpeechCredential(obj, logger);
|
||||
|
||||
const { text, language, engine = 'standard' } = body;
|
||||
const salt = uuidv4();
|
||||
/* parse Nuance voices into name and model */
|
||||
let voice = body.voice;
|
||||
let model;
|
||||
if (cred.vendor === 'nuance' && voice) {
|
||||
const arr = /([A-Za-z-]*)\s+-\s+(enhanced|standard)/.exec(voice);
|
||||
if (arr) {
|
||||
voice = arr[1];
|
||||
model = arr[2];
|
||||
}
|
||||
} else if (cred.vendor === 'deepgram') {
|
||||
model = voice;
|
||||
}
|
||||
const stats = {
|
||||
histogram: () => {},
|
||||
increment: () => {},
|
||||
};
|
||||
const { filePath } = await synthAudio(stats, {
|
||||
account_sid: accountSid,
|
||||
text,
|
||||
vendor: cred.vendor,
|
||||
language,
|
||||
voice,
|
||||
engine,
|
||||
model,
|
||||
salt,
|
||||
credentials: cred,
|
||||
disableTtsCache: false,
|
||||
disableTtsStreaming: true
|
||||
});
|
||||
|
||||
let contentType = 'audio/mpeg';
|
||||
|
||||
let readStream = fs.createReadStream(filePath);
|
||||
if (['nuance', 'nvidia'].includes(cred.vendor) ||
|
||||
(
|
||||
(process.env.JAMBONES_TTS_TRIM_SILENCE || !process.env.JAMBONES_DISABLE_TTS_STREAMING) &&
|
||||
['microsoft', 'azure'].includes(cred.vendor)
|
||||
)
|
||||
) {
|
||||
if (encodingMp3) {
|
||||
readStream = pipeline(
|
||||
readStream,
|
||||
new PCMToMP3Encoder({
|
||||
channels: 1,
|
||||
sampleRate: 8000,
|
||||
bitRate: 128
|
||||
}, logger),
|
||||
(err) => {
|
||||
if (err) {
|
||||
logger.error('ttscache/Synthesize failed:', err);
|
||||
if (!res.headersSent) {
|
||||
res.status(500).end('Server error');
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
contentType = 'application/octet-stream';
|
||||
}
|
||||
}
|
||||
res.writeHead(200, {
|
||||
'Content-Type': contentType,
|
||||
});
|
||||
|
||||
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`);
|
||||
});
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,9 +1,9 @@
|
||||
const router = require('express').Router();
|
||||
const User = require('../../models/user');
|
||||
const {DbErrorBadRequest} = require('../../utils/errors');
|
||||
const {DbErrorBadRequest, BadRequestError, DbErrorForbidden} = require('../../utils/errors');
|
||||
const {generateHashedPassword, verifyPassword} = require('../../utils/password-utils');
|
||||
const {promisePool} = require('../../db');
|
||||
const {validatePasswordSettings} = require('./utils');
|
||||
const {validatePasswordSettings, parseUserSid} = require('./utils');
|
||||
const {decrypt} = require('../../utils/encrypt-decrypt');
|
||||
const {cacheClient} = require('../../helpers');
|
||||
const sysError = require('../error');
|
||||
@@ -99,6 +99,48 @@ const validateRequest = async(user_sid, req) => {
|
||||
return user;
|
||||
};
|
||||
|
||||
const getActiveAdminUsers = (users) => {
|
||||
return users.filter((e) => !e.account_sid && !e.service_provider_sid && e.is_active);
|
||||
};
|
||||
|
||||
const ensureUserActionIsAllowed = (req, user) => {
|
||||
if (req.user.hasAdminAuth) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.user.hasServiceProviderAuth && req.user.service_provider_sid === user.service_provider_sid) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.user.hasAccountAuth && req.user.account_sid === user.account_sid) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new DbErrorForbidden('insufficient permissions');
|
||||
};
|
||||
|
||||
const ensureUserDeletionIsAllowed = (req, activeAdminUsers, user) => {
|
||||
try {
|
||||
if (req.user.hasAdminAuth && activeAdminUsers.length === 1 && activeAdminUsers[0].user_sid === user[0].user_sid) {
|
||||
throw new BadRequestError('cannot delete this admin user - there are no other active admin users');
|
||||
}
|
||||
|
||||
ensureUserActionIsAllowed(req, user[0]);
|
||||
return;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const ensureUserRetrievalIsAllowed = (req, user) => {
|
||||
try {
|
||||
ensureUserActionIsAllowed(req, user);
|
||||
return;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
router.get('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
|
||||
@@ -256,22 +298,20 @@ router.get('/me', async(req, res) => {
|
||||
|
||||
router.get('/:user_sid', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
const {user_sid} = req.params;
|
||||
|
||||
try {
|
||||
const user_sid = parseUserSid(req);
|
||||
const [user] = await User.retrieve(user_sid);
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const {hashed_password, ...rest} = user;
|
||||
if (!user) throw new Error('failure retrieving user');
|
||||
|
||||
if (req.user.hasAdminAuth ||
|
||||
req.user.hasAccountAuth && req.user.account_sid === user.account_sid ||
|
||||
req.user.hasServiceProviderAuth && req.user.service_provider_sid === user.service_provider_sid) {
|
||||
res.status(200).json(rest);
|
||||
} else {
|
||||
res.sendStatus(403);
|
||||
if (!user) {
|
||||
throw new Error('failure retrieving user');
|
||||
}
|
||||
|
||||
ensureUserRetrievalIsAllowed(req, user);
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { hashed_password, ...rest } = user;
|
||||
return res.status(200).json(rest);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
@@ -298,8 +338,8 @@ router.put('/:user_sid', async(req, res) => {
|
||||
//if (req.user.user_sid && req.user.user_sid !== user_sid) return res.sendStatus(403);
|
||||
|
||||
if (!hasAdminAuth &&
|
||||
!(hasAccountAuth && req.user.account_sid === user[0].account_sid) &&
|
||||
!(hasServiceProviderAuth && req.user.service_provider_sid === user[0].service_provider_sid) &&
|
||||
!(hasAccountAuth && user[0] && req.user.account_sid === user[0].account_sid) &&
|
||||
!(hasServiceProviderAuth && user[0] && req.user.service_provider_sid === user[0].service_provider_sid) &&
|
||||
(req.user.user_sid && req.user.user_sid !== user_sid)) {
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
@@ -413,12 +453,12 @@ router.post('/', async(req, res) => {
|
||||
|
||||
if (name) {
|
||||
logger.debug({payload}, 'user with this username already exists');
|
||||
return res.status(422).json({msg: 'user with this username already exists'});
|
||||
return res.status(422).json({msg: 'invalid username or email'});
|
||||
}
|
||||
|
||||
if (email) {
|
||||
logger.debug({payload}, 'user with this email already exists');
|
||||
return res.status(422).json({msg: 'user with this email already exists'});
|
||||
return res.status(422).json({msg: 'invalid username or email'});
|
||||
}
|
||||
|
||||
if (req.user.hasAdminAuth) {
|
||||
@@ -449,28 +489,21 @@ router.post('/', async(req, res) => {
|
||||
|
||||
router.delete('/:user_sid', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
const {user_sid} = req.params;
|
||||
const allUsers = await User.retrieveAll();
|
||||
const activeAdminUsers = allUsers.filter((e) => !e.account_sid && !e.service_provider_sid && e.is_active);
|
||||
const user = await User.retrieve(user_sid);
|
||||
|
||||
try {
|
||||
if (req.user.hasAdminAuth && activeAdminUsers.length === 1) {
|
||||
throw new Error('cannot delete this admin user - there are no other active admin users');
|
||||
}
|
||||
const user_sid = parseUserSid(req);
|
||||
const allUsers = await User.retrieveAll();
|
||||
const activeAdminUsers = getActiveAdminUsers(allUsers);
|
||||
const user = allUsers.filter((user) => user.user_sid === user_sid);
|
||||
|
||||
if (req.user.hasAdminAuth ||
|
||||
(req.user.hasAccountAuth && req.user.account_sid === user[0].account_sid) ||
|
||||
(req.user.hasServiceProviderAuth && req.user.service_provider_sid === user[0].service_provider_sid)) {
|
||||
await User.remove(user_sid);
|
||||
/* invalidate the jwt of the deleted user */
|
||||
const redisKey = cacheClient.generateRedisKey('jwt', user_sid, 'v2');
|
||||
await cacheClient.delete(redisKey);
|
||||
ensureUserDeletionIsAllowed(req, activeAdminUsers, user);
|
||||
await User.remove(user_sid);
|
||||
|
||||
return res.sendStatus(204);
|
||||
} else {
|
||||
throw new DbErrorBadRequest('invalid request');
|
||||
}
|
||||
/* invalidate the jwt of the deleted user */
|
||||
const redisKey = cacheClient.generateRedisKey('jwt', user_sid, 'v2');
|
||||
await cacheClient.delete(redisKey);
|
||||
|
||||
return res.sendStatus(204);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
|
||||
@@ -218,20 +218,67 @@ const parseWebhookSid = (req) => {
|
||||
}
|
||||
};
|
||||
|
||||
const hasAccountPermissions = (req, res, next) => {
|
||||
const parseSipGatewaySid = (req) => {
|
||||
try {
|
||||
return validateSid('SipGateways', req);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const parseUserSid = (req) => {
|
||||
try {
|
||||
return validateSid('Users', req);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const parseLcrSid = (req) => {
|
||||
try {
|
||||
return validateSid('Lcrs', req);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const hasAccountPermissions = async(req, res, next) => {
|
||||
try {
|
||||
if (req.user.hasScope('admin')) {
|
||||
return next();
|
||||
}
|
||||
|
||||
if (req.user.hasScope('service_provider')) {
|
||||
return next();
|
||||
const service_provider_sid = parseServiceProviderSid(req);
|
||||
const account_sid = parseAccountSid(req);
|
||||
if (service_provider_sid) {
|
||||
if (service_provider_sid === req.user.service_provider_sid) {
|
||||
return next();
|
||||
}
|
||||
}
|
||||
if (account_sid) {
|
||||
const [r] = await Account.retrieve(account_sid);
|
||||
if (r && r.service_provider_sid === req.user.service_provider_sid) {
|
||||
return next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (req.user.hasScope('account')) {
|
||||
const account_sid = parseAccountSid(req);
|
||||
if (account_sid === req.user.account_sid) {
|
||||
return next();
|
||||
const service_provider_sid = parseServiceProviderSid(req);
|
||||
const [r] = await Account.retrieve(account_sid);
|
||||
|
||||
if (account_sid) {
|
||||
if (r && r.account_sid === req.user.account_sid) {
|
||||
return next();
|
||||
}
|
||||
}
|
||||
|
||||
if (service_provider_sid) {
|
||||
if (r && r.service_provider_sid === req.user.service_provider_sid) {
|
||||
return next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,6 +452,9 @@ module.exports = {
|
||||
parseSpeechCredentialSid,
|
||||
parseVoipCarrierSid,
|
||||
parseWebhookSid,
|
||||
parseSipGatewaySid,
|
||||
parseUserSid,
|
||||
parseLcrSid,
|
||||
hasAccountPermissions,
|
||||
hasServiceProviderPermissions,
|
||||
checkLimits,
|
||||
|
||||
@@ -74,7 +74,14 @@ decorate(router, VoipCarrier, ['add', 'update', 'delete'], preconditions);
|
||||
router.get('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
const results = await VoipCarrier.retrieveAll(req.user.hasAccountAuth ? req.user.account_sid : null);
|
||||
const results = req.user.hasAdminAuth ?
|
||||
await VoipCarrier.retrieveAll(req.user.hasAccountAuth ? req.user.account_sid : null) :
|
||||
await VoipCarrier.retrieveAllForSP(req.user.service_provider_sid);
|
||||
|
||||
if (req.user.hasScope('account')) {
|
||||
return res.status(200).json(results.filter((c) => c.account_sid === req.user.account_sid || !c.account_sid));
|
||||
}
|
||||
|
||||
res.status(200).json(results);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
@@ -89,6 +96,20 @@ router.get('/:sid', async(req, res) => {
|
||||
const account_sid = req.user.hasAccountAuth ? req.user.account_sid : null;
|
||||
const results = await VoipCarrier.retrieve(sid, account_sid);
|
||||
if (results.length === 0) return res.status(404).end();
|
||||
const ret = results[0];
|
||||
ret.register_status = JSON.parse(ret.register_status || '{}');
|
||||
|
||||
if (req.user.hasServiceProviderAuth && results.length === 1) {
|
||||
if (results.length === 1 && results[0].service_provider_sid !== req.user.service_provider_sid) {
|
||||
throw new DbErrorBadRequest('insufficient privileges');
|
||||
}
|
||||
}
|
||||
if (req.user.hasAccountAuth && results.length === 1) {
|
||||
if (results.length === 1 && results[0].account_sid !== req.user.account_sid) {
|
||||
throw new DbErrorBadRequest('insufficient privileges');
|
||||
}
|
||||
}
|
||||
|
||||
return res.status(200).json(results[0]);
|
||||
}
|
||||
catch (err) {
|
||||
|
||||
@@ -2,17 +2,37 @@ const router = require('express').Router();
|
||||
const Webhook = require('../../models/webhook');
|
||||
const decorate = require('./decorate');
|
||||
const sysError = require('../error');
|
||||
const {DbErrorForbidden} = require('../../utils/errors');
|
||||
const { parseWebhookSid } = require('./utils');
|
||||
const {promisePool} = require('../../db');
|
||||
|
||||
decorate(router, Webhook, ['add']);
|
||||
|
||||
/* retrieve */
|
||||
router.get('/:sid', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
|
||||
try {
|
||||
const sid = parseWebhookSid(req);
|
||||
const results = await Webhook.retrieve(sid);
|
||||
|
||||
if (results.length === 0) return res.status(404).end();
|
||||
|
||||
if (req.user.hasAccountAuth) {
|
||||
/* can only update carriers for the user's account */
|
||||
if (results[0].account_sid !== req.user.account_sid) {
|
||||
throw new DbErrorForbidden('insufficient privileges');
|
||||
}
|
||||
}
|
||||
if (req.user.hasServiceProviderAuth) {
|
||||
const [r] = await promisePool.execute(
|
||||
'SELECT service_provider_sid from accounts WHERE account_sid = ?', [results[0].account_sid]
|
||||
);
|
||||
if (r.length === 1 && r[0].service_provider_sid === req.user.service_provider_sid) {
|
||||
return;
|
||||
}
|
||||
throw new DbErrorForbidden('insufficient permissions');
|
||||
}
|
||||
return res.status(200).json(results[0]);
|
||||
}
|
||||
catch (err) {
|
||||
|
||||
@@ -61,8 +61,7 @@ router.post('/', express.raw({type: 'application/json'}), async(req, res) => {
|
||||
}
|
||||
|
||||
/* process event */
|
||||
logger.info(`received webhook: ${evt.type}`);
|
||||
if (evt.type.startsWith('invoice.')) handleInvoiceEvents(logger, evt);
|
||||
if (evt?.type?.startsWith('invoice.')) handleInvoiceEvents(logger, evt);
|
||||
else {
|
||||
logger.debug(evt, 'unhandled stripe webook');
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -89,7 +89,12 @@ const createDnsRecords = async(logger, domain, name, value, ttl = 3600) => {
|
||||
const str = await res.text();
|
||||
return JSON.parse(str);
|
||||
}
|
||||
logger.error({res}, 'Error creating records');
|
||||
let body;
|
||||
try {
|
||||
body = await res.json();
|
||||
} catch (err) {
|
||||
}
|
||||
logger.error({headers: res.headers, body}, `Error creating records, status ${res.statusCode}`);
|
||||
} catch (err) {
|
||||
logger.error({err}, 'Error retrieving domains');
|
||||
}
|
||||
|
||||
@@ -47,13 +47,15 @@ const sendEmailByCustomVendor = async(logger, from, to, subject, text) => {
|
||||
};
|
||||
|
||||
const sendEmailByMailgun = async(logger, from, to, subject, text) => {
|
||||
const mg = mailgun.client({
|
||||
username: 'api',
|
||||
key: process.env.MAILGUN_API_KEY
|
||||
});
|
||||
if (!process.env.MAILGUN_API_KEY) throw new Error('MAILGUN_API_KEY env variable is not defined!');
|
||||
if (!process.env.MAILGUN_DOMAIN) throw new Error('MAILGUN_DOMAIN env variable is not defined!');
|
||||
|
||||
const mg = mailgun.client({
|
||||
username: 'api',
|
||||
key: process.env.MAILGUN_API_KEY,
|
||||
...(process.env.MAILGUN_URL && {url: process.env.MAILGUN_URL})
|
||||
});
|
||||
|
||||
try {
|
||||
const res = await mg.messages.create(process.env.MAILGUN_DOMAIN, {
|
||||
from,
|
||||
|
||||
@@ -23,7 +23,18 @@ const decrypt = (data) => {
|
||||
return decrpyted.toString();
|
||||
};
|
||||
|
||||
const obscureKey = (key, key_spoiler_length = 6) => {
|
||||
const key_spoiler_char = 'X';
|
||||
|
||||
if (!key || key.length <= key_spoiler_length) {
|
||||
return key;
|
||||
}
|
||||
|
||||
return `${key.slice(0, key_spoiler_length)}${key_spoiler_char.repeat(key.length - key_spoiler_length)}`;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
encrypt,
|
||||
decrypt
|
||||
decrypt,
|
||||
obscureKey
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"trial": [
|
||||
{
|
||||
"category": "voice_call_session",
|
||||
"quantity": 20
|
||||
"quantity": 5
|
||||
},
|
||||
{
|
||||
"category": "device",
|
||||
|
||||
@@ -39,11 +39,17 @@ const getHomerSipTrace = async(logger, apiKey, callId) => {
|
||||
const obj = await postJSON('/api/v3/call/transaction', {
|
||||
param: {
|
||||
transaction: {
|
||||
call: true
|
||||
call: true,
|
||||
registration: true,
|
||||
rest: false
|
||||
},
|
||||
orlogic: true,
|
||||
search: {
|
||||
'1_call': {
|
||||
callid: [callId]
|
||||
},
|
||||
'1_registration': {
|
||||
callid: [callId]
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -58,7 +64,7 @@ const getHomerSipTrace = async(logger, apiKey, callId) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getHomerPcap = async(logger, apiKey, callIds) => {
|
||||
const getHomerPcap = async(logger, apiKey, callIds, method) => {
|
||||
if (!process.env.HOMER_BASE_URL || !process.env.HOMER_USERNAME || !process.env.HOMER_PASSWORD) {
|
||||
logger.debug('getHomerPcap: Homer integration not installed');
|
||||
}
|
||||
@@ -67,12 +73,23 @@ const getHomerPcap = async(logger, apiKey, callIds) => {
|
||||
const stream = await postPcap('/api/v3/export/call/messages/pcap', {
|
||||
param: {
|
||||
transaction: {
|
||||
call: true
|
||||
call: method === 'invite',
|
||||
registration: method === 'register',
|
||||
rest: false
|
||||
},
|
||||
orlogic: true,
|
||||
search: {
|
||||
'1_call': {
|
||||
callid: callIds
|
||||
}
|
||||
...(method === 'invite' && {
|
||||
'1_call': {
|
||||
callid: callIds
|
||||
}
|
||||
})
|
||||
,
|
||||
...(method === 'register' && {
|
||||
'1_registration': {
|
||||
callid: callIds
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
timestamp: {
|
||||
|
||||
19
lib/utils/jaeger-utils.js
Normal file
19
lib/utils/jaeger-utils.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const bent = require('bent');
|
||||
const getJSON = bent(process.env.JAEGER_BASE_URL || 'http://127.0.0.1', 'GET', 'json', 200);
|
||||
|
||||
const getJaegerTrace = async(logger, traceId) => {
|
||||
if (!process.env.JAEGER_BASE_URL) {
|
||||
logger.debug('getJaegerTrace: jaeger integration not installed');
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return await getJSON(`/api/v3/traces/${traceId}`);
|
||||
} catch (err) {
|
||||
const url = `${process.env.JAEGER_BASE_URL}/api/traces/${traceId}`;
|
||||
logger.error({err, traceId}, `getJaegerTrace: Error retrieving spans from ${url}`);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getJaegerTrace
|
||||
};
|
||||
1
lib/utils/jambonz-sample.text
Normal file
1
lib/utils/jambonz-sample.text
Normal file
@@ -0,0 +1 @@
|
||||
Hello From Jambonz. This file was created because Record all call bucket credential test.
|
||||
22
lib/utils/speech-data/stt-assemblyai.js
Normal file
22
lib/utils/speech-data/stt-assemblyai.js
Normal file
@@ -0,0 +1,22 @@
|
||||
module.exports = [
|
||||
{ name: 'Global English', value: 'en' },
|
||||
{ name: 'Australian English', value: 'en_au' },
|
||||
{ name: 'British English', value: 'en_uk' },
|
||||
{ name: 'US English', value: 'en_us' },
|
||||
{ name: 'Spanish', value: 'es' },
|
||||
{ name: 'French', value: 'fr' },
|
||||
{ name: 'German', value: 'de' },
|
||||
{ name: 'Italian', value: 'it' },
|
||||
{ name: 'Portuguese', value: 'pt' },
|
||||
{ name: 'Dutch', value: 'nl' },
|
||||
{ name: 'Hindi', value: 'hi' },
|
||||
{ name: 'Japanese', value: 'ja' },
|
||||
{ name: 'Chinese', value: 'zh' },
|
||||
{ name: 'Finnish', value: 'fi' },
|
||||
{ name: 'Korean', value: 'ko' },
|
||||
{ name: 'Polish', value: 'pl' },
|
||||
{ name: 'Russian', value: 'ru' },
|
||||
{ name: 'Turkish', value: 'tr' },
|
||||
{ name: 'Ukrainian', value: 'uk' },
|
||||
{ name: 'Vietnamese', value: 'vi' },
|
||||
];
|
||||
10
lib/utils/speech-data/stt-aws.js
Normal file
10
lib/utils/speech-data/stt-aws.js
Normal file
@@ -0,0 +1,10 @@
|
||||
module.exports = [
|
||||
{ name: 'Australian English', value: 'en-AU' },
|
||||
{ name: 'British English', value: 'en-GB' },
|
||||
{ name: 'US English', value: 'en-US' },
|
||||
{ name: 'French', value: 'fr-FR' },
|
||||
{ name: 'Canadian French', value: 'fr-CA' },
|
||||
{ name: 'German', value: 'de-DE' },
|
||||
{ name: 'Italian', value: 'it-IT' },
|
||||
{ name: 'US Spanish', value: 'es-US' },
|
||||
];
|
||||
26
lib/utils/speech-data/stt-cobalt.js
Normal file
26
lib/utils/speech-data/stt-cobalt.js
Normal file
@@ -0,0 +1,26 @@
|
||||
module.exports = [
|
||||
{
|
||||
name: 'English US',
|
||||
value: 'en_US-8khz',
|
||||
},
|
||||
{
|
||||
name: 'English UK',
|
||||
value: 'en_UK-8khz',
|
||||
},
|
||||
{
|
||||
name: 'Spanish',
|
||||
value: 'es_xx-8khz',
|
||||
},
|
||||
{
|
||||
name: 'French',
|
||||
value: 'fr_fr-8khz',
|
||||
},
|
||||
{
|
||||
name: 'Russian',
|
||||
value: 'ru_ru-8khz',
|
||||
},
|
||||
{
|
||||
name: 'Portuguese',
|
||||
value: 'pt_br-8khz',
|
||||
},
|
||||
];
|
||||
138
lib/utils/speech-data/stt-deepgram.js
Normal file
138
lib/utils/speech-data/stt-deepgram.js
Normal file
@@ -0,0 +1,138 @@
|
||||
module.exports = [
|
||||
{
|
||||
name: 'Chinese - general',
|
||||
value: 'zh',
|
||||
},
|
||||
{
|
||||
name: 'Chinese (China)',
|
||||
value: 'zh-CN',
|
||||
},
|
||||
{
|
||||
name: 'Chinese (Taiwan)',
|
||||
value: 'zh-TW',
|
||||
},
|
||||
{
|
||||
name: 'Dutch - general',
|
||||
value: 'nl',
|
||||
},
|
||||
{
|
||||
name: 'English - general',
|
||||
value: 'en',
|
||||
},
|
||||
{
|
||||
name: 'English (Australia)',
|
||||
value: 'en-AU',
|
||||
},
|
||||
{
|
||||
name: 'English (United Kingdom)',
|
||||
value: 'en-GB',
|
||||
},
|
||||
{
|
||||
name: 'English (India)',
|
||||
value: 'en-IN',
|
||||
},
|
||||
{
|
||||
name: 'English (New Zealand)',
|
||||
value: 'en-NZ',
|
||||
},
|
||||
{
|
||||
name: 'English (United States)',
|
||||
value: 'en-US',
|
||||
},
|
||||
{
|
||||
name: 'French - general',
|
||||
value: 'fr',
|
||||
},
|
||||
{
|
||||
name: 'French (Canada)',
|
||||
value: 'fr-CA',
|
||||
},
|
||||
{
|
||||
name: 'German - general',
|
||||
value: 'de',
|
||||
},
|
||||
{
|
||||
name: 'Hindi - general',
|
||||
value: 'hi',
|
||||
},
|
||||
{
|
||||
name: 'Hindi (Roman Script)',
|
||||
value: 'hi-Latin',
|
||||
},
|
||||
{
|
||||
name: 'Indonesian - general',
|
||||
value: 'in',
|
||||
},
|
||||
{
|
||||
name: 'Italian - general',
|
||||
value: 'it',
|
||||
},
|
||||
{
|
||||
name: 'Japanese - general',
|
||||
value: 'ja',
|
||||
},
|
||||
{
|
||||
name: 'Korean - general',
|
||||
value: 'ko',
|
||||
},
|
||||
{
|
||||
name: 'Norwegian - general',
|
||||
value: 'no',
|
||||
},
|
||||
{
|
||||
name: 'Polish - general',
|
||||
value: 'pl',
|
||||
},
|
||||
{
|
||||
name: 'Portuguese - general',
|
||||
value: 'pt',
|
||||
},
|
||||
{
|
||||
name: 'Portuguese (Brazil)',
|
||||
value: 'pt-BR',
|
||||
},
|
||||
{
|
||||
name: 'Portuguese (Portugal)',
|
||||
value: 'pt-PT',
|
||||
},
|
||||
{
|
||||
name: 'Russian - general',
|
||||
value: 'ru',
|
||||
},
|
||||
{
|
||||
name: 'Spanish - general',
|
||||
value: 'es',
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Latin America)',
|
||||
value: 'es-419',
|
||||
},
|
||||
{
|
||||
name: 'Swedish - general',
|
||||
value: 'sv',
|
||||
},
|
||||
{
|
||||
name: 'Turkish - general',
|
||||
value: 'tr',
|
||||
},
|
||||
{
|
||||
name: 'Ukrainian - general',
|
||||
value: 'uk',
|
||||
},
|
||||
{
|
||||
name: 'Flemish - general',
|
||||
value: 'nl-BE',
|
||||
},
|
||||
{
|
||||
name: 'Danish - general',
|
||||
value: 'da',
|
||||
},
|
||||
{
|
||||
name: 'Tamil - general',
|
||||
value: 'ta',
|
||||
},
|
||||
{
|
||||
name: 'Tamasheq - general',
|
||||
value: 'taq',
|
||||
},
|
||||
];
|
||||
130
lib/utils/speech-data/stt-google.js
Normal file
130
lib/utils/speech-data/stt-google.js
Normal file
@@ -0,0 +1,130 @@
|
||||
module.exports = [
|
||||
{ name: 'Afrikaans (South Africa)', value: 'af-ZA' },
|
||||
{ name: 'Albanian (Albania)', value: 'sq-AL' },
|
||||
{ name: 'Amharic (Ethiopia)', value: 'am-ET' },
|
||||
{ name: 'Arabic (Algeria)', value: 'ar-DZ' },
|
||||
{ name: 'Arabic (Bahrain)', value: 'ar-BH' },
|
||||
{ name: 'Arabic (Egypt)', value: 'ar-EG' },
|
||||
{ name: 'Arabic (Iraq)', value: 'ar-IQ' },
|
||||
{ name: 'Arabic (Israel)', value: 'ar-IL' },
|
||||
{ name: 'Arabic (Jordan)', value: 'ar-JO' },
|
||||
{ name: 'Arabic (Kuwait)', value: 'ar-KW' },
|
||||
{ name: 'Arabic (Lebanon)', value: 'ar-LB' },
|
||||
{ name: 'Arabic (Morocco)', value: 'ar-MA' },
|
||||
{ name: 'Arabic (Oman)', value: 'ar-OM' },
|
||||
{ name: 'Arabic (Qatar)', value: 'ar-QA' },
|
||||
{ name: 'Arabic (Saudi Arabia)', value: 'ar-SA' },
|
||||
{ name: 'Arabic (State of Palestine)', value: 'ar-PS' },
|
||||
{ name: 'Arabic (Tunisia)', value: 'ar-TN' },
|
||||
{ name: 'Arabic (United Arab Emirates)', value: 'ar-AE' },
|
||||
{ name: 'Armenian (Armenia)', value: 'hy-AM' },
|
||||
{ name: 'Azerbaijani (Azerbaijan)', value: 'az-AZ' },
|
||||
{ name: 'Basque (Spain)', value: 'eu-ES' },
|
||||
{ name: 'Bengali (Bangladesh)', value: 'bn-BD' },
|
||||
{ name: 'Bengali (India)', value: 'bn-IN' },
|
||||
{ name: 'Bulgarian (Bulgaria)', value: 'bg-BG' },
|
||||
{ name: 'Burmese (Myanmar)', value: 'my-MM' },
|
||||
{ name: 'Catalan (Spain)', value: 'ca-ES' },
|
||||
{ name: 'Chinese, Cantonese (Traditional, Hong Kong)', value: 'yue-Hant-HK' },
|
||||
{ name: 'Chinese, Mandarin (Simplified, China)', value: 'zh' },
|
||||
{ name: 'Chinese, Mandarin (Simplified, Hong Kong)', value: 'zh-HK' },
|
||||
{ name: 'Chinese, Mandarin (Simplified, Taiwan)', value: 'zh-TW' },
|
||||
{ name: 'Croatian (Croatia)', value: 'hr-HR' },
|
||||
{ name: 'Czech (Czech Republic)', value: 'cs-CZ' },
|
||||
{ name: 'Danish (Denmark)', value: 'da-DK' },
|
||||
{ name: 'Dutch (Belgium)', value: 'nl-BE' },
|
||||
{ name: 'Dutch (Netherlands)', value: 'nl-NL' },
|
||||
{ name: 'English (Australia)', value: 'en-AU' },
|
||||
{ name: 'English (Canada)', value: 'en-CA' },
|
||||
{ name: 'English (Ghana)', value: 'en-GH' },
|
||||
{ name: 'English (India)', value: 'en-IN' },
|
||||
{ name: 'English (Ireland)', value: 'en-IE' },
|
||||
{ name: 'English (Kenya)', value: 'en-KE' },
|
||||
{ name: 'English (New Zealand)', value: 'en-NZ' },
|
||||
{ name: 'English (Nigeria)', value: 'en-NG' },
|
||||
{ name: 'English (Philippines)', value: 'en-PH' },
|
||||
{ name: 'English (Singapore)', value: 'en-SG' },
|
||||
{ name: 'English (South Africa)', value: 'en-ZA' },
|
||||
{ name: 'English (Tanzania)', value: 'en-TZ' },
|
||||
{ name: 'English (United Kingdom)', value: 'en-GB' },
|
||||
{ name: 'English (United States)', value: 'en-US' },
|
||||
{ name: 'Estonian (Estonia)', value: 'et-EE' },
|
||||
{ name: 'Filipino (Philippines)', value: 'fil-PH' },
|
||||
{ name: 'Finnish (Finland)', value: 'fi-FI' },
|
||||
{ name: 'French (Canada)', value: 'fr-CA' },
|
||||
{ name: 'French (France)', value: 'fr-FR' },
|
||||
{ name: 'Galician (Spain)', value: 'gl-ES' },
|
||||
{ name: 'Georgian (Georgia)', value: 'ka-GE' },
|
||||
{ name: 'German (Germany)', value: 'de-DE' },
|
||||
{ name: 'Greek (Greece)', value: 'el-GR' },
|
||||
{ name: 'Gujarati (India)', value: 'gu-IN' },
|
||||
{ name: 'Hebrew (Israel)', value: 'he-IL' },
|
||||
{ name: 'Hindi (India)', value: 'hi-IN' },
|
||||
{ name: 'Hungarian (Hungary)', value: 'hu-HU' },
|
||||
{ name: 'Icelandic (Iceland)', value: 'is-IS' },
|
||||
{ name: 'Indonesian (Indonesia)', value: 'id-ID' },
|
||||
{ name: 'Italian (Italy)', value: 'it-IT' },
|
||||
{ name: 'Japanese (Japan)', value: 'ja-JP' },
|
||||
{ name: 'Javanese (Indonesia)', value: 'jv-ID' },
|
||||
{ name: 'Kannada (India)', value: 'kn-IN' },
|
||||
{ name: 'Khmer (Cambodia)', value: 'km-KH' },
|
||||
{ name: 'Korean (South Korea)', value: 'ko-KR' },
|
||||
{ name: 'Lao (Laos)', value: 'lo-LA' },
|
||||
{ name: 'Latvian (Latvia)', value: 'lv-LV' },
|
||||
{ name: 'Lithuanian (Lithuania)', value: 'lt-LT' },
|
||||
{ name: 'Macedonian (North Macedonia)', value: 'mk-MK' },
|
||||
{ name: 'Malay (Malaysia)', value: 'ms-MY' },
|
||||
{ name: 'Malayalam (India)', value: 'ml-IN' },
|
||||
{ name: 'Marathi (India)', value: 'mr-IN' },
|
||||
{ name: 'Mongolian (Mongolia)', value: 'mn-MN' },
|
||||
{ name: 'Nepali (Nepal)', value: 'ne-NP' },
|
||||
{ name: 'Norwegian Bokmål (Norway)', value: 'nb-NO' },
|
||||
{ name: 'Persian (Iran)', value: 'fa-IR' },
|
||||
{ name: 'Polish (Poland)', value: 'pl-PL' },
|
||||
{ name: 'Portuguese (Brazil)', value: 'pt-BR' },
|
||||
{ name: 'Portuguese (Portugal)', value: 'pt-PT' },
|
||||
{ name: 'Punjabi (Gurmukhi, India)', value: 'pa-guru-IN' },
|
||||
{ name: 'Romanian (Romania)', value: 'ro-RO' },
|
||||
{ name: 'Russian (Russia)', value: 'ru-RU' },
|
||||
{ name: 'Serbian (Serbia)', value: 'sr-RS' },
|
||||
{ name: 'Sinhala (Sri Lanka)', value: 'si-LK' },
|
||||
{ name: 'Slovak (Slovakia)', value: 'sk-SK' },
|
||||
{ name: 'Slovenian (Slovenia)', value: 'sl-SI' },
|
||||
{ name: 'Spanish (Argentina)', value: 'es-AR' },
|
||||
{ name: 'Spanish (Bolivia)', value: 'es-BO' },
|
||||
{ name: 'Spanish (Chile)', value: 'es-CL' },
|
||||
{ name: 'Spanish (Colombia)', value: 'es-CO' },
|
||||
{ name: 'Spanish (Costa Rica)', value: 'es-CR' },
|
||||
{ name: 'Spanish (Dominican Republic)', value: 'es-DO' },
|
||||
{ name: 'Spanish (Ecuador)', value: 'es-EC' },
|
||||
{ name: 'Spanish (El Salvador)', value: 'es-SV' },
|
||||
{ name: 'Spanish (Guatemala)', value: 'es-GT' },
|
||||
{ name: 'Spanish (Honduras)', value: 'es-HN' },
|
||||
{ name: 'Spanish (Mexico)', value: 'es-MX' },
|
||||
{ name: 'Spanish (Nicaragua)', value: 'es-NI' },
|
||||
{ name: 'Spanish (Panama)', value: 'es-PA' },
|
||||
{ name: 'Spanish (Paraguay)', value: 'es-PY' },
|
||||
{ name: 'Spanish (Peru)', value: 'es-PE' },
|
||||
{ name: 'Spanish (Puerto Rico)', value: 'es-PR' },
|
||||
{ name: 'Spanish (Spain)', value: 'es-ES' },
|
||||
{ name: 'Spanish (United States)', value: 'es-US' },
|
||||
{ name: 'Spanish (Uruguay)', value: 'es-UY' },
|
||||
{ name: 'Spanish (Venezuela)', value: 'es-VE' },
|
||||
{ name: 'Sundanese (Indonesia)', value: 'su-ID' },
|
||||
{ name: 'Swahili (Kenya)', value: 'sw-KE' },
|
||||
{ name: 'Swahili (Tanzania)', value: 'sw-TZ' },
|
||||
{ name: 'Swedish (Sweden)', value: 'sv-SE' },
|
||||
{ name: 'Tamil (India)', value: 'ta-IN' },
|
||||
{ name: 'Tamil (Malaysia)', value: 'ta-MY' },
|
||||
{ name: 'Tamil (Singapore)', value: 'ta-SG' },
|
||||
{ name: 'Tamil (Sri Lanka)', value: 'ta-LK' },
|
||||
{ name: 'Telugu (India)', value: 'te-IN' },
|
||||
{ name: 'Thai (Thailand)', value: 'th-TH' },
|
||||
{ name: 'Turkish (Turkey)', value: 'tr-TR' },
|
||||
{ name: 'Ukrainian (Ukraine)', value: 'uk-UA' },
|
||||
{ name: 'Urdu (India)', value: 'ur-IN' },
|
||||
{ name: 'Urdu (Pakistan)', value: 'ur-PK' },
|
||||
{ name: 'Uzbek (Uzbekistan)', value: 'uz-UZ' },
|
||||
{ name: 'Vietnamese (Vietnam)', value: 'vi-VN' },
|
||||
{ name: 'Zulu (South Africa)', value: 'zu-ZA' },
|
||||
];
|
||||
82
lib/utils/speech-data/stt-ibm.js
Normal file
82
lib/utils/speech-data/stt-ibm.js
Normal file
@@ -0,0 +1,82 @@
|
||||
module.exports = [
|
||||
{
|
||||
name: 'Arabic (Modern Standard)',
|
||||
value: 'ar-MS_Telephony',
|
||||
},
|
||||
{
|
||||
name: 'Chinese (Mandarin)',
|
||||
value: 'zh-CN_Telephony',
|
||||
},
|
||||
{
|
||||
name: 'Czech ',
|
||||
value: 'cs-CZ_Telephony',
|
||||
},
|
||||
{
|
||||
name: 'Dutch (Belgian)',
|
||||
value: 'nl-BE_Telephony',
|
||||
},
|
||||
{
|
||||
name: 'Dutch (Netherlands)',
|
||||
value: 'nl-NL_Telephony',
|
||||
},
|
||||
{
|
||||
name: 'English (all supported dialects)',
|
||||
value: 'en-WW_Medical_Telephony',
|
||||
},
|
||||
{
|
||||
name: 'English (Australian)',
|
||||
value: 'en-AU_Telephony',
|
||||
},
|
||||
{
|
||||
name: 'English (Indian)',
|
||||
value: 'en-IN_Telephony',
|
||||
},
|
||||
{
|
||||
name: 'English (United Kingdom)',
|
||||
value: 'en-GB_Telephony',
|
||||
},
|
||||
{
|
||||
name: 'English (United States)',
|
||||
value: 'en-US_Telephony',
|
||||
},
|
||||
{
|
||||
name: 'French (Canadian)',
|
||||
value: 'fr-CA_Telephony',
|
||||
},
|
||||
{
|
||||
name: 'French (France)',
|
||||
value: 'fr-FR_Telephony',
|
||||
},
|
||||
{
|
||||
name: 'German',
|
||||
value: 'de-DE_Telephony',
|
||||
},
|
||||
{
|
||||
name: 'Hindi (Indian)',
|
||||
value: 'hi-IN_Telephony',
|
||||
},
|
||||
{
|
||||
name: 'Italian',
|
||||
value: 'it-IT_Telephony',
|
||||
},
|
||||
{
|
||||
name: 'Korean',
|
||||
value: 'ko-KR_Telephony',
|
||||
},
|
||||
{
|
||||
name: 'Portuguese (Brazilian)',
|
||||
value: 'pt-BR_Telephony',
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Mexican)',
|
||||
value: 'es-LA_Telephony',
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Castilian)',
|
||||
value: 'es-ES_Telephony',
|
||||
},
|
||||
{
|
||||
name: 'Swedish ',
|
||||
value: 'sv-SE_Telephony',
|
||||
},
|
||||
];
|
||||
490
lib/utils/speech-data/stt-microsoft.js
Normal file
490
lib/utils/speech-data/stt-microsoft.js
Normal file
@@ -0,0 +1,490 @@
|
||||
module.exports = [
|
||||
{
|
||||
name: 'Afrikaans (South Africa)',
|
||||
value: 'af-ZA',
|
||||
},
|
||||
{
|
||||
name: 'Amharic (Ethiopia)',
|
||||
value: 'am-ET',
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Algeria)',
|
||||
value: 'ar-DZ',
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Bahrain)',
|
||||
value: 'ar-BH',
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Egypt)',
|
||||
value: 'ar-EG',
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Iraq)',
|
||||
value: 'ar-IQ',
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Israel)',
|
||||
value: 'ar-IL',
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Jordan)',
|
||||
value: 'ar-JO',
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Kuwait)',
|
||||
value: 'ar-KW',
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Lebanon)',
|
||||
value: 'ar-LB',
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Libya)',
|
||||
value: 'ar-LY',
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Morocco)',
|
||||
value: 'ar-MA',
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Oman)',
|
||||
value: 'ar-OM',
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Qatar)',
|
||||
value: 'ar-QA',
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Saudi Arabia)',
|
||||
value: 'ar-SA',
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Palestinian Authority)',
|
||||
value: 'ar-PS',
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Syria)',
|
||||
value: 'ar-SY',
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Tunisia)',
|
||||
value: 'ar-TN',
|
||||
},
|
||||
{
|
||||
name: 'Arabic (United Arab Emirates)',
|
||||
value: 'ar-AE',
|
||||
},
|
||||
{
|
||||
name: 'Arabic (Yemen)',
|
||||
value: 'ar-YE',
|
||||
},
|
||||
{
|
||||
name: 'Bulgarian (Bulgaria)',
|
||||
value: 'bg-BG',
|
||||
},
|
||||
{
|
||||
name: 'Bengali (India)',
|
||||
value: 'bn-IN',
|
||||
},
|
||||
{
|
||||
name: 'Catalan (Spain)',
|
||||
value: 'ca-ES',
|
||||
},
|
||||
{
|
||||
name: 'Chinese (Cantonese, Traditional)',
|
||||
value: 'zh-HK',
|
||||
},
|
||||
{
|
||||
name: 'Chinese (Mandarin, Simplified)',
|
||||
value: 'zh-CN',
|
||||
},
|
||||
{
|
||||
name: 'Chinese (Taiwanese Mandarin)',
|
||||
value: 'zh-TW',
|
||||
},
|
||||
{
|
||||
name: 'Croatian (Croatia)',
|
||||
value: 'hr-HR',
|
||||
},
|
||||
{
|
||||
name: 'Czech (Czech)',
|
||||
value: 'cs-CZ',
|
||||
},
|
||||
{
|
||||
name: 'Danish (Denmark)',
|
||||
value: 'da-DK',
|
||||
},
|
||||
{
|
||||
name: 'Dutch (Netherlands)',
|
||||
value: 'nl-NL',
|
||||
},
|
||||
{
|
||||
name: 'Dutch (Belgium)',
|
||||
value: 'nl-BE',
|
||||
},
|
||||
{
|
||||
name: 'English (Australia)',
|
||||
value: 'en-AU',
|
||||
},
|
||||
{
|
||||
name: 'English (Canada)',
|
||||
value: 'en-CA',
|
||||
},
|
||||
{
|
||||
name: 'English (Ghana)',
|
||||
value: 'en-GH',
|
||||
},
|
||||
{
|
||||
name: 'English (Hong Kong)',
|
||||
value: 'en-HK',
|
||||
},
|
||||
{
|
||||
name: 'English (India)',
|
||||
value: 'en-IN',
|
||||
},
|
||||
{
|
||||
name: 'English (Ireland)',
|
||||
value: 'en-IE',
|
||||
},
|
||||
{
|
||||
name: 'English (Kenya)',
|
||||
value: 'en-KE',
|
||||
},
|
||||
{
|
||||
name: 'English (New Zealand)',
|
||||
value: 'en-NZ',
|
||||
},
|
||||
{
|
||||
name: 'English (Nigeria)',
|
||||
value: 'en-NG',
|
||||
},
|
||||
{
|
||||
name: 'English (Philippines)',
|
||||
value: 'en-PH',
|
||||
},
|
||||
{
|
||||
name: 'English (Singapore)',
|
||||
value: 'en-SG',
|
||||
},
|
||||
{
|
||||
name: 'English (South Africa)',
|
||||
value: 'en-ZA',
|
||||
},
|
||||
{
|
||||
name: 'English (Tanzania)',
|
||||
value: 'en-TZ',
|
||||
},
|
||||
{
|
||||
name: 'English (United Kingdom)',
|
||||
value: 'en-GB',
|
||||
},
|
||||
{
|
||||
name: 'English (United States)',
|
||||
value: 'en-US',
|
||||
},
|
||||
{
|
||||
name: 'Estonian(Estonia)',
|
||||
value: 'et-EE',
|
||||
},
|
||||
{
|
||||
name: 'Filipino (Philippines)',
|
||||
value: 'fil-PH',
|
||||
},
|
||||
{
|
||||
name: 'Finnish (Finland)',
|
||||
value: 'fi-FI',
|
||||
},
|
||||
{
|
||||
name: 'French (Belgium)',
|
||||
value: 'fr-BE',
|
||||
},
|
||||
{
|
||||
name: 'French (Canada)',
|
||||
value: 'fr-CA',
|
||||
},
|
||||
{
|
||||
name: 'French (France)',
|
||||
value: 'fr-FR',
|
||||
},
|
||||
{
|
||||
name: 'French (Switzerland)',
|
||||
value: 'fr-CH',
|
||||
},
|
||||
{
|
||||
name: 'German (Austria)',
|
||||
value: 'de-AT',
|
||||
},
|
||||
{
|
||||
name: 'German (Switzerland)',
|
||||
value: 'de-CH',
|
||||
},
|
||||
{
|
||||
name: 'German (Germany)',
|
||||
value: 'de-DE',
|
||||
},
|
||||
{
|
||||
name: 'Greek (Greece)',
|
||||
value: 'el-GR',
|
||||
},
|
||||
{
|
||||
name: 'Gujarati (Indian)',
|
||||
value: 'gu-IN',
|
||||
},
|
||||
{
|
||||
name: 'Hebrew (Israel)',
|
||||
value: 'he-IL',
|
||||
},
|
||||
{
|
||||
name: 'Hindi (India)',
|
||||
value: 'hi-IN',
|
||||
},
|
||||
{
|
||||
name: 'Hungarian (Hungary)',
|
||||
value: 'hu-HU',
|
||||
},
|
||||
{
|
||||
name: 'Indonesian (Indonesia)',
|
||||
value: 'id-ID',
|
||||
},
|
||||
{
|
||||
name: 'Icelandic (Iceland)',
|
||||
value: 'is-IS',
|
||||
},
|
||||
{
|
||||
name: 'Irish (Ireland)',
|
||||
value: 'ga-IE',
|
||||
},
|
||||
{
|
||||
name: 'Italian (Italy)',
|
||||
value: 'it-IT',
|
||||
},
|
||||
{
|
||||
name: 'Japanese (Japan)',
|
||||
value: 'ja-JP',
|
||||
},
|
||||
{
|
||||
name: 'Javanese (Indonesia)',
|
||||
value: 'jv-ID',
|
||||
},
|
||||
{
|
||||
name: 'Kannada (India)',
|
||||
value: 'kn-IN',
|
||||
},
|
||||
{
|
||||
name: 'Khmer (Cambodia)',
|
||||
value: 'km-KH',
|
||||
},
|
||||
{
|
||||
name: 'Korean (Korea)',
|
||||
value: 'ko-KR',
|
||||
},
|
||||
{
|
||||
name: 'Latvian (Latvia)',
|
||||
value: 'lv-LV',
|
||||
},
|
||||
{
|
||||
name: 'Lao (Laos)',
|
||||
value: 'lo-LA',
|
||||
},
|
||||
{
|
||||
name: 'Lithuanian (Lithuania)',
|
||||
value: 'lt-LT',
|
||||
},
|
||||
{
|
||||
name: 'Malay (Malaysia)',
|
||||
value: 'ms-MY',
|
||||
},
|
||||
{
|
||||
name: 'Macedonian (North Macedonia)',
|
||||
value: 'mk-MK',
|
||||
},
|
||||
{
|
||||
name: 'Maltese (Malta)',
|
||||
value: 'mt-MT',
|
||||
},
|
||||
{
|
||||
name: 'Marathi (India)',
|
||||
value: 'mr-IN',
|
||||
},
|
||||
{
|
||||
name: 'Burmese (Myanmar)',
|
||||
value: 'my-MM',
|
||||
},
|
||||
{
|
||||
name: 'Norwegian (Bokmål, Norway)',
|
||||
value: 'nb-NO',
|
||||
},
|
||||
{
|
||||
name: 'Persian (Iran)',
|
||||
value: 'fa-IR',
|
||||
},
|
||||
{
|
||||
name: 'Polish (Poland)',
|
||||
value: 'pl-PL',
|
||||
},
|
||||
{
|
||||
name: 'Portuguese (Brazil)',
|
||||
value: 'pt-BR',
|
||||
},
|
||||
{
|
||||
name: 'Portuguese (Portugal)',
|
||||
value: 'pt-PT',
|
||||
},
|
||||
{
|
||||
name: 'Romanian (Romania)',
|
||||
value: 'ro-RO',
|
||||
},
|
||||
{
|
||||
name: 'Russian (Russia)',
|
||||
value: 'ru-RU',
|
||||
},
|
||||
{
|
||||
name: 'Slovak (Slovakia)',
|
||||
value: 'sk-SK',
|
||||
},
|
||||
{
|
||||
name: 'Slovenian (Slovenia)',
|
||||
value: 'sl-SI',
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Argentina)',
|
||||
value: 'es-AR',
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Bolivia)',
|
||||
value: 'es-BO',
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Chile)',
|
||||
value: 'es-CL',
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Colombia)',
|
||||
value: 'es-CO',
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Costa Rica)',
|
||||
value: 'es-CR',
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Cuba)',
|
||||
value: 'es-CU',
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Dominican Republic)',
|
||||
value: 'es-DO',
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Ecuador)',
|
||||
value: 'es-EC',
|
||||
},
|
||||
{
|
||||
name: 'Spanish (El Salvador)',
|
||||
value: 'es-SV',
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Equatorial Guinea)',
|
||||
value: 'es-GQ',
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Guatemala)',
|
||||
value: 'es-GT',
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Honduras)',
|
||||
value: 'es-HN',
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Mexico)',
|
||||
value: 'es-MX',
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Nicaragua)',
|
||||
value: 'es-NI',
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Panama)',
|
||||
value: 'es-PA',
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Paraguay)',
|
||||
value: 'es-PY',
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Peru)',
|
||||
value: 'es-PE',
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Puerto Rico)',
|
||||
value: 'es-PR',
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Spain)',
|
||||
value: 'es-ES',
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Uruguay)',
|
||||
value: 'es-UY',
|
||||
},
|
||||
{
|
||||
name: 'Spanish (USA)',
|
||||
value: 'es-US',
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Venezuela)',
|
||||
value: 'es-VE',
|
||||
},
|
||||
{
|
||||
name: 'Swahili (Kenya)',
|
||||
value: 'sw-KE',
|
||||
},
|
||||
{
|
||||
name: 'Swahili (Tanzania)',
|
||||
value: 'sw-TZ',
|
||||
},
|
||||
{
|
||||
name: 'Sinhala (Sri Lanka)',
|
||||
value: 'si-LK',
|
||||
},
|
||||
{
|
||||
name: 'Swedish (Sweden)',
|
||||
value: 'sv-SE',
|
||||
},
|
||||
{
|
||||
name: 'Serbian (Serbia)',
|
||||
value: 'sr-RS',
|
||||
},
|
||||
{
|
||||
name: 'Tamil (India)',
|
||||
value: 'ta-IN',
|
||||
},
|
||||
{
|
||||
name: 'Telugu (India)',
|
||||
value: 'te-IN',
|
||||
},
|
||||
{
|
||||
name: 'Thai (Thailand)',
|
||||
value: 'th-TH',
|
||||
},
|
||||
{
|
||||
name: 'Turkish (Turkey)',
|
||||
value: 'tr-TR',
|
||||
},
|
||||
{
|
||||
name: 'Ukrainian (Ukraine)',
|
||||
value: 'uk-UA',
|
||||
},
|
||||
{
|
||||
name: 'Uzbek (Uzbekistan)',
|
||||
value: 'uz-UZ',
|
||||
},
|
||||
{
|
||||
name: 'Zulu (South Africa)',
|
||||
value: 'zu-ZA',
|
||||
},
|
||||
{
|
||||
name: 'Vietnamese (Vietnam)',
|
||||
value: 'vi-VN',
|
||||
},
|
||||
];
|
||||
207
lib/utils/speech-data/stt-nuance.js
Normal file
207
lib/utils/speech-data/stt-nuance.js
Normal file
@@ -0,0 +1,207 @@
|
||||
module.exports = [
|
||||
{
|
||||
name: 'Arabic (Worldwide)',
|
||||
value: 'ar-WW',
|
||||
valueMix: 'ara-XWW',
|
||||
},
|
||||
{
|
||||
name: 'Catalan (Spain)',
|
||||
value: 'ca-ES',
|
||||
valueMix: 'cat-ESP',
|
||||
},
|
||||
{
|
||||
name: 'Croatian (Croatia)',
|
||||
value: 'hr-HR',
|
||||
valueMix: 'hrv-HRV',
|
||||
},
|
||||
{
|
||||
name: 'Czech (Czech Republic)',
|
||||
value: 'cs-CZ',
|
||||
valueMix: 'ces-CZE',
|
||||
},
|
||||
{
|
||||
name: 'Danish (Denmark)',
|
||||
value: 'da-DK',
|
||||
valueMix: 'dan-DNK',
|
||||
},
|
||||
{
|
||||
name: 'Dutch (Netherlands)',
|
||||
value: 'nl-NL',
|
||||
valueMix: 'nld-NLD',
|
||||
},
|
||||
{
|
||||
name: 'English (Australia)',
|
||||
value: 'en-AU',
|
||||
valueMix: 'eng-AUS',
|
||||
},
|
||||
{
|
||||
name: 'English (United States)',
|
||||
value: 'en-US',
|
||||
valueMix: 'eng-USA',
|
||||
},
|
||||
{
|
||||
name: 'English (India)',
|
||||
value: 'en-IN',
|
||||
valueMix: 'eng-IND',
|
||||
},
|
||||
{
|
||||
name: 'English (United Kingdom)',
|
||||
value: 'en-GB',
|
||||
valueMix: 'eng-GBR',
|
||||
},
|
||||
{
|
||||
name: 'Finnish (Finland)',
|
||||
value: 'fi-FI',
|
||||
valueMix: 'fin-FIN',
|
||||
},
|
||||
{
|
||||
name: 'French (Canada)',
|
||||
value: 'fr-CA',
|
||||
valueMix: 'fra-CAN',
|
||||
},
|
||||
{
|
||||
name: 'French (France)',
|
||||
value: 'fr-FR',
|
||||
valueMix: 'fra-FRA',
|
||||
},
|
||||
{
|
||||
name: 'German (Germany)',
|
||||
value: 'de-DE',
|
||||
valueMix: 'deu-DEU',
|
||||
},
|
||||
{
|
||||
name: 'Greek (Greece)',
|
||||
value: 'el-GR',
|
||||
valueMix: 'ell-GRC',
|
||||
},
|
||||
{
|
||||
name: 'Hebrew (Israel)',
|
||||
value: 'he-IL',
|
||||
valueMix: 'heb-ISR',
|
||||
},
|
||||
{
|
||||
name: 'Hindi (India)',
|
||||
value: 'hi-IN',
|
||||
valueMix: 'hin-IND',
|
||||
},
|
||||
{
|
||||
name: 'Hungarian (Hungary)',
|
||||
value: 'hu-HU',
|
||||
valueMix: 'hun-HUN',
|
||||
},
|
||||
{
|
||||
name: 'Indonesian (Indonesia)',
|
||||
value: 'id-ID',
|
||||
valueMix: 'ind-IDN',
|
||||
},
|
||||
{
|
||||
name: 'Italian (Italy)',
|
||||
value: 'it-IT',
|
||||
valueMix: 'ita-ITA',
|
||||
},
|
||||
{
|
||||
name: 'Japanese (Japan)',
|
||||
value: 'ja-JP',
|
||||
valueMix: 'jpn-JPN',
|
||||
},
|
||||
{
|
||||
name: 'Korean (South Korea)',
|
||||
value: 'ko-KR',
|
||||
valueMix: 'kor-KOR',
|
||||
},
|
||||
{
|
||||
name: 'Malay (Malaysia)',
|
||||
value: 'ms-MY',
|
||||
valueMix: 'zlm-MYS',
|
||||
},
|
||||
{
|
||||
name: 'Norwegian (Norway)',
|
||||
value: 'no-NO',
|
||||
valueMix: 'nor-NOR',
|
||||
},
|
||||
{
|
||||
name: 'Polish (Poland)',
|
||||
value: 'pl-PL',
|
||||
valueMix: 'pol-POL',
|
||||
},
|
||||
{
|
||||
name: 'Portuguese (Brazil)',
|
||||
value: 'pt-BR',
|
||||
valueMix: 'por-BRA',
|
||||
},
|
||||
{
|
||||
name: 'Portuguese (Portugal)',
|
||||
value: 'pt-PT',
|
||||
valueMix: 'por-PRT',
|
||||
},
|
||||
{
|
||||
name: 'Romanian (Romania)',
|
||||
value: 'ro-RO',
|
||||
valueMix: 'ron-ROU',
|
||||
},
|
||||
{
|
||||
name: 'Russian (Russia)',
|
||||
value: 'ru-RU',
|
||||
valueMix: 'rus-RUS',
|
||||
},
|
||||
{
|
||||
name: 'Shanghainese (China)',
|
||||
value: 'zh-WU',
|
||||
valueMix: 'wuu-CHN',
|
||||
},
|
||||
{
|
||||
name: 'Mandarin (China)',
|
||||
value: 'zh-CN',
|
||||
valueMix: 'cmn-CHN',
|
||||
},
|
||||
{
|
||||
name: 'Slovak (Slovakia)',
|
||||
value: 'sk-SK',
|
||||
valueMix: 'slk-SVK',
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Spain)',
|
||||
value: 'es-ES',
|
||||
valueMix: 'spa-ESP',
|
||||
},
|
||||
{
|
||||
name: 'Spanish (Latin America)',
|
||||
value: 'es-US',
|
||||
valueMix: 'spa-XLA',
|
||||
},
|
||||
{
|
||||
name: 'Swedish (Sweden)',
|
||||
value: 'sv-SE',
|
||||
valueMix: 'swe-SWE',
|
||||
},
|
||||
{
|
||||
name: 'Thai (Thailand)',
|
||||
value: 'th-TH',
|
||||
valueMix: 'tha-THA',
|
||||
},
|
||||
{
|
||||
name: 'Cantonese (Hong Kong)',
|
||||
value: 'cn-HK',
|
||||
valueMix: 'yue-CHS',
|
||||
},
|
||||
{
|
||||
name: 'Mandarin (Taiwan)',
|
||||
value: 'zh-TW',
|
||||
valueMix: 'cmn-TWN',
|
||||
},
|
||||
{
|
||||
name: 'Turkish (Turkey)',
|
||||
value: 'tr-TR',
|
||||
valueMix: 'tur-TUR',
|
||||
},
|
||||
{
|
||||
name: 'Ukrainian (Ukraine)',
|
||||
value: 'uk-UA',
|
||||
valueMix: 'ukr-UKR',
|
||||
},
|
||||
{
|
||||
name: 'Vietnamese (Vietnam)',
|
||||
value: 'vi-VN',
|
||||
valueMix: 'vie-VNM',
|
||||
},
|
||||
];
|
||||
58
lib/utils/speech-data/stt-nvidia.js
Normal file
58
lib/utils/speech-data/stt-nvidia.js
Normal file
@@ -0,0 +1,58 @@
|
||||
module.exports = [
|
||||
{
|
||||
name: 'Arabic',
|
||||
value: 'ar-AR',
|
||||
},
|
||||
{
|
||||
name: 'English',
|
||||
value: 'en-US',
|
||||
},
|
||||
{
|
||||
name: 'English - GB',
|
||||
value: 'en-GB',
|
||||
},
|
||||
{
|
||||
name: 'Spanish - US',
|
||||
value: 'es-US',
|
||||
},
|
||||
{
|
||||
name: 'Spanish',
|
||||
value: 'es-ES',
|
||||
},
|
||||
{
|
||||
name: 'German',
|
||||
value: 'de-DE',
|
||||
},
|
||||
{
|
||||
name: 'French',
|
||||
value: 'fr-FR',
|
||||
},
|
||||
{
|
||||
name: 'Hindi',
|
||||
value: 'hi-IN',
|
||||
},
|
||||
{
|
||||
name: 'Russian',
|
||||
value: 'ru-RU',
|
||||
},
|
||||
{
|
||||
name: 'Korean',
|
||||
value: 'ko-KR',
|
||||
},
|
||||
{
|
||||
name: 'Brazilian-Portuguese',
|
||||
value: 'pt-BR',
|
||||
},
|
||||
{
|
||||
name: 'Japanese',
|
||||
value: 'ja-JP',
|
||||
},
|
||||
{
|
||||
name: 'Italian',
|
||||
value: 'it-IT',
|
||||
},
|
||||
{
|
||||
name: 'Mandarin',
|
||||
value: 'zh-CN',
|
||||
},
|
||||
];
|
||||
6
lib/utils/speech-data/stt-soniox.js
Normal file
6
lib/utils/speech-data/stt-soniox.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = [
|
||||
{
|
||||
name: 'English (United States)',
|
||||
value: 'en-US',
|
||||
},
|
||||
];
|
||||
213
lib/utils/speech-data/tts-aws.js
Normal file
213
lib/utils/speech-data/tts-aws.js
Normal file
@@ -0,0 +1,213 @@
|
||||
module.exports = [
|
||||
{
|
||||
value: 'arb',
|
||||
name: 'Arabic',
|
||||
voices: [{ value: 'Zeina', name: 'Zeina (Female)' }],
|
||||
},
|
||||
{
|
||||
value: 'cmn-CN',
|
||||
name: 'Chinese, Mandarin',
|
||||
voices: [{ value: 'Zhiyu', name: 'Zhiyu (Female)' }],
|
||||
},
|
||||
{
|
||||
value: 'da-DK',
|
||||
name: 'Danish',
|
||||
voices: [
|
||||
{ value: 'Naja', name: 'Naja (Female)' },
|
||||
{ value: 'Mads', name: 'Mads (Male)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'nl-NL',
|
||||
name: 'Dutch',
|
||||
voices: [
|
||||
{ value: 'Lotte', name: 'Lotte (Female)' },
|
||||
{ value: 'Ruben', name: 'Ruben (Male)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'en-AU',
|
||||
name: 'English (Australian)',
|
||||
voices: [
|
||||
{ value: 'Nicole', name: 'Nicole (Female)' },
|
||||
{ value: 'Russell', name: 'Russell (Male)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'en-GB',
|
||||
name: 'English (British)',
|
||||
voices: [
|
||||
{ value: 'Amy', name: 'Amy (Female)' },
|
||||
{ value: 'Emma', name: 'Emma (Female)' },
|
||||
{ value: 'Brian', name: 'Brian (Male)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'en-IN',
|
||||
name: 'English (Indian)',
|
||||
voices: [
|
||||
{ value: 'Aditi', name: 'Aditi (Female)' },
|
||||
{ value: 'Raveena', name: 'Raveena (Female)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'en-US',
|
||||
name: 'English (US)',
|
||||
voices: [
|
||||
{ value: 'Joanna', name: 'Joanna (Female)' },
|
||||
{ value: 'Kendra', name: 'Kendra (Female)' },
|
||||
{ value: 'Kimberly', name: 'Kimberly (Female)' },
|
||||
{ value: 'Ivy', name: 'Ivy (Female child)' },
|
||||
{ value: 'Salli', name: 'Salli (Female)' },
|
||||
{ value: 'Joey', name: 'Joey (Male)' },
|
||||
{ value: 'Matthew', name: 'Matthew (Male)' },
|
||||
{ value: 'Justin', name: 'Justin (Male child)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'en-GB-WLS',
|
||||
name: 'English (Welsh)',
|
||||
voices: [{ value: 'Geraint', name: 'Geraint (Male)' }],
|
||||
},
|
||||
{
|
||||
value: 'fr-FR',
|
||||
name: 'French',
|
||||
voices: [
|
||||
{ value: 'Celine', name: 'Céline (Female)' },
|
||||
{ value: 'Lea', name: 'Léa (Female)' },
|
||||
{ value: 'Mathieu', name: 'Mathieu (Male)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'fr-CA',
|
||||
name: 'French (Canadian)',
|
||||
voices: [{ value: 'Chantal', name: 'Chantal (Female)' }],
|
||||
},
|
||||
{
|
||||
value: 'de-DE',
|
||||
name: 'German',
|
||||
voices: [
|
||||
{ value: 'Marlene', name: 'Marlene (Female)' },
|
||||
{ value: 'Vicki', name: 'Vicki (Female)' },
|
||||
{ value: 'Hans', name: 'Hans (Male)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'hi-IN',
|
||||
name: 'Hindi',
|
||||
voices: [{ value: 'Aditi', name: 'Aditi (Female)' }],
|
||||
},
|
||||
{
|
||||
value: 'is-IS',
|
||||
name: 'Icelandic',
|
||||
voices: [
|
||||
{ value: 'Dora', name: 'Dóra (Female)' },
|
||||
{ value: 'Karl', name: 'Karl (Male)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'it-IT',
|
||||
name: 'Italian',
|
||||
voices: [
|
||||
{ value: 'Carla', name: 'Carla (Female)' },
|
||||
{ value: 'Bianca', name: 'Bianca (Female)' },
|
||||
{ value: 'Giorgio', name: 'Giorgio (Male)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'ja-JP',
|
||||
name: 'Japanese',
|
||||
voices: [
|
||||
{ value: 'Mizuki', name: 'Mizuki (Female)' },
|
||||
{ value: 'Takumi', name: 'Takumi (Male)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'ko-KR',
|
||||
name: 'Korean',
|
||||
voices: [{ value: 'Seoyeon', name: 'Seoyeon (Female)' }],
|
||||
},
|
||||
{
|
||||
value: 'nb-NO',
|
||||
name: 'Norwegian',
|
||||
voices: [{ value: 'Liv', name: 'Liv (Female)' }],
|
||||
},
|
||||
{
|
||||
value: 'pl-PL',
|
||||
name: 'Polish',
|
||||
voices: [
|
||||
{ value: 'Ewa', name: 'Ewa (Female)' },
|
||||
{ value: 'Maja', name: 'Maja (Female)' },
|
||||
{ value: 'Jacek', name: 'Jacek (Male)' },
|
||||
{ value: 'Jan', name: 'Jan (Male)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'pt-BR',
|
||||
name: 'Portuguese (Brazilian)',
|
||||
voices: [
|
||||
{ value: 'Camila', name: 'Camila (Female)' },
|
||||
{ value: 'Vitoria', name: 'Vitória (Female)' },
|
||||
{ value: 'Ricardo', name: 'Ricardo (Male)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'pt-PT',
|
||||
name: 'Portuguese (European)',
|
||||
voices: [
|
||||
{ value: 'Ines', name: 'Inês (Female)' },
|
||||
{ value: 'Cristiano', name: 'Cristiano (Male)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'ro-RO',
|
||||
name: 'Romanian',
|
||||
voices: [{ value: 'Carmen', name: 'Carmen (Female)' }],
|
||||
},
|
||||
{
|
||||
value: 'ru-RU',
|
||||
name: 'Russian',
|
||||
voices: [
|
||||
{ value: 'Tatyana', name: 'Tatyana (Female)' },
|
||||
{ value: 'Maxim', name: 'Maxim (Male)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'es-ES',
|
||||
name: 'Spanish (European)',
|
||||
voices: [
|
||||
{ value: 'Conchita', name: 'Conchita (Female)' },
|
||||
{ value: 'Lucia', name: 'Lucia (Female)' },
|
||||
{ value: 'Enrique', name: 'Enrique (Male)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'es-MX',
|
||||
name: 'Spanish (Mexican)',
|
||||
voices: [{ value: 'Mia', name: 'Mia (Female)' }],
|
||||
},
|
||||
{
|
||||
value: 'es-US',
|
||||
name: 'Spanish (US)',
|
||||
voices: [
|
||||
{ value: 'Lupe', name: 'Lupe (Female)' },
|
||||
{ value: 'Penelope', name: 'Penélope (Female)' },
|
||||
{ value: 'Miguel', name: 'Miguel (Male)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'sv-SE',
|
||||
name: 'Swedish',
|
||||
voices: [{ value: 'Astrid', name: 'Astrid (Female)' }],
|
||||
},
|
||||
{
|
||||
value: 'tr-TR',
|
||||
name: 'Turkish',
|
||||
voices: [{ value: 'Filiz', name: 'Filiz (Female)' }],
|
||||
},
|
||||
{
|
||||
value: 'cy-GB',
|
||||
name: 'Welsh',
|
||||
voices: [{ value: 'Gwyneth', name: 'Gwyneth (Female)' }],
|
||||
},
|
||||
];
|
||||
192
lib/utils/speech-data/tts-elevenlabs.js
Normal file
192
lib/utils/speech-data/tts-elevenlabs.js
Normal file
@@ -0,0 +1,192 @@
|
||||
module.exports = [
|
||||
{
|
||||
value: 'ar',
|
||||
name: 'Arabic',
|
||||
voices: [
|
||||
{
|
||||
value: 'pNInz6obpgDQGcFmaJgB',
|
||||
name: 'Adam - american, deep, middle aged, male, narration',
|
||||
},
|
||||
{
|
||||
value: 'ErXwobaYiN019PkySvjV',
|
||||
name: 'Antoni - american, well-rounded, young, male, narration',
|
||||
},
|
||||
{
|
||||
value: 'VR6AewLTigWG4xSOukaG',
|
||||
name: 'Arnold - american, crisp, middle aged, male, narration',
|
||||
},
|
||||
{
|
||||
value: 'EXAVITQu4vr4xnSDxMaL',
|
||||
name: 'Bella - american, soft, young, female, narration',
|
||||
},
|
||||
{
|
||||
value: 'N2lVS1w4EtoT3dr4eOWO',
|
||||
name: 'Callum - american, hoarse, middle aged, male, video games',
|
||||
},
|
||||
{
|
||||
value: 'IKne3meq5aSn9XLyUdCD',
|
||||
name: 'Charlie - australian, casual, middle aged, male, conversational',
|
||||
},
|
||||
{
|
||||
value: 'XB0fDUnXU5powFXDhCwa',
|
||||
name: 'Charlotte - english-swedish, seductive, middle aged, female, video games',
|
||||
},
|
||||
{
|
||||
value: '2EiwWnXFnvU5JabPnv8n',
|
||||
name: 'Clyde - american, war veteran, middle aged, male, video games',
|
||||
},
|
||||
{
|
||||
value: 'onwK4e9ZLuTAKqWW03F9',
|
||||
name: 'Daniel - british, deep, middle aged, male, news presenter',
|
||||
},
|
||||
{
|
||||
value: 'CYw3kZ02Hs0563khs1Fj',
|
||||
name: 'Dave - british-essex, conversational, young, male, video games',
|
||||
},
|
||||
{
|
||||
value: 'AZnzlk1XvdvUeBnXmlld',
|
||||
name: 'Domi - american, strong, young, female, narration',
|
||||
},
|
||||
{
|
||||
value: 'ThT5KcBeYPX3keUQqHPh',
|
||||
name: "Dorothy - british, pleasant, young, female, children's stories",
|
||||
},
|
||||
{
|
||||
value: 'MF3mGyEYCl7XYWbV9V6O',
|
||||
name: 'Elli - american, emotional, young, female, narration',
|
||||
},
|
||||
{
|
||||
value: 'LcfcDJNUP1GQjkzn1xUU',
|
||||
name: 'Emily - american, calm, young, female, meditation',
|
||||
},
|
||||
{
|
||||
value: 'g5CIjZEefAph4nQFvHAz',
|
||||
name: 'Ethan - american, undefined, young, male, ASMR',
|
||||
},
|
||||
{
|
||||
value: 'D38z5RcWu1voky8WS1ja',
|
||||
name: 'Fin - irish, sailor, old, male, video games',
|
||||
},
|
||||
{
|
||||
value: 'jsCqWAovK2LkecY7zXl4',
|
||||
name: 'Freya - american, undefined, young, female, undefined',
|
||||
},
|
||||
{
|
||||
value: 'jBpfuIE2acCO8z3wKNLl',
|
||||
name: 'Gigi - american, childlish, young, female, animation',
|
||||
},
|
||||
{
|
||||
value: 'zcAOhNBS3c14rBihAFp1',
|
||||
name: 'Giovanni - english-italian, foreigner, young, male, audiobook',
|
||||
},
|
||||
{
|
||||
value: 'z9fAnlkpzviPz146aGWa',
|
||||
name: 'Glinda - american, witch, middle aged, female, video games',
|
||||
},
|
||||
{
|
||||
value: 'oWAxZDx7w5VEj9dCyTzz',
|
||||
name: 'Grace - american-southern, undefined, young, female, audiobook ',
|
||||
},
|
||||
{
|
||||
value: 'SOYHLrjzK2X1ezoPC6cr',
|
||||
name: 'Harry - american, anxious, young, male, video games',
|
||||
},
|
||||
{
|
||||
value: 'ZQe5CZNOzWyzPSCn5a3c',
|
||||
name: 'James - australian, calm , old, male, news',
|
||||
},
|
||||
{
|
||||
value: 'bVMeCyTHy58xNoL34h3p',
|
||||
name: 'Jeremy - american-irish, excited, young, male, narration',
|
||||
},
|
||||
{
|
||||
value: 't0jbNlBVZ17f02VDIeMI',
|
||||
name: 'Jessie - american, raspy , old, male, video games',
|
||||
},
|
||||
{
|
||||
value: 'Zlb1dXrM653N07WRdFW3',
|
||||
name: 'Joseph - british, undefined, middle aged, male, news',
|
||||
},
|
||||
{
|
||||
value: 'TxGEqnHWrfWFTfGW9XjX',
|
||||
name: 'Josh - american, deep, young, male, narration',
|
||||
},
|
||||
{
|
||||
value: 'TX3LPaxmHKxFdv7VOQHJ',
|
||||
name: 'Liam - american, undefined, young, male, narration',
|
||||
},
|
||||
{
|
||||
value: 'XrExE9yKIg1WjnnlVkGX',
|
||||
name: 'Matilda - american, warm, young, female, audiobook',
|
||||
},
|
||||
{
|
||||
value: 'Yko7PKHZNXotIFUBG7I9',
|
||||
name: 'Matthew - british, undefined, middle aged, male, audiobook',
|
||||
},
|
||||
{
|
||||
value: 'flq6f7yk4E4fJM5XTYuZ',
|
||||
name: 'Michael - american, undefined, old, male, audiobook',
|
||||
},
|
||||
{
|
||||
value: 'zrHiDhphv9ZnVXBqCLjz',
|
||||
name: 'Mimi - english-swedish, childish, young, female, animation',
|
||||
},
|
||||
{
|
||||
value: 'piTKgcLEGmPE4e6mEKli',
|
||||
name: 'Nicole - american, whisper, young, female, audiobook',
|
||||
},
|
||||
{
|
||||
value: 'ODq5zmih8GrVes37Dizd',
|
||||
name: 'Patrick - american, shouty, middle aged, male, video games',
|
||||
},
|
||||
{
|
||||
value: '21m00Tcm4TlvDq8ikWAM',
|
||||
name: 'Rachel - american, calm, young, female, narration',
|
||||
},
|
||||
{
|
||||
value: 'wViXBPUzp2ZZixB1xQuM',
|
||||
name: 'Ryan - american, soldier, middle aged, male, audiobook',
|
||||
},
|
||||
{
|
||||
value: 'yoZ06aMxZJJ28mfd3POQ',
|
||||
name: 'Sam - american, raspy, young, male, narration',
|
||||
},
|
||||
{
|
||||
value: 'pMsXgVXv3BLzUgSXRplE',
|
||||
name: 'Serena - american, pleasant, middle aged, female, interactive',
|
||||
},
|
||||
{
|
||||
value: 'GBv7mTt0atIp3Br8iCZE',
|
||||
name: 'Thomas - american, calm, young, male, meditation',
|
||||
},
|
||||
],
|
||||
},
|
||||
{ value: 'bg', name: 'Bulgarian', voices: [] },
|
||||
{ value: 'zh', name: 'Chinese', voices: [] },
|
||||
{ value: 'hr', name: 'Croatian', voices: [] },
|
||||
{ value: 'cs', name: 'Czech', voices: [] },
|
||||
{ value: 'da', name: 'Danish', voices: [] },
|
||||
{ value: 'nl', name: 'Dutch', voices: [] },
|
||||
{ value: 'en', name: 'English', voices: [] },
|
||||
{ value: 'fil', name: 'Filipino', voices: [] },
|
||||
{ value: 'fi', name: 'Finnish', voices: [] },
|
||||
{ value: 'fr', name: 'French', voices: [] },
|
||||
{ value: 'de', name: 'German', voices: [] },
|
||||
{ value: 'el', name: 'Greek', voices: [] },
|
||||
{ value: 'hi', name: 'Hindi', voices: [] },
|
||||
{ value: 'id', name: 'Indonesian', voices: [] },
|
||||
{ value: 'it', name: 'Italian', voices: [] },
|
||||
{ value: 'ja', name: 'Japanese', voices: [] },
|
||||
{ value: 'ko', name: 'Korean', voices: [] },
|
||||
{ value: 'ms', name: 'Malay', voices: [] },
|
||||
{ value: 'pl', name: 'Polish', voices: [] },
|
||||
{ value: 'pt', name: 'Portuguese', voices: [] },
|
||||
{ value: 'ro', name: 'Romanian', voices: [] },
|
||||
{ value: 'ru', name: 'Russian', voices: [] },
|
||||
{ value: 'sk', name: 'Slovak', voices: [] },
|
||||
{ value: 'es', name: 'Spanish', voices: [] },
|
||||
{ value: 'sv', name: 'Swedish', voices: [] },
|
||||
{ value: 'ta', name: 'Tamil', voices: [] },
|
||||
{ value: 'tr', name: 'Turkish', voices: [] },
|
||||
{ value: 'uk', name: 'Ukrainian', voices: [] },
|
||||
];
|
||||
796
lib/utils/speech-data/tts-google.js
Normal file
796
lib/utils/speech-data/tts-google.js
Normal file
@@ -0,0 +1,796 @@
|
||||
module.exports = [
|
||||
{
|
||||
value: 'ar-XA',
|
||||
name: 'Arabic',
|
||||
voices: [
|
||||
{ value: 'ar-XA-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'ar-XA-Standard-B', name: 'Standard-B (Male)' },
|
||||
{ value: 'ar-XA-Standard-C', name: 'Standard-C (Male)' },
|
||||
{ value: 'ar-XA-Standard-D', name: 'Standard-D (Female)' },
|
||||
{ value: 'ar-XA-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'ar-XA-Wavenet-B', name: 'Wavenet-B (Male)' },
|
||||
{ value: 'ar-XA-Wavenet-C', name: 'Wavenet-C (Male)' },
|
||||
{ value: 'ar-XA-Wavenet-D', name: 'Wavenet-D (Female)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'af-ZA',
|
||||
name: 'Afrikaans (South Africa)',
|
||||
voices: [{ value: 'af-ZA-Standard-A', name: 'Standard-A (Female)' }],
|
||||
},
|
||||
{
|
||||
value: 'bn-IN',
|
||||
name: 'Bengali (India)',
|
||||
voices: [
|
||||
{ value: 'bn-IN-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'bn-IN-Standard-B', name: 'Standard-B (Male)' },
|
||||
{ value: 'bn-IN-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'bn-IN-Wavenet-B', name: 'Wavenet-B (Male)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'bg-BG',
|
||||
name: 'Bulgarian (Bulgaria)',
|
||||
voices: [{ value: 'bg-BG-Standard-A', name: 'Standard-A (Female)' }],
|
||||
},
|
||||
{
|
||||
value: 'ca-ES',
|
||||
name: 'Catalan (Spain)',
|
||||
voices: [{ value: 'ca-ES-Standard-A', name: 'Standard-A (Female)' }],
|
||||
},
|
||||
{
|
||||
value: 'cs-CZ',
|
||||
name: 'Czech (Czech Republic)',
|
||||
voices: [
|
||||
{ value: 'cs-CZ-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'cs-CZ-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'da-DK',
|
||||
name: 'Danish (Denmark)',
|
||||
voices: [
|
||||
{ value: 'da-DK-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'da-DK-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'da-DK-Neural2-D', name: 'Neural2-D (Female)' },
|
||||
{ value: 'da-DK-Neural2-F', name: 'Neural2-F (Male)' },
|
||||
|
||||
{ value: 'da-DK-Standard-C', name: 'Standard-C (Male)' },
|
||||
{ value: 'da-DK-Standard-D', name: 'Standard-D (Female)' },
|
||||
{ value: 'da-DK-Standard-E', name: 'Standard-E (Female)' },
|
||||
{ value: 'da-DK-Wavenet-C', name: 'Wavenet-C (Male)' },
|
||||
{ value: 'da-DK-Wavenet-D', name: 'Wavenet-D (Female)' },
|
||||
{ value: 'da-DK-Wavenet-E', name: 'Wavenet-E (Female)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'eu-ES',
|
||||
name: 'Basque (Spain)',
|
||||
voices: [{ value: 'eu-ES-Standard-A', name: 'Standard-A (Female)' }],
|
||||
},
|
||||
{
|
||||
value: 'nl-NL',
|
||||
name: 'Dutch (Netherlands)',
|
||||
voices: [
|
||||
{ value: 'nl-NL-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'nl-NL-Standard-B', name: 'Standard-B (Male)' },
|
||||
{ value: 'nl-NL-Standard-C', name: 'Standard-C (Male)' },
|
||||
{ value: 'nl-NL-Standard-D', name: 'Standard-D (Female)' },
|
||||
{ value: 'nl-NL-Standard-E', name: 'Standard-E (Female)' },
|
||||
{ value: 'nl-NL-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'nl-NL-Wavenet-B', name: 'Wavenet-B (Male)' },
|
||||
{ value: 'nl-NL-Wavenet-C', name: 'Wavenet-C (Male)' },
|
||||
{ value: 'nl-NL-Wavenet-D', name: 'Wavenet-D (Female)' },
|
||||
{ value: 'nl-NL-Wavenet-E', name: 'Wavenet-E (Female)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'en-AU',
|
||||
name: 'English (Australia)',
|
||||
voices: [
|
||||
{ value: 'en-AU-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'en-AU-Standard-B', name: 'Standard-B (Male)' },
|
||||
{ value: 'en-AU-Standard-C', name: 'Standard-C (Female)' },
|
||||
{ value: 'en-AU-Standard-D', name: 'Standard-D (Male)' },
|
||||
{ value: 'en-AU-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'en-AU-Wavenet-B', name: 'Wavenet-B (Male)' },
|
||||
{ value: 'en-AU-Wavenet-C', name: 'Wavenet-C (Female)' },
|
||||
{ value: 'en-AU-Wavenet-D', name: 'Wavenet-D (Male)' },
|
||||
{ value: 'en-AU-Neural2-A', name: 'Neural2-A (Female)' },
|
||||
{ value: 'en-AU-Neural2-B', name: 'Neural2-B (Male)' },
|
||||
{ value: 'en-AU-Neural2-C', name: 'Neural2-C (Female)' },
|
||||
{ value: 'en-AU-Neural2-D', name: 'Neural2-D (Male)' },
|
||||
{ value: 'en-AU-Polyglot-1', name: 'Polyglot-1 (Male)' },
|
||||
{ value: 'en-AU-News-E', name: 'News-E (Female)' },
|
||||
{ value: 'en-AU-News-F', name: 'News-F (Female)' },
|
||||
{ value: 'en-AU-News-G', name: 'News-G (Male)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'en-IN',
|
||||
name: 'English (India)',
|
||||
voices: [
|
||||
{ value: 'en-IN-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'en-IN-Standard-B', name: 'Standard-B (Male)' },
|
||||
{ value: 'en-IN-Standard-C', name: 'Standard-C (Male)' },
|
||||
{ value: 'en-IN-Standard-D', name: 'Standard-D (Female)' },
|
||||
{ value: 'en-IN-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'en-IN-Wavenet-B', name: 'Wavenet-B (Male)' },
|
||||
{ value: 'en-IN-Wavenet-C', name: 'Wavenet-C (Male)' },
|
||||
{ value: 'en-IN-Wavenet-D', name: 'Wavenet-D (Female)' },
|
||||
|
||||
{ value: 'en-IN-Neural2-A', name: 'Neural2-A (Female)' },
|
||||
{ value: 'en-IN-Neural2-B', name: 'Neural2-B (Male)' },
|
||||
{ value: 'en-IN-Neural2-C', name: 'Neural2-C (Male)' },
|
||||
{ value: 'en-IN-Neural2-D', name: 'Neural2-D (Female)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'en-GB',
|
||||
name: 'English (UK)',
|
||||
voices: [
|
||||
{ value: 'en-GB-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'en-GB-Standard-B', name: 'Standard-B (Male)' },
|
||||
{ value: 'en-GB-Standard-C', name: 'Standard-C (Female)' },
|
||||
{ value: 'en-GB-Standard-D', name: 'Standard-D (Male)' },
|
||||
{ value: 'en-GB-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'en-GB-Wavenet-B', name: 'Wavenet-B (Male)' },
|
||||
{ value: 'en-GB-Wavenet-C', name: 'Wavenet-C (Female)' },
|
||||
{ value: 'en-GB-Wavenet-D', name: 'Wavenet-D (Male)' },
|
||||
{ value: 'en-GB-Neural2-A', name: 'Neural2-A (Female)' },
|
||||
{ value: 'en-GB-Neural2-B', name: 'Neural2-B (Male)' },
|
||||
{ value: 'en-GB-Neural2-C', name: 'Neural2-C (Female)' },
|
||||
{ value: 'en-GB-Neural2-D', name: 'Neural2-D (Male)' },
|
||||
{ value: 'en-GB-Neural2-F', name: 'Neural2-F (Female)' },
|
||||
{ value: 'en-GB-News-G', name: 'News-G (Female)' },
|
||||
{ value: 'en-GB-News-H', name: 'News-H (Female)' },
|
||||
{ value: 'en-GB-News-I', name: 'News-I (Female)' },
|
||||
{ value: 'en-GB-News-J', name: 'News-J (Male)' },
|
||||
{ value: 'en-GB-News-K', name: 'News-K (Male)' },
|
||||
{ value: 'en-GB-News-L', name: 'News-L (Male)' },
|
||||
{ value: 'en-GB-News-M', name: 'News-M (Male)' },
|
||||
|
||||
{ value: 'en-GB-Studio-B', name: 'Studio-B (Male)' },
|
||||
{ value: 'en-GB-Studio-C', name: 'Studio-C (Female)' },
|
||||
{ value: 'en-GB-Wavenet-F', name: 'Wavenet-F (Female)' },
|
||||
{ value: 'en-GB-Standard-F', name: 'Standard-F (Female)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'en-US',
|
||||
name: 'English (US)',
|
||||
voices: [
|
||||
{ value: 'en-US-Standard-B', name: 'Standard-B (Male)' },
|
||||
{ value: 'en-US-Standard-C', name: 'Standard-C (Female)' },
|
||||
{ value: 'en-US-Standard-D', name: 'Standard-D (Male)' },
|
||||
{ value: 'en-US-Standard-E', name: 'Standard-E (Female)' },
|
||||
{ value: 'en-US-Wavenet-A', name: 'Wavenet-A (Male)' },
|
||||
{ value: 'en-US-Wavenet-B', name: 'Wavenet-B (Male)' },
|
||||
{ value: 'en-US-Wavenet-C', name: 'Wavenet-C (Female)' },
|
||||
{ value: 'en-US-Wavenet-D', name: 'Wavenet-D (Male)' },
|
||||
{ value: 'en-US-Wavenet-E', name: 'Wavenet-E (Female)' },
|
||||
{ value: 'en-US-Wavenet-F', name: 'Wavenet-F (Female)' },
|
||||
{ value: 'en-US-Neural2-A', name: 'Neural2-A (Male)' },
|
||||
{ value: 'en-US-Neural2-C', name: 'Neural2-C (Female)' },
|
||||
{ value: 'en-US-Neural2-D', name: 'Neural2-D (Male)' },
|
||||
{ value: 'en-US-Neural2-E', name: 'Neural2-E (Female)' },
|
||||
{ value: 'en-US-Neural2-F', name: 'Neural2-F (Female)' },
|
||||
{ value: 'en-US-Neural2-G', name: 'Neural2-G (Female)' },
|
||||
{ value: 'en-US-Neural2-H', name: 'Neural2-H (Female)' },
|
||||
{ value: 'en-US-Neural2-I', name: 'Neural2-I (Male)' },
|
||||
{ value: 'en-US-Neural2-J', name: 'Neural2-J (Male)' },
|
||||
{ value: 'en-US-Studio-M', name: 'Studio-M (Male)' },
|
||||
{ value: 'en-US-Studio-O', name: 'Studio-M (Female)' },
|
||||
{ value: 'en-US-Polyglot-1', name: 'Polyglot-1 (Male)' },
|
||||
{ value: 'en-US-News-K', name: 'News-K (Female)' },
|
||||
{ value: 'en-US-News-L', name: 'News-L (Female)' },
|
||||
{ value: 'en-US-News-M', name: 'News-M (Male)' },
|
||||
{ value: 'en-US-News-N', name: 'News-N (Male)' },
|
||||
|
||||
{ value: 'en-US-Standard-A', name: 'Standard-A (Male)' },
|
||||
{ value: 'en-US-Standard-F', name: 'Standard-F (Female)' },
|
||||
{ value: 'en-US-Standard-G', name: 'Standard-G (Female)' },
|
||||
{ value: 'en-US-Standard-H', name: 'Standard-H (Female)' },
|
||||
{ value: 'en-US-Standard-I', name: 'Standard-I (Male)' },
|
||||
{ value: 'en-US-Standard-J', name: 'Standard-J (Male)' },
|
||||
{ value: 'en-US-Wavenet-G', name: 'Wavenet-G (Female)' },
|
||||
{ value: 'en-US-Wavenet-H', name: 'Wavenet-H (Female)' },
|
||||
{ value: 'en-US-Wavenet-I', name: 'Wavenet-I (Male)' },
|
||||
{ value: 'en-US-Wavenet-J', name: 'Wavenet-J (Male)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'fil-PH',
|
||||
name: 'Filipino (Philippines)',
|
||||
voices: [
|
||||
{ value: 'fil-PH-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'fil-PH-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'fil-ph-Neural2-A', name: 'Neural2-A (Female)' },
|
||||
{ value: 'fil-ph-Neural2-D', name: 'Neural2-A (Male)' },
|
||||
|
||||
{ value: 'fil-PH-Standard-B', name: 'Standard-B (Female)' },
|
||||
{ value: 'fil-PH-Standard-C', name: 'Standard-C (Male)' },
|
||||
{ value: 'fil-PH-Standard-D', name: 'Standard-D (Male)' },
|
||||
{ value: 'fil-PH-Wavenet-B', name: 'Wavenet-B (Female)' },
|
||||
{ value: 'fil-PH-Wavenet-C', name: 'Wavenet-C (Male)' },
|
||||
{ value: 'fil-PH-Wavenet-D', name: 'Wavenet-D (Male)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'fi-FI',
|
||||
name: 'Finnish (Finland)',
|
||||
voices: [
|
||||
{ value: 'fi-FI-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'fi-FI-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'fr-CA',
|
||||
name: 'French (Canada)',
|
||||
voices: [
|
||||
{ value: 'fr-CA-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'fr-CA-Standard-B', name: 'Standard-B (Male)' },
|
||||
{ value: 'fr-CA-Standard-C', name: 'Standard-C (Female)' },
|
||||
{ value: 'fr-CA-Standard-D', name: 'Standard-D (Male)' },
|
||||
{ value: 'fr-CA-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'fr-CA-Wavenet-B', name: 'Wavenet-B (Male)' },
|
||||
{ value: 'fr-CA-Wavenet-C', name: 'Wavenet-C (Female)' },
|
||||
{ value: 'fr-CA-Wavenet-D', name: 'Wavenet-D (Male)' },
|
||||
{ value: 'fr-CA-Neural2-A', name: 'Neural2-A (Female)' },
|
||||
{ value: 'fr-CA-Neural2-B', name: 'Neural2-B (Male)' },
|
||||
{ value: 'fr-CA-Neural2-C', name: 'Neural2-C (Female)' },
|
||||
{ value: 'fr-CA-Neural2-D', name: 'Neural2-D (Male)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'fr-FR',
|
||||
name: 'French (France)',
|
||||
voices: [
|
||||
{ value: 'fr-FR-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'fr-FR-Standard-B', name: 'Standard-B (Male)' },
|
||||
{ value: 'fr-FR-Standard-C', name: 'Standard-C (Female)' },
|
||||
{ value: 'fr-FR-Standard-D', name: 'Standard-D (Male)' },
|
||||
{ value: 'fr-FR-Standard-E', name: 'Standard-E (Female)' },
|
||||
{ value: 'fr-FR-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'fr-FR-Wavenet-B', name: 'Wavenet-B (Male)' },
|
||||
{ value: 'fr-FR-Wavenet-C', name: 'Wavenet-C (Female)' },
|
||||
{ value: 'fr-FR-Wavenet-D', name: 'Wavenet-D (Male)' },
|
||||
{ value: 'fr-FR-Wavenet-E', name: 'Wavenet-E (Female)' },
|
||||
{ value: 'fr-FR-Neural2-A', name: 'Neural2-A (Female)' },
|
||||
{ value: 'fr-FR-Neural2-B', name: 'Neural2-B (Male)' },
|
||||
{ value: 'fr-FR-Neural2-C', name: 'Neural2-C (Female)' },
|
||||
{ value: 'fr-FR-Neural2-D', name: 'Neural2-D (Male)' },
|
||||
{ value: 'fr-FR-Neural2-E', name: 'Neural2-E (Female)' },
|
||||
{ value: 'fr-FR-Polyglot-1', name: 'Polyglot-1 (Male)' },
|
||||
|
||||
{ value: 'fr-FR-Studio-A', name: 'Studio-A (Female)' },
|
||||
{ value: 'fr-FR-Studio-D', name: 'Studio-D (Male)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'de-DE',
|
||||
name: 'German (Germany)',
|
||||
voices: [
|
||||
{ value: 'de-DE-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'de-DE-Standard-B', name: 'Standard-B (Male)' },
|
||||
{ value: 'de-DE-Standard-E', name: 'Standard-E (Male)' },
|
||||
{ value: 'de-DE-Standard-F', name: 'Standard-F (Female)' },
|
||||
{ value: 'de-DE-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'de-DE-Wavenet-B', name: 'Wavenet-B (Male)' },
|
||||
{ value: 'de-DE-Wavenet-C', name: 'Wavenet-C (Female)' },
|
||||
{ value: 'de-DE-Wavenet-D', name: 'Wavenet-D (Male)' },
|
||||
{ value: 'de-DE-Wavenet-E', name: 'Wavenet-E (Male)' },
|
||||
{ value: 'de-DE-Wavenet-F', name: 'Wavenet-F (Female)' },
|
||||
{ value: 'de-DE-Neural2-B', name: 'Neural2-B (Male)' },
|
||||
{ value: 'de-DE-Neural2-C', name: 'Neural2-C (Female)' },
|
||||
{ value: 'de-DE-Neural2-D', name: 'Neural2-D (Male)' },
|
||||
{ value: 'de-DE-Neural2-F', name: 'Neural2-F (Female)' },
|
||||
{ value: 'de-DE-Polyglot-1', name: 'Polyglot-1 (Male)' },
|
||||
|
||||
{ value: 'de-DE-Neural2-A', name: 'Neural2-A (Female)' },
|
||||
{ value: 'de-DE-Standard-C', name: 'Standard-C (Female)' },
|
||||
{ value: 'de-DE-Standard-D', name: 'Standard-D (Male)' },
|
||||
{ value: 'de-DE-Studio-B', name: 'Studio-B (Male)' },
|
||||
{ value: 'de-DE-Studio-C', name: 'Studio-C (Female)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'el-GR',
|
||||
name: 'Greek (Greece)',
|
||||
voices: [
|
||||
{ value: 'el-GR-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'el-GR-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
value: 'gl-ES',
|
||||
name: 'Galician (Spain)',
|
||||
voices: [{ value: 'gl-ES-Standard-A', name: 'Standard-A (Female)' }],
|
||||
},
|
||||
{
|
||||
value: 'gu-IN',
|
||||
name: 'Gujarati (India)',
|
||||
voices: [
|
||||
{ value: 'gu-IN-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'gu-IN-Standard-B', name: 'Standard-B (Male)' },
|
||||
{ value: 'gu-IN-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'gu-IN-Wavenet-B', name: 'Wavenet-B (Male)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'he-IL',
|
||||
name: 'Hebrew (Israel)',
|
||||
voices: [
|
||||
{ value: 'he-IL-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'he-IL-Standard-B', name: 'Standard-B (Male)' },
|
||||
{ value: 'he-IL-Standard-C', name: 'Standard-C (Female)' },
|
||||
{ value: 'he-IL-Standard-D', name: 'Standard-D (Male)' },
|
||||
{ value: 'he-IL-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'he-IL-Wavenet-B', name: 'Wavenet-B (Male)' },
|
||||
{ value: 'he-IL-Wavenet-C', name: 'Wavenet-C (Female)' },
|
||||
{ value: 'he-IL-Wavenet-D', name: 'Wavenet-D (Male)' },
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
value: 'hi-IN',
|
||||
name: 'Hindi (India)',
|
||||
voices: [
|
||||
{ value: 'hi-IN-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'hi-IN-Standard-B', name: 'Standard-B (Male)' },
|
||||
{ value: 'hi-IN-Standard-C', name: 'Standard-C (Male)' },
|
||||
{ value: 'hi-IN-Standard-D', name: 'Standard-D (Female)' },
|
||||
{ value: 'hi-IN-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'hi-IN-Wavenet-B', name: 'Wavenet-B (Male)' },
|
||||
{ value: 'hi-IN-Wavenet-C', name: 'Wavenet-C (Male)' },
|
||||
{ value: 'hi-IN-Wavenet-D', name: 'Wavenet-D (Female)' },
|
||||
{ value: 'hi-IN-Neural2-A', name: 'Neural2-A (Female)' },
|
||||
{ value: 'hi-IN-Neural2-B', name: 'Neural2-B (Male)' },
|
||||
{ value: 'hi-IN-Neural2-C', name: 'Neural2-C (Male)' },
|
||||
{ value: 'hi-IN-Neural2-D', name: 'Neural2-D (Female)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'hu-HU',
|
||||
name: 'Hungarian (Hungary)',
|
||||
voices: [
|
||||
{ value: 'hu-HU-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'hu-HU-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'is-IS',
|
||||
name: 'Icelandic (Iceland)',
|
||||
voices: [{ value: 'is-IS-Standard-A', name: 'Standard-A (Female)' }],
|
||||
},
|
||||
{
|
||||
value: 'id-ID',
|
||||
name: 'Indonesian (Indonesia)',
|
||||
voices: [
|
||||
{ value: 'id-ID-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'id-ID-Standard-B', name: 'Standard-B (Male)' },
|
||||
{ value: 'id-ID-Standard-C', name: 'Standard-C (Male)' },
|
||||
{ value: 'id-ID-Standard-D', name: 'Standard-D (Female)' },
|
||||
{ value: 'id-ID-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'id-ID-Wavenet-B', name: 'Wavenet-B (Male)' },
|
||||
{ value: 'id-ID-Wavenet-C', name: 'Wavenet-C (Male)' },
|
||||
{ value: 'id-ID-Wavenet-D', name: 'Wavenet-D (Female)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'it-IT',
|
||||
name: 'Italian (Italy)',
|
||||
voices: [
|
||||
{ value: 'it-IT-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'it-IT-Standard-B', name: 'Standard-B (Female)' },
|
||||
{ value: 'it-IT-Standard-C', name: 'Standard-C (Male)' },
|
||||
{ value: 'it-IT-Standard-D', name: 'Standard-D (Male)' },
|
||||
{ value: 'it-IT-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'it-IT-Wavenet-B', name: 'Wavenet-B (Female)' },
|
||||
{ value: 'it-IT-Wavenet-C', name: 'Wavenet-C (Male)' },
|
||||
{ value: 'it-IT-Wavenet-D', name: 'Wavenet-D (Male)' },
|
||||
{ value: 'it-IT-Neural2-A', name: 'Neural2-A (Female)' },
|
||||
{ value: 'it-IT-Neural2-C', name: 'Neural2-C (Male)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'ja-JP',
|
||||
name: 'Japanese (Japan)',
|
||||
voices: [
|
||||
{ value: 'ja-JP-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'ja-JP-Standard-B', name: 'Standard-B (Female)' },
|
||||
{ value: 'ja-JP-Standard-C', name: 'Standard-C (Male)' },
|
||||
{ value: 'ja-JP-Standard-D', name: 'Standard-D (Male)' },
|
||||
{ value: 'ja-JP-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'ja-JP-Wavenet-B', name: 'Wavenet-B (Female)' },
|
||||
{ value: 'ja-JP-Wavenet-C', name: 'Wavenet-C (Male)' },
|
||||
{ value: 'ja-JP-Wavenet-D', name: 'Wavenet-D (Male)' },
|
||||
{ value: 'ja-JP-Neural2-B', name: 'Neural2-B (Female)' },
|
||||
{ value: 'ja-JP-Neural2-C', name: 'Neural2-C (Male)' },
|
||||
{ value: 'ja-JP-Neural2-D', name: 'Neural2-D (Male)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'kn-IN',
|
||||
name: 'Kannada (India)',
|
||||
voices: [
|
||||
{ value: 'kn-IN-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'kn-IN-Standard-B', name: 'Standard-B (Male)' },
|
||||
{ value: 'kn-IN-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'kn-IN-Wavenet-B', name: 'Wavenet-B (Male)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'ko-KR',
|
||||
name: 'Korean (South Korea)',
|
||||
voices: [
|
||||
{ value: 'ko-KR-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'ko-KR-Standard-B', name: 'Standard-B (Female)' },
|
||||
{ value: 'ko-KR-Standard-C', name: 'Standard-C (Male)' },
|
||||
{ value: 'ko-KR-Standard-D', name: 'Standard-D (Male)' },
|
||||
{ value: 'ko-KR-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'ko-KR-Wavenet-B', name: 'Wavenet-B (Female)' },
|
||||
{ value: 'ko-KR-Wavenet-C', name: 'Wavenet-C (Male)' },
|
||||
{ value: 'ko-KR-Wavenet-D', name: 'Wavenet-D (Male)' },
|
||||
{ value: 'ko-KR-Neural2-A', name: 'Neural2-A (Female)' },
|
||||
{ value: 'ko-KR-Neural2-B', name: 'Neural2-B (Female)' },
|
||||
{ value: 'ko-KR-Neural2-C', name: 'Neural2-C (Male)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'lv-LV',
|
||||
name: 'Latvian (Latvia)',
|
||||
voices: [{ value: 'lv-LV-Standard-A', name: 'Standard-A (Male)' }],
|
||||
},
|
||||
{
|
||||
value: 'lt-LT',
|
||||
name: 'Lithuanian (Lithuania)',
|
||||
voices: [{ value: 'lt-LT-Standard-A', name: 'Standard-A (Male)' }],
|
||||
},
|
||||
{
|
||||
value: 'cmn-CN',
|
||||
name: 'Mandarin Chinese',
|
||||
voices: [
|
||||
{ value: 'cmn-CN-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'cmn-CN-Standard-B', name: 'Standard-B (Male)' },
|
||||
{ value: 'cmn-CN-Standard-C', name: 'Standard-C (Male)' },
|
||||
{ value: 'cmn-CN-Standard-D', name: 'Standard-D (Female)' },
|
||||
{ value: 'cmn-CN-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'cmn-CN-Wavenet-B', name: 'Wavenet-B (Male)' },
|
||||
{ value: 'cmn-CN-Wavenet-C', name: 'Wavenet-C (Male)' },
|
||||
{ value: 'cmn-CN-Wavenet-D', name: 'Wavenet-D (Female)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'cmn-TW',
|
||||
name: 'Mandarin Chinese (Traditional)',
|
||||
voices: [
|
||||
{ value: 'cmn-TW-Standard-A-Alpha', name: 'Standard-A-Alpha (Female)' },
|
||||
{ value: 'cmn-TW-Standard-B-Alpha', name: 'Standard-B-Alpha (Male)' },
|
||||
{ value: 'cmn-TW-Standard-C-Alpha', name: 'Standard-C-Alpha (Male)' },
|
||||
{ value: 'cmn-TW-Wavenet-A-Alpha', name: 'Wavenet-A-Alpha (Female)' },
|
||||
{ value: 'cmn-TW-Wavenet-B-Alpha', name: 'Wavenet-B-Alpha (Male)' },
|
||||
{ value: 'cmn-TW-Wavenet-C-Alpha', name: 'Wavenet-C-Alpha (Male)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'ms-MY',
|
||||
name: 'Malay (Malaysia)',
|
||||
voices: [
|
||||
{ value: 'ms-MY-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'ms-MY-Standard-B', name: 'Standard-B (Male)' },
|
||||
{ value: 'ms-MY-Standard-C', name: 'Standard-C (Female)' },
|
||||
{ value: 'ms-MY-Standard-D', name: 'Standard-D (Male)' },
|
||||
{ value: 'ms-MY-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'ms-MY-Wavenet-B', name: 'Wavenet-B (Male)' },
|
||||
{ value: 'ms-MY-Wavenet-C', name: 'Wavenet-C (Female)' },
|
||||
{ value: 'ms-MY-Wavenet-D', name: 'Wavenet-D (Male)' },
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
value: 'ml-IN',
|
||||
name: 'Malayalam (India)',
|
||||
voices: [
|
||||
{ value: 'ml-IN-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'ml-IN-Standard-B', name: 'Standard-B (Male)' },
|
||||
{ value: 'ml-IN-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'ml-IN-Wavenet-B', name: 'Wavenet-B (Male)' },
|
||||
{ value: 'ml-IN-Wavenet-C', name: 'Wavenet-C (Female)' },
|
||||
{ value: 'ml-IN-Wavenet-D', name: 'Wavenet-D (Male)' },
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
value: 'mr-IN',
|
||||
name: 'Marathi (India)',
|
||||
voices: [
|
||||
{ value: 'mr-IN-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'mr-IN-Standard-B', name: 'Standard-B (Male)' },
|
||||
{ value: 'mr-IN-Standard-C', name: 'Standard-C (Female)' },
|
||||
{ value: 'mr-IN-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'mr-IN-Wavenet-B', name: 'Wavenet-B (Male)' },
|
||||
{ value: 'mr-IN-Wavenet-C', name: 'Wavenet-C (Female)' },
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
value: 'nb-NO',
|
||||
name: 'Norwegian (Norway)',
|
||||
voices: [
|
||||
{ value: 'nb-NO-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'nb-NO-Standard-B', name: 'Standard-B (Male)' },
|
||||
{ value: 'nb-NO-Standard-C', name: 'Standard-C (Female)' },
|
||||
{ value: 'nb-NO-Standard-D', name: 'Standard-D (Male)' },
|
||||
{ value: 'nb-no-Standard-E', name: 'Standard-E (Female)' },
|
||||
{ value: 'nb-NO-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'nb-NO-Wavenet-B', name: 'Wavenet-B (Male)' },
|
||||
{ value: 'nb-NO-Wavenet-C', name: 'Wavenet-C (Female)' },
|
||||
{ value: 'nb-NO-Wavenet-D', name: 'Wavenet-D (Male)' },
|
||||
{ value: 'nb-no-Wavenet-E', name: 'Wavenet-E (Female)' },
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
value: 'nl-BE',
|
||||
name: 'Dutch (Belgium)',
|
||||
voices: [
|
||||
{ value: 'nl-BE-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'nl-BE-Standard-B', name: 'Standard-B (Male)' },
|
||||
{ value: 'nl-BE-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'nl-BE-Wavenet-B', name: 'Wavenet-B (Male)' },
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
value: 'pl-PL',
|
||||
name: 'Polish (Poland)',
|
||||
voices: [
|
||||
{ value: 'pl-PL-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'pl-PL-Standard-B', name: 'Standard-B (Male)' },
|
||||
{ value: 'pl-PL-Standard-C', name: 'Standard-C (Male)' },
|
||||
{ value: 'pl-PL-Standard-D', name: 'Standard-D (Female)' },
|
||||
{ value: 'pl-PL-Standard-E', name: 'Standard-E (Female)' },
|
||||
{ value: 'pl-PL-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'pl-PL-Wavenet-B', name: 'Wavenet-B (Male)' },
|
||||
{ value: 'pl-PL-Wavenet-C', name: 'Wavenet-C (Male)' },
|
||||
{ value: 'pl-PL-Wavenet-D', name: 'Wavenet-D (Female)' },
|
||||
{ value: 'pl-PL-Wavenet-E', name: 'Wavenet-E (Female)' },
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
value: 'pa-IN',
|
||||
name: 'Punjabi (India)',
|
||||
voices: [
|
||||
{ value: 'pa-IN-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'pa-IN-Standard-B', name: 'Standard-B (Male)' },
|
||||
{ value: 'pa-IN-Standard-C', name: 'Standard-C (Female)' },
|
||||
{ value: 'pa-IN-Standard-D', name: 'Standard-D (Male)' },
|
||||
{ value: 'pa-IN-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'pa-IN-Wavenet-B', name: 'Wavenet-B (Male)' },
|
||||
{ value: 'pa-IN-Wavenet-C', name: 'Wavenet-C (Female)' },
|
||||
{ value: 'pa-IN-Wavenet-D', name: 'Wavenet-D (Male)' },
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
value: 'pt-BR',
|
||||
name: 'Portuguese (Brazil)',
|
||||
voices: [
|
||||
{ value: 'pt-BR-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'pt-BR-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'pt-BR-Neural2-A', name: 'Neural2-A (Female)' },
|
||||
{ value: 'pt-BR-Neural2-B', name: 'Neural2-B (Male)' },
|
||||
{ value: 'pt-BR-Neural2-C', name: 'Neural2-C (Female)' },
|
||||
|
||||
{ value: 'pt-BR-Standard-B', name: 'Standard-B (Male)' },
|
||||
{ value: 'pt-BR-Standard-C', name: 'Standard-C (Female)' },
|
||||
{ value: 'pt-BR-Wavenet-B', name: 'Wavenet-B (Male)' },
|
||||
{ value: 'pt-BR-Wavenet-C', name: 'Wavenet-C (Female)' },
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
value: 'pt-PT',
|
||||
name: 'Portuguese (Portugal)',
|
||||
voices: [
|
||||
{ value: 'pt-PT-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'pt-PT-Standard-B', name: 'Standard-B (Male)' },
|
||||
{ value: 'pt-PT-Standard-C', name: 'Standard-C (Male)' },
|
||||
{ value: 'pt-PT-Standard-D', name: 'Standard-D (Female)' },
|
||||
{ value: 'pt-PT-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'pt-PT-Wavenet-B', name: 'Wavenet-B (Male)' },
|
||||
{ value: 'pt-PT-Wavenet-C', name: 'Wavenet-C (Male)' },
|
||||
{ value: 'pt-PT-Wavenet-D', name: 'Wavenet-D (Female)' },
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
value: 'ro-RO',
|
||||
name: 'Romanian (Romania)',
|
||||
voices: [
|
||||
{ value: 'ro-RO-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'ro-RO-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
value: 'ru-RU',
|
||||
name: 'Russian (Russia)',
|
||||
voices: [
|
||||
{ value: 'ru-RU-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'ru-RU-Standard-B', name: 'Standard-B (Male)' },
|
||||
{ value: 'ru-RU-Standard-C', name: 'Standard-C (Female)' },
|
||||
{ value: 'ru-RU-Standard-D', name: 'Standard-D (Male)' },
|
||||
{ value: 'ru-RU-Standard-E', name: 'Standard-E (Female)' },
|
||||
{ value: 'ru-RU-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'ru-RU-Wavenet-B', name: 'Wavenet-B (Male)' },
|
||||
{ value: 'ru-RU-Wavenet-C', name: 'Wavenet-C (Female)' },
|
||||
{ value: 'ru-RU-Wavenet-D', name: 'Wavenet-D (Male)' },
|
||||
{ value: 'ru-RU-Wavenet-E', name: 'Wavenet-E (Female)' },
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
value: 'sk-SK',
|
||||
name: 'Slovak (Slovakia)',
|
||||
voices: [
|
||||
{ value: 'sk-SK-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'sk-SK-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
value: 'sr-RS',
|
||||
name: 'Serbian (Cyrillic)',
|
||||
voices: [{ value: 'sr-RS-Standard-A', name: 'Standard-A (Female)' }],
|
||||
},
|
||||
|
||||
{
|
||||
value: 'es-ES',
|
||||
name: 'Spanish (Spain)',
|
||||
voices: [
|
||||
{ value: 'es-ES-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'es-ES-Neural2-A', name: 'Neural2-A (Female)' },
|
||||
{ value: 'es-ES-Neural2-B', name: 'Neural2-B (Male)' },
|
||||
{ value: 'es-ES-Neural2-C', name: 'Neural2-C (Female)' },
|
||||
{ value: 'es-ES-Neural2-D', name: 'Neural2-D (Female)' },
|
||||
{ value: 'es-ES-Neural2-E', name: 'Neural2-E (Female)' },
|
||||
{ value: 'es-ES-Neural2-F', name: 'Neural2-F (Male)' },
|
||||
{ value: 'es-ES-Polyglot-1', name: 'Polyglot-1 (Male)' },
|
||||
|
||||
{ value: 'es-ES-Standard-B', name: 'Standard-B (Male)' },
|
||||
{ value: 'es-ES-Standard-C', name: 'Standard-C (Female)' },
|
||||
{ value: 'es-ES-Standard-D', name: 'Standard-D (Female)' },
|
||||
{ value: 'es-ES-Wavenet-B', name: 'Wavenet-B (Male)' },
|
||||
{ value: 'es-ES-Wavenet-C', name: 'Wavenet-C (Female)' },
|
||||
{ value: 'es-ES-Wavenet-D', name: 'Wavenet-D (Female)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'es-US',
|
||||
name: 'Spanish (US)',
|
||||
voices: [
|
||||
{ value: 'es-US-Neural2-A', name: 'Neural2-A (Female)' },
|
||||
{ value: 'es-US-Neural2-B', name: 'Neural2-B (Male)' },
|
||||
{ value: 'es-US-Neural2-C', name: 'Neural2-C (Male)' },
|
||||
{ value: 'es-US-Studio-B', name: 'Studio-B (Male)' },
|
||||
{ value: 'es-US-Polyglot-1', name: 'Polyglot-1 (Male)' },
|
||||
{ value: 'es-US-News-D', name: 'News-D (Male)' },
|
||||
{ value: 'es-US-News-E', name: 'News-E (Male)' },
|
||||
{ value: 'es-US-News-F', name: 'News-F (Female)' },
|
||||
{ value: 'es-US-News-G', name: 'News-G (Female)' },
|
||||
|
||||
{ value: 'es-US-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'es-US-Standard-B', name: 'Standard-B (Male)' },
|
||||
{ value: 'es-US-Standard-C', name: 'Standard-C (Male)' },
|
||||
{ value: 'es-US-Studio-B', name: 'Studio-B (Male)' },
|
||||
{ value: 'es-US-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'es-US-Wavenet-B', name: 'Wavenet-B (Male)' },
|
||||
{ value: 'es-US-Wavenet-C', name: 'Wavenet-C (Male)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'sv-SE',
|
||||
name: 'Swedish (Sweden)',
|
||||
voices: [
|
||||
{ value: 'sv-SE-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'sv-SE-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
|
||||
{ value: 'sv-SE-Standard-B', name: 'Standard-B (Female)' },
|
||||
{ value: 'sv-SE-Standard-C', name: 'Standard-C (Female)' },
|
||||
{ value: 'sv-SE-Standard-D', name: 'Standard-D (Male)' },
|
||||
{ value: 'sv-SE-Standard-E', name: 'Standard-E (Male)' },
|
||||
{ value: 'sv-SE-Wavenet-B', name: 'Wavenet-B (Female)' },
|
||||
{ value: 'sv-SE-Wavenet-C', name: 'Wavenet-C (Male)' },
|
||||
{ value: 'sv-SE-Wavenet-D', name: 'Wavenet-D (Female)' },
|
||||
{ value: 'sv-SE-Wavenet-E', name: 'Wavenet-E (Male)' },
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
value: 'ta-IN',
|
||||
name: 'Tamil (India)',
|
||||
voices: [
|
||||
{ value: 'ta-IN-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'ta-IN-Standard-B', name: 'Standard-B (Male)' },
|
||||
{ value: 'ta-IN-Standard-C', name: 'Standard-C (Female)' },
|
||||
{ value: 'ta-IN-Standard-D', name: 'Standard-D (Male)' },
|
||||
{ value: 'ta-IN-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'ta-IN-Wavenet-B', name: 'Wavenet-B (Male)' },
|
||||
{ value: 'ta-IN-Wavenet-C', name: 'Wavenet-C (Female)' },
|
||||
{ value: 'ta-IN-Wavenet-D', name: 'Wavenet-D (Male)' },
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
value: 'te-IN',
|
||||
name: 'Telugu (India)',
|
||||
voices: [
|
||||
{ value: 'te-IN-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'te-IN-Standard-B', name: 'Standard-B (Male)' },
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
value: 'tr-TR',
|
||||
name: 'Turkish (Turkey)',
|
||||
voices: [
|
||||
{ value: 'tr-TR-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'tr-TR-Standard-B', name: 'Standard-B (Male)' },
|
||||
{ value: 'tr-TR-Standard-C', name: 'Standard-C (Female)' },
|
||||
{ value: 'tr-TR-Standard-D', name: 'Standard-D (Female)' },
|
||||
{ value: 'tr-TR-Standard-E', name: 'Standard-E (Male)' },
|
||||
{ value: 'tr-TR-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'tr-TR-Wavenet-B', name: 'Wavenet-B (Male)' },
|
||||
{ value: 'tr-TR-Wavenet-C', name: 'Wavenet-C (Female)' },
|
||||
{ value: 'tr-TR-Wavenet-D', name: 'Wavenet-D (Female)' },
|
||||
{ value: 'tr-TR-Wavenet-E', name: 'Wavenet-E (Male)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'uk-UA',
|
||||
name: 'Ukrainian (Ukraine)',
|
||||
voices: [
|
||||
{ value: 'uk-UA-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'uk-UA-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'th-TH',
|
||||
name: 'Thai (Thailand)',
|
||||
voices: [
|
||||
{ value: 'th-TH-Neural2-C', name: 'Neural2-C (Female)' },
|
||||
|
||||
{ value: 'th-TH-Standard-A', name: 'Standard-A (Female)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'vi-VN',
|
||||
name: 'Vietnamese (Vietnam)',
|
||||
voices: [
|
||||
{ value: 'vi-VN-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'vi-VN-Standard-B', name: 'Standard-B (Male)' },
|
||||
{ value: 'vi-VN-Standard-C', name: 'Standard-C (Female)' },
|
||||
{ value: 'vi-VN-Standard-D', name: 'Standard-D (Male)' },
|
||||
{ value: 'vi-VN-Wavenet-A', name: 'Wavenet-A (Female)' },
|
||||
{ value: 'vi-VN-Wavenet-B', name: 'Wavenet-B (Male)' },
|
||||
{ value: 'vi-VN-Wavenet-C', name: 'Wavenet-C (Female)' },
|
||||
{ value: 'vi-VN-Wavenet-D', name: 'Wavenet-D (Male)' },
|
||||
{ value: 'vi-VN-Neural2-A', name: 'Neural2-A (Female)' },
|
||||
{ value: 'vi-VN-Neural2-D', name: 'Neural2-D (Male)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'yue-HK',
|
||||
name: 'Chinese (Hong Kong)',
|
||||
voices: [
|
||||
{ value: 'yue-HK-Standard-A', name: 'Standard-A (Female)' },
|
||||
{ value: 'yue-HK-Standard-B', name: 'Standard-B (Male)' },
|
||||
{ value: 'yue-HK-Standard-C', name: 'Standard-C (Female)' },
|
||||
{ value: 'yue-HK-Standard-D', name: 'Standard-D (Male)' },
|
||||
],
|
||||
},
|
||||
];
|
||||
167
lib/utils/speech-data/tts-ibm.js
Normal file
167
lib/utils/speech-data/tts-ibm.js
Normal file
@@ -0,0 +1,167 @@
|
||||
module.exports = [
|
||||
{
|
||||
value: 'de-DE',
|
||||
name: 'German (Germany)',
|
||||
voices: [
|
||||
{ value: 'de-DE_DieterVoice', name: 'Dieter (Male): Standard German' },
|
||||
{
|
||||
value: 'de-DE_DieterV2Voice',
|
||||
name: 'Dieter 2 (Male): Standard German',
|
||||
},
|
||||
{
|
||||
value: 'de-DE_DieterV3Voice',
|
||||
name: 'Dieter 3 (Male): Standard German',
|
||||
},
|
||||
{ value: 'de-DE_ErikaV3Voice', name: 'Erika (Female): Standard German' },
|
||||
{ value: 'de-DE_BirgitVoice', name: 'Brigit (Female): Standard German' },
|
||||
{
|
||||
value: 'de-DE_BirgitV2Voice',
|
||||
name: 'Brigit 2 (Female): Standard German',
|
||||
},
|
||||
{
|
||||
value: 'de-DE_BirgitV3Voice',
|
||||
name: 'Brigit 3 (Female): Standard German',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'en-US',
|
||||
name: 'English (US)',
|
||||
voices: [
|
||||
{
|
||||
value: 'en-US_MichaelExpressive',
|
||||
name: 'Michael (Male): American English - Expressive',
|
||||
},
|
||||
{ value: 'en-US_MichaelVoice', name: 'Michael (Male): American English' },
|
||||
{
|
||||
value: 'en-US_MichaelV2Voice',
|
||||
name: 'Michael 2 (Male): American English',
|
||||
},
|
||||
{
|
||||
value: 'en-US_MichaelV3Voice',
|
||||
name: 'Michael 3 (Male): American English',
|
||||
},
|
||||
{ value: 'en-US_HenryV3Voice', name: 'Henry (Male): American English' },
|
||||
{ value: 'en-US_EmilyV3Voice', name: 'Emily (Female): American English' },
|
||||
{
|
||||
value: 'en-US_OliviaV3Voice',
|
||||
name: 'Olivia (Female): American English',
|
||||
},
|
||||
{
|
||||
value: 'en-US_AllisonExpressive',
|
||||
name: 'Allison (Female): American English - Expressive',
|
||||
},
|
||||
{
|
||||
value: 'en-US_AllisonVoice',
|
||||
name: 'Allison (Female): American English',
|
||||
},
|
||||
{
|
||||
value: 'en-US_AllisonV2Voice',
|
||||
name: 'Allison 2 (Female): American English',
|
||||
},
|
||||
{
|
||||
value: 'en-US_AllisonV3Voice',
|
||||
name: 'Allison 3 (Female): American English',
|
||||
},
|
||||
{
|
||||
value: 'en-US_LisaExpressive',
|
||||
name: 'Lisa (Female): American English - Expressive',
|
||||
},
|
||||
{ value: 'en-US_LisaVoice', name: 'Lisa (Female): American English' },
|
||||
{ value: 'en-US_LisaV2Voice', name: 'Lisa 2 (Female): American English' },
|
||||
{ value: 'en-US_LisaV3Voice', name: 'Lisa 3 (Female): American English' },
|
||||
{ value: 'en-US_KevinV3Voice', name: 'Kevin (Male): American English' },
|
||||
{
|
||||
value: 'en-US_EmmaExpressive',
|
||||
name: 'Emma (Female): American English - Expressive',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'en-GB',
|
||||
name: 'English (GB)',
|
||||
voices: [
|
||||
{ value: 'en-GB_JamesV3Voice', name: 'James (Male)' },
|
||||
{ value: 'en-GB_KateVoice', name: 'Kate (Female)' },
|
||||
{ value: 'en-GB_KateV3Voice', name: 'Kate 2 (Female)' },
|
||||
{ value: 'en-GB_CharlotteV3Voice', name: 'Charlotte (Female)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'es-US',
|
||||
name: 'Spanish (North America)',
|
||||
voices: [
|
||||
{
|
||||
value: 'es-US_SofiaVoice',
|
||||
name: 'Sofia (Female): North American Spanish',
|
||||
},
|
||||
{
|
||||
value: 'es-US_SofiaV3Voice',
|
||||
name: 'Sofia 2 (Female): North American Spanish',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'es-LA',
|
||||
name: 'Spanish (Latin America)',
|
||||
voices: [
|
||||
{
|
||||
value: 'es-LA_SofiaVoice',
|
||||
name: 'Sofia (Female): Latin American Spanish',
|
||||
},
|
||||
{
|
||||
value: 'es-LA_SofiaV3Voice',
|
||||
name: 'Sofia 2 (Female): Latin American Spanish',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'es-ES',
|
||||
name: 'Spanish (Castilian)',
|
||||
voices: [
|
||||
{ value: 'es-ES_LauraVoice', name: 'Laura (Female)' },
|
||||
{ value: 'es-ES_LauraV3Voice', name: 'Laura 2 (Female)' },
|
||||
{ value: 'es-ES_EnriqueVoice', name: 'Enrique (Male)' },
|
||||
{ value: 'es-ES_EnriqueV3Voice', name: 'Enrique 2 (Male)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'fr-FR',
|
||||
name: 'French (FR)',
|
||||
voices: [
|
||||
{ value: 'fr-FR_NicolasV3Voice', name: 'Nicolas (Male)' },
|
||||
{ value: 'fr-FR_ReneeVoice', name: 'Renee (Female)' },
|
||||
{ value: 'fr-FR_ReneeV3Voice', name: 'Renee 2 (Female)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'fr-CA',
|
||||
name: 'French (CA)',
|
||||
voices: [{ value: 'fr-CA_LouiseV3Voice', name: 'Louise (Female)' }],
|
||||
},
|
||||
{
|
||||
value: 'it-IT',
|
||||
name: 'Italian',
|
||||
voices: [
|
||||
{ value: 'it-IT_FrancescaVoice', name: 'Francesca (Female)' },
|
||||
{ value: 'it-IT_FrancescaV2Voice', name: 'Francesca 2 (Female)' },
|
||||
{ value: 'it-IT_FrancescaV3Voice', name: 'Francesca 3 (Female)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'pt-BR',
|
||||
name: 'Portuguese (Brazil)',
|
||||
voices: [
|
||||
{ value: 'pt-BR_IsabelaVoice', name: 'Isabela (Female)' },
|
||||
{ value: 'pt-BR_IsabelaV3Voice', name: 'Isabela 2 (Female)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'ja-JP',
|
||||
name: 'Japanese',
|
||||
voices: [
|
||||
{ value: 'ja-JP_EmiVoice', name: 'Emi (Female)' },
|
||||
{ value: 'ja-JP_EmiV3Voice', name: 'Emi 2 (Female)' },
|
||||
],
|
||||
},
|
||||
];
|
||||
7476
lib/utils/speech-data/tts-microsoft-raw.js
Normal file
7476
lib/utils/speech-data/tts-microsoft-raw.js
Normal file
File diff suppressed because it is too large
Load Diff
28
lib/utils/speech-data/tts-microsoft.js
Normal file
28
lib/utils/speech-data/tts-microsoft.js
Normal file
@@ -0,0 +1,28 @@
|
||||
const TtsAwsLanguagesVoiceRaw = require('./tts-microsoft-raw');
|
||||
|
||||
const languagesVoices = [];
|
||||
|
||||
TtsAwsLanguagesVoiceRaw.forEach((data) => {
|
||||
const lang = languagesVoices.find((l) => {
|
||||
return l.value === data.Locale;
|
||||
});
|
||||
|
||||
if (!lang) {
|
||||
languagesVoices.push({
|
||||
value: data.Locale,
|
||||
name: data.LocaleName,
|
||||
voices: TtsAwsLanguagesVoiceRaw
|
||||
.filter((d) => {
|
||||
return d.Locale === data.Locale;
|
||||
})
|
||||
.map((d) => {
|
||||
return {
|
||||
value: d.ShortName,
|
||||
name: `${d.DisplayName} (${d.Gender})`,
|
||||
};
|
||||
}),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = languagesVoices;
|
||||
14
lib/utils/speech-data/tts-model-deepgram.js
Normal file
14
lib/utils/speech-data/tts-model-deepgram.js
Normal file
@@ -0,0 +1,14 @@
|
||||
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' },
|
||||
];
|
||||
8
lib/utils/speech-data/tts-model-elevenlabs.js
Normal file
8
lib/utils/speech-data/tts-model-elevenlabs.js
Normal file
@@ -0,0 +1,8 @@
|
||||
module.exports = [
|
||||
{ name: 'Turbo v2', value: 'eleven_turbo_v2' },
|
||||
{ name: 'Multilingual v2', value: 'eleven_multilingual_v2' },
|
||||
{ name: 'Multilingual v1', value: 'eleven_multilingual_v1' },
|
||||
{ name: 'English v1', value: 'eleven_monolingual_v1' },
|
||||
{ name: 'English v2', value: 'eleven_english_sts_v2' },
|
||||
];
|
||||
|
||||
6
lib/utils/speech-data/tts-model-playht.js
Normal file
6
lib/utils/speech-data/tts-model-playht.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = [
|
||||
{ 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' },
|
||||
];
|
||||
|
||||
5
lib/utils/speech-data/tts-model-whisper.js
Normal file
5
lib/utils/speech-data/tts-model-whisper.js
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = [
|
||||
{ name: 'TTS-1', value: 'tts-1' },
|
||||
{ name: 'TTS-1-HD', value: 'tts-1-hd' },
|
||||
];
|
||||
|
||||
958
lib/utils/speech-data/tts-nuance.js
Normal file
958
lib/utils/speech-data/tts-nuance.js
Normal file
@@ -0,0 +1,958 @@
|
||||
module.exports = [
|
||||
{
|
||||
value: 'ar-WW',
|
||||
name: 'Arabic (Worldwide)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Laila - standard',
|
||||
name: 'Laila (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Tarik - standard',
|
||||
name: 'Tarik (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Miriam - standard',
|
||||
name: 'Miriam (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'eu-ES',
|
||||
name: 'Basque (Spain)',
|
||||
voices: [{ value: 'Miren', name: 'Miren (standard)', model: 'standard' }],
|
||||
},
|
||||
{
|
||||
value: 'bn-IN',
|
||||
name: 'Bengali (India)',
|
||||
voices: [
|
||||
{ value: 'Paya - standard', name: 'Paya (standard)', model: 'standard' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'bho-IN',
|
||||
name: 'Bhojpuri (India)',
|
||||
voices: [
|
||||
{ value: 'Jaya - standard', name: 'Jaya (standard)', model: 'standard' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'bg-BG',
|
||||
name: 'Bulgarian (Bulgaria)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Daria - standard',
|
||||
name: 'Daria (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'yue-HK',
|
||||
name: 'Cantonese (Hong Kong)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Sinji-Ml - standard',
|
||||
name: 'Sinji-Ml (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'ca-ES',
|
||||
name: 'Catalan (Spain)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Jordi - standard',
|
||||
name: 'Jordi (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Montserrat - standard',
|
||||
name: 'Montserrat (standard)',
|
||||
model: 'enhanced',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'yue-HK',
|
||||
name: 'Croatian (Croatia)',
|
||||
voices: [
|
||||
{ value: 'Lana - standard', name: 'Lana (standard)', model: 'standard' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'cs-CZ',
|
||||
name: 'Czech (Czech Republic)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Iveta - standard',
|
||||
name: 'Iveta (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Zuzana - standard',
|
||||
name: 'Zuzana (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Zuzana-ml - enhanced',
|
||||
name: 'Zuzana (enhanced)',
|
||||
model: 'enhanced',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'da-DK',
|
||||
name: 'Danish (Denmark)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Magnus - standard',
|
||||
name: 'Magnus (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{ value: 'Sara - standard', name: 'Sara (standard)', model: 'standard' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'nl-BE',
|
||||
name: 'Dutch (Belgium)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Ellen - standard',
|
||||
name: 'Ellen (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'nl-NL',
|
||||
name: 'Dutch (Belgium)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Claire-Ml - standard',
|
||||
name: 'Claire-Ml (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Xander - standard',
|
||||
name: 'Xander (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'en-AU',
|
||||
name: 'English (Australia)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Karen - standard',
|
||||
name: 'Karen (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{ value: 'Lee - standard', name: 'Lee (standard)', model: 'standard' },
|
||||
{
|
||||
value: 'Matilda - enhanced',
|
||||
name: 'Matilda (enhanced)',
|
||||
model: 'enhanced',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'en-IN',
|
||||
name: 'English (India)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Isha-Ml - enhanced',
|
||||
name: 'Isha-Ml (enhanced)',
|
||||
model: 'enhanced',
|
||||
},
|
||||
{
|
||||
value: 'Rishi - standard',
|
||||
name: 'Rishi (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Rishi-Ml - standard',
|
||||
name: 'Rishi-Ml (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Sangeeta - standard',
|
||||
name: 'Sangeeta (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Veena - standard',
|
||||
name: 'Veena (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'en-IE',
|
||||
name: 'English (Ireland)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Moira - standard',
|
||||
name: 'Moira (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'en-SC',
|
||||
name: 'English (Scotland)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Fiona - standard',
|
||||
name: 'Fiona (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'en-ZA',
|
||||
name: 'English (South Africa)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Tessa - standard',
|
||||
name: 'Tessa (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'en-GB',
|
||||
name: 'English (United Kingdom)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Daniel - standard',
|
||||
name: 'Daniel (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{ value: 'Kate - standard', name: 'Kate (standard)', model: 'standard' },
|
||||
{
|
||||
value: 'Malcolm - standard',
|
||||
name: 'Malcolm (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Oliver - standard',
|
||||
name: 'Oliver (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Serena - enhanced',
|
||||
name: 'Serena (enhanced)',
|
||||
model: 'enhanced',
|
||||
},
|
||||
{
|
||||
value: 'Simon - standard',
|
||||
name: 'Simon (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Stephanie - standard',
|
||||
name: 'Stephanie (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'en-US',
|
||||
name: 'English (United States)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Allison - standard',
|
||||
name: 'Allison (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Ava-Ml - enhanced',
|
||||
name: 'Ava-Ml (enhanced)',
|
||||
model: 'enhanced',
|
||||
},
|
||||
{
|
||||
value: 'Chloe - standard',
|
||||
name: 'Chloe (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{ value: 'Evan - enhanced', name: 'Evan (enhanced)', model: 'enhanced' },
|
||||
{
|
||||
value: 'Nathan - enhanced',
|
||||
name: 'Nathan (enhanced)',
|
||||
model: 'enhanced',
|
||||
},
|
||||
{
|
||||
value: 'Evelyn - standard',
|
||||
name: 'Evelyn (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Nolan - standard',
|
||||
name: 'Nolan (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Samantha - standard',
|
||||
name: 'Samantha (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Susan - standard',
|
||||
name: 'Susan (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{ value: 'Tom - standard', name: 'Tom (standard)', model: 'standard' },
|
||||
{
|
||||
value: 'Zoe-Ml - enhanced',
|
||||
name: 'Zoe-Ml (enhanced)',
|
||||
model: 'enhanced',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'fi-FI',
|
||||
name: 'Finnish (Finland)',
|
||||
voices: [
|
||||
{ value: 'Onni - standard', name: 'Onni (standard)', model: 'standard' },
|
||||
{ value: 'Satu - standard', name: 'Satu (standard)', model: 'standard' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'fr-BE',
|
||||
name: 'French (Belgium)',
|
||||
voices: [
|
||||
{ value: 'Aude - standard', name: 'Aude (standard)', model: 'standard' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'fr-CA',
|
||||
name: 'French (Canada)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Amelie-Ml - enhanced',
|
||||
name: 'Amelie-Ml (enhanced)',
|
||||
model: 'enhanced',
|
||||
},
|
||||
{
|
||||
value: 'Chantal - standard',
|
||||
name: 'Chantal (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Nicolas - standard',
|
||||
name: 'Nicolas (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'fr-FR',
|
||||
name: 'French (France)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Audrey-Ml - enhanced',
|
||||
name: 'Audrey-Ml (enhanced)',
|
||||
model: 'enhanced',
|
||||
},
|
||||
{
|
||||
value: 'Aurelie - standard',
|
||||
name: 'Aurelie (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Thomas - standard',
|
||||
name: 'Thomas (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'gl-ES',
|
||||
name: 'Galician (Spain)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Carmela - standard',
|
||||
name: 'Carmela (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'de-DE',
|
||||
name: 'German (Germany)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Anna-Ml - enhanced',
|
||||
name: 'Anna-Ml (enhanced)',
|
||||
model: 'enhanced',
|
||||
},
|
||||
{
|
||||
value: 'Markus - standard',
|
||||
name: 'Markus (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Petra-Ml - enhanced',
|
||||
name: 'Petra-Ml (enhanced)',
|
||||
model: 'enhanced',
|
||||
},
|
||||
{
|
||||
value: 'Viktor - standard',
|
||||
name: 'Viktor (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Yannick - standard',
|
||||
name: 'Yannick (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'el-GR',
|
||||
name: 'Greek (Greece)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Melina - standard',
|
||||
name: 'Melina (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Nikos - standard',
|
||||
name: 'Nikos (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'he-IL',
|
||||
name: 'Hebrew (Israel)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Carmit - standard',
|
||||
name: 'Carmit (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'hi-IN',
|
||||
name: 'Hindi (India)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Kiyara-Ml - enhanced',
|
||||
name: 'Kiyara-Ml (enhanced)',
|
||||
model: 'enhanced',
|
||||
},
|
||||
{
|
||||
value: 'Lekha - standard',
|
||||
name: 'Lekha (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{ value: 'neel - standard', name: 'Neel (standard)', model: 'standard' },
|
||||
{
|
||||
value: 'Neel-Ml - standard',
|
||||
name: 'Neel-Ml (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'hu-HU',
|
||||
name: 'Hungarian (Hungary)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Mariska - standard',
|
||||
name: 'Mariska (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'id-ID',
|
||||
name: 'Indonesian (Indonesia)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Damayanti - standard',
|
||||
name: 'Damayanti (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'it-IT',
|
||||
name: 'Italian (Italy)',
|
||||
voices: [
|
||||
{ value: 'Emma - enhanced', name: 'Emma (enhanced)', model: 'enhanced' },
|
||||
{
|
||||
value: 'Federica-Ml - standard',
|
||||
name: 'Federica-Ml (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{ value: 'Luca - standard', name: 'Luca (standard)', model: 'standard' },
|
||||
{
|
||||
value: 'Neel-Ml - standard',
|
||||
name: 'Neel-Ml (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Paola - standard',
|
||||
name: 'Paola (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'ja-JP',
|
||||
name: 'Japanese (Japan)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Ayane - standard',
|
||||
name: 'Ayane (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Daisuke - standard',
|
||||
name: 'Daisuke (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Ichiro - standard',
|
||||
name: 'Ichiro (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Koharu - standard',
|
||||
name: 'Koharu (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Kyoko - standard',
|
||||
name: 'Kyoko (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Mizuki - standard',
|
||||
name: 'Mizuki (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Otoya - standard',
|
||||
name: 'Otoya (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Sakura - standard',
|
||||
name: 'Sakura (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Seiji - standard',
|
||||
name: 'Seiji (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'kn-IN',
|
||||
name: 'Kannada (India)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Alpana - standard',
|
||||
name: 'Alpana (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'ko-KR',
|
||||
name: 'Korean (South Korea)',
|
||||
voices: [
|
||||
{ value: 'Jina - enhanced', name: 'Jina (enhanced)', model: 'enhanced' },
|
||||
{ value: 'Sora - standard', name: 'Sora (standard)', model: 'standard' },
|
||||
{ value: 'Yuna - standard', name: 'Yuna (standard)', model: 'standard' },
|
||||
{
|
||||
value: 'Yuna-Ml - enhanced',
|
||||
name: 'Yuna-Ml (enhanced)',
|
||||
model: 'enhanced',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'zlm-MY',
|
||||
name: 'Malay (Malaysia)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Amira - standard',
|
||||
name: 'Amira (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'zh-CN',
|
||||
name: 'Mandarin (China)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Lili-Ml - enhanced',
|
||||
name: 'Lili-Ml (enhanced)',
|
||||
model: 'enhanced',
|
||||
},
|
||||
{
|
||||
value: 'Binbin-Ml - standard',
|
||||
name: 'Binbin-Ml (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Lilian-Ml - standard',
|
||||
name: 'Lilian-Ml (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Lisheng-Ml - standard',
|
||||
name: 'Lisheng-Ml (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Tiantian-Ml - standard',
|
||||
name: 'Tiantian-Ml (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Tingting-Ml - standard',
|
||||
name: 'Tingting-Ml (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'cmn-TW',
|
||||
name: 'Mandarin (Taiwan)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Meijia-Ml - standard',
|
||||
name: 'Meijia-Ml (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'mr-IN',
|
||||
name: 'Marathi (India)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Ananya - standard',
|
||||
name: 'Ananya (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'nb-NO',
|
||||
name: 'Norwegian Bokmål (Norway)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Henrik - standard',
|
||||
name: 'Henrik (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{ value: 'Nora - standard', name: 'Nora (standard)', model: 'standard' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'pl-PL',
|
||||
name: 'Polish (Poland)',
|
||||
voices: [
|
||||
{ value: 'Ewa - enhanced', name: 'Ewa (enhanced)', model: 'enhanced' },
|
||||
{
|
||||
value: 'Krzysztof - standard',
|
||||
name: 'Krzysztof (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Zosia - standard',
|
||||
name: 'Zosia (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'pt-BR',
|
||||
name: 'Portuguese (Brazil)',
|
||||
voices: [
|
||||
{
|
||||
value: 'luciana - enhanced',
|
||||
name: 'Luciana (enhanced)',
|
||||
model: 'enhanced',
|
||||
},
|
||||
{
|
||||
value: 'Fernanda - standard',
|
||||
name: 'Fernanda (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Felipe - standard',
|
||||
name: 'Felipe (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'pt-PT',
|
||||
name: 'Portuguese (Portugal)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Catarina - standard',
|
||||
name: 'Catarina (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Joana - standard',
|
||||
name: 'Joana (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Joaquim - standard',
|
||||
name: 'Joaquim (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'ro-RO',
|
||||
name: 'Romanian (Romania)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Ioana - standard',
|
||||
name: 'Ioana (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'ru-RU',
|
||||
name: 'Russian (Russia)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Katya - standard',
|
||||
name: 'Katya (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Katya-Ml - standard',
|
||||
name: 'Katya-Ml (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Milena - standard',
|
||||
name: 'Milena (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{ value: 'yuri - standard', name: 'Yuri (standard)', model: 'standard' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'sk-SK',
|
||||
name: 'Slovak (Slovakia)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Laura - standard',
|
||||
name: 'Laura (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'es-AR',
|
||||
name: 'Spanish (Argentina)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Diego - standard',
|
||||
name: 'Diego (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Isabela - standard',
|
||||
name: 'Isabela (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'es-CL',
|
||||
name: 'Spanish (Chile)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Francisca - standard',
|
||||
name: 'Francisca (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'es-CO',
|
||||
name: 'Spanish (Colombia)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Carlos - standard',
|
||||
name: 'Carlos (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Soledad - standard',
|
||||
name: 'Soledad (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Ximena - standard',
|
||||
name: 'Ximena (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'es-MX',
|
||||
name: 'Spanish (Mexico)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Angelica - standard',
|
||||
name: 'Angelica (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Javier - standard',
|
||||
name: 'Javier (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{ value: 'Juan - standard', name: 'Juan (standard)', model: 'standard' },
|
||||
{
|
||||
value: 'Paulina-Ml - enhanced',
|
||||
name: 'Paulina-Ml (enhanced)',
|
||||
model: 'enhanced',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'es-ES',
|
||||
name: 'Spanish (Spain)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Jorge - standard',
|
||||
name: 'Jorge (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Marisol-Ml - standard',
|
||||
name: 'Marisol-Ml (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Monica-Ml - standard',
|
||||
name: 'Monica-Ml (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'sv-SE',
|
||||
name: 'Swedish (Sweden)',
|
||||
voices: [
|
||||
{ value: 'Alva - standard', name: 'Alva (standard)', model: 'standard' },
|
||||
{
|
||||
value: 'Klara - standard',
|
||||
name: 'Klara (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Oskar - standard',
|
||||
name: 'Oskar (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'ta-IN',
|
||||
name: 'Tamil (India)',
|
||||
voices: [
|
||||
{ value: 'Vani - standard', name: 'Vani (standard)', model: 'standard' },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'te-IN',
|
||||
name: 'Telugu (India)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Geeta - standard',
|
||||
name: 'Geeta (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'th-TH',
|
||||
name: 'Thai (Thailand)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Kanya - enhanced',
|
||||
name: 'Kanya (enhanced)',
|
||||
model: 'enhanced',
|
||||
},
|
||||
{
|
||||
value: 'Narisa - standard',
|
||||
name: 'Narisa (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'tr-TR',
|
||||
name: 'Turkish (Turkey)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Cem-Ml - standard',
|
||||
name: 'Cem-Ml (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
{
|
||||
value: 'Yelda - standard',
|
||||
name: 'Yelda (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'uk-UA',
|
||||
name: 'Ukrainian (Ukraine)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Lesya - standard',
|
||||
name: 'Lesya (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'va-ES',
|
||||
name: 'Valencian (Spain)',
|
||||
voices: [
|
||||
{
|
||||
value: 'Empar - standard',
|
||||
name: 'Empar (standard)',
|
||||
model: 'standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'vi-VN',
|
||||
name: 'Vietnamese (Vietnam)',
|
||||
voices: [
|
||||
{ value: 'Linh - standard', name: 'Linh (standard)', model: 'standard' },
|
||||
],
|
||||
},
|
||||
];
|
||||
16
lib/utils/speech-data/tts-nvidia.js
Normal file
16
lib/utils/speech-data/tts-nvidia.js
Normal file
@@ -0,0 +1,16 @@
|
||||
module.exports = [
|
||||
{
|
||||
value: 'en-US',
|
||||
name: 'English',
|
||||
voices: [
|
||||
{
|
||||
value: 'English-US.Female-1',
|
||||
name: 'Female',
|
||||
},
|
||||
{
|
||||
value: 'English-US.Male-1',
|
||||
name: 'Male',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
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,',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
39
lib/utils/speech-data/tts-wellsaid.js
Normal file
39
lib/utils/speech-data/tts-wellsaid.js
Normal file
@@ -0,0 +1,39 @@
|
||||
module.exports = [
|
||||
{
|
||||
value: 'en-US',
|
||||
name: 'English (US)',
|
||||
voices: [
|
||||
{ value: '3', name: 'Alana B.' },
|
||||
{ value: '4', name: 'Ramona J.' },
|
||||
{ value: '5', name: 'Ramona J. (promo)' },
|
||||
{ value: '7', name: 'Wade C.' },
|
||||
{ value: '8', name: 'Sofia H.' },
|
||||
{ value: '9', name: 'David D.' },
|
||||
{ value: '11', name: 'Isabel V.' },
|
||||
{ value: '12', name: 'Ava H.' },
|
||||
{ value: '13', name: 'Jeremy G.' },
|
||||
{ value: '14', name: 'Nicole L.' },
|
||||
{ value: '15', name: 'Paige L.' },
|
||||
{ value: '16', name: 'Tobin A.' },
|
||||
{ value: '17', name: 'Kai M.' },
|
||||
{ value: '18', name: 'Tristan F.' },
|
||||
{ value: '19', name: 'Patrick K.' },
|
||||
{ value: '20', name: 'Soifia H. (promo)' },
|
||||
{ value: '21', name: 'Damian P. (promo)' },
|
||||
{ value: '22', name: 'Jodi P. (promo)' },
|
||||
{ value: '23', name: 'Lee M. (promo)' },
|
||||
{ value: '24', name: 'Selene R. (promo)' },
|
||||
{ value: '26', name: 'Wade C. (promo)' },
|
||||
{ value: '27', name: 'Joe F.' },
|
||||
{ value: '28', name: 'Joe F. (promo)' },
|
||||
{ value: '29', name: 'Garry J. (character)' },
|
||||
{ value: '33', name: 'Jude D.' },
|
||||
{ value: '34', name: 'Eric S. (promo)' },
|
||||
{ value: '35', name: 'Chase J.' },
|
||||
{ value: '37', name: 'Steve B. (promo)' },
|
||||
{ value: '38', name: 'Bella B. (promo)' },
|
||||
{ value: '39', name: 'Tilda C. (promo)' },
|
||||
{ value: '41', name: 'Paul B. (promo)' },
|
||||
],
|
||||
},
|
||||
];
|
||||
14
lib/utils/speech-data/tts-whisper.js
Normal file
14
lib/utils/speech-data/tts-whisper.js
Normal file
@@ -0,0 +1,14 @@
|
||||
module.exports = [
|
||||
{
|
||||
value: 'en-US',
|
||||
name: 'English',
|
||||
voices: [
|
||||
{ value: 'alloy', name: 'Alloy' },
|
||||
{ value: 'echo', name: 'Echo' },
|
||||
{ value: 'fable', name: 'Fable' },
|
||||
{ value: 'onyx', name: 'Onyx' },
|
||||
{ value: 'nova', name: 'Nova' },
|
||||
{ value: 'shimmer', name: 'Shimmer' },
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -5,7 +5,36 @@ const sdk = require('microsoft-cognitiveservices-speech-sdk');
|
||||
const { SpeechClient } = require('@soniox/soniox-node');
|
||||
const bent = require('bent');
|
||||
const fs = require('fs');
|
||||
const { AssemblyAI } = require('assemblyai');
|
||||
const {decrypt, obscureKey} = require('./encrypt-decrypt');
|
||||
|
||||
const TtsGoogleLanguagesVoices = require('./speech-data/tts-google');
|
||||
const TtsAwsLanguagesVoices = require('./speech-data/tts-aws');
|
||||
const TtsMicrosoftLanguagesVoices = require('./speech-data/tts-microsoft');
|
||||
const TtsWellsaidLanguagesVoices = require('./speech-data/tts-wellsaid');
|
||||
const TtsNuanceLanguagesVoices = require('./speech-data/tts-nuance');
|
||||
const TtsIbmLanguagesVoices = require('./speech-data/tts-ibm');
|
||||
const TtsNvidiaLanguagesVoices = require('./speech-data/tts-nvidia');
|
||||
const TtsElevenlabsLanguagesVoices = require('./speech-data/tts-elevenlabs');
|
||||
const TtsWhisperLanguagesVoices = require('./speech-data/tts-whisper');
|
||||
const TtsPlayHtLanguagesVoices = require('./speech-data/tts-playht');
|
||||
|
||||
const TtsModelDeepgram = require('./speech-data/tts-model-deepgram');
|
||||
const TtsModelElevenLabs = require('./speech-data/tts-model-elevenlabs');
|
||||
const TtsModelWhisper = require('./speech-data/tts-model-whisper');
|
||||
const TtsModelPlayHT = require('./speech-data/tts-model-playht');
|
||||
const TtsModelRimelabs = require('./speech-data/tts-model-rimelabs');
|
||||
|
||||
const SttGoogleLanguagesVoices = require('./speech-data/stt-google');
|
||||
const SttAwsLanguagesVoices = require('./speech-data/stt-aws');
|
||||
const SttMicrosoftLanguagesVoices = require('./speech-data/stt-microsoft');
|
||||
const SttNuanceLanguagesVoices = require('./speech-data/stt-nuance');
|
||||
const SttDeepgramLanguagesVoices = require('./speech-data/stt-deepgram');
|
||||
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 SttAssemblyaiLanguagesVoices = require('./speech-data/stt-assemblyai');
|
||||
|
||||
const testSonioxStt = async(logger, credentials) => {
|
||||
const api_key = credentials;
|
||||
@@ -93,6 +122,13 @@ const testMicrosoftStt = async(logger, credentials) => {
|
||||
const speechConfig = sdk.SpeechConfig.fromSubscription(api_key, region);
|
||||
const audioConfig = sdk.AudioConfig.fromWavFileInput(fs.readFileSync(`${__dirname}/../../data/test_audio.wav`));
|
||||
speechConfig.speechRecognitionLanguage = 'en-US';
|
||||
|
||||
if (process.env.JAMBONES_HTTP_PROXY_IP && process.env.JAMBONES_HTTP_PROXY_PORT) {
|
||||
logger.debug(
|
||||
`testMicrosoftStt: using proxy ${process.env.JAMBONES_HTTP_PROXY_IP}:${process.env.JAMBONES_HTTP_PROXY_PORT}`);
|
||||
speechConfig.setProxy(process.env.JAMBONES_HTTP_PROXY_IP, process.env.JAMBONES_HTTP_PROXY_PORT);
|
||||
}
|
||||
|
||||
const speechRecognizer = new sdk.SpeechRecognizer(speechConfig, audioConfig);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -129,16 +165,26 @@ const testAwsTts = async(logger, getTtsVoices, credentials) => {
|
||||
}
|
||||
};
|
||||
|
||||
const testAwsStt = async(logger, credentials) => {
|
||||
const testAwsStt = async(logger, getAwsAuthToken, credentials) => {
|
||||
try {
|
||||
const {region, accessKeyId, secretAccessKey} = credentials;
|
||||
const client = new TranscribeClient({
|
||||
region,
|
||||
credentials: {
|
||||
accessKeyId,
|
||||
secretAccessKey
|
||||
}
|
||||
});
|
||||
const {region, accessKeyId, secretAccessKey, roleArn} = credentials;
|
||||
let client = null;
|
||||
if (accessKeyId && secretAccessKey) {
|
||||
client = new TranscribeClient({
|
||||
region,
|
||||
credentials: {
|
||||
accessKeyId,
|
||||
secretAccessKey
|
||||
}
|
||||
});
|
||||
} else if (roleArn) {
|
||||
client = new TranscribeClient({
|
||||
region,
|
||||
credentials: await getAwsAuthToken(null, null, region, roleArn),
|
||||
});
|
||||
} else {
|
||||
client = new TranscribeClient({region});
|
||||
}
|
||||
const command = new ListVocabulariesCommand({});
|
||||
const response = await client.send(command);
|
||||
return response;
|
||||
@@ -148,38 +194,19 @@ const testAwsStt = async(logger, credentials) => {
|
||||
}
|
||||
};
|
||||
|
||||
const testMicrosoftTts = async(logger, credentials) => {
|
||||
const {
|
||||
api_key,
|
||||
region,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
use_custom_tts,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
custom_tts_endpoint,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
use_custom_stt,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
custom_stt_endpoint
|
||||
} = credentials;
|
||||
|
||||
logger.info({
|
||||
api_key,
|
||||
region,
|
||||
use_custom_tts,
|
||||
custom_tts_endpoint,
|
||||
use_custom_stt,
|
||||
custom_stt_endpoint
|
||||
}, 'testing microsoft tts');
|
||||
if (!api_key) throw new Error('testMicrosoftTts: credentials are missing api_key');
|
||||
if (!region) throw new Error('testMicrosoftTts: credentials are missing region');
|
||||
const testMicrosoftTts = async(logger, synthAudio, credentials) => {
|
||||
try {
|
||||
const getJSON = bent('json', {
|
||||
'Ocp-Apim-Subscription-Key': api_key
|
||||
});
|
||||
const response = await getJSON(`https://${region}.tts.speech.microsoft.com/cognitiveservices/voices/list`);
|
||||
return response;
|
||||
await synthAudio({increment: () => {}, histogram: () => {}},
|
||||
{
|
||||
vendor: 'microsoft',
|
||||
credentials,
|
||||
language: 'en-US',
|
||||
voice: 'en-US-JennyMultilingualNeural',
|
||||
text: 'Hi there and welcome to jambones!'
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
logger.info({err}, `testMicrosoftTts - failed to list voices for region ${region}`);
|
||||
logger.info({err}, 'testMicrosoftTts returned error');
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
@@ -203,6 +230,106 @@ const testWellSaidTts = async(logger, credentials) => {
|
||||
}
|
||||
};
|
||||
|
||||
const testElevenlabs = async(logger, credentials) => {
|
||||
const {api_key, model_id} = credentials;
|
||||
try {
|
||||
const post = bent('https://api.elevenlabs.io', 'POST', 'buffer', {
|
||||
'xi-api-key': api_key,
|
||||
'Accept': 'audio/mpeg',
|
||||
'Content-Type': 'application/json'
|
||||
});
|
||||
const mp3 = await post('/v1/text-to-speech/21m00Tcm4TlvDq8ikWAM', {
|
||||
text: 'Hello',
|
||||
model_id,
|
||||
voice_settings: {
|
||||
stability: 0.5,
|
||||
similarity_boost: 0.5
|
||||
}
|
||||
});
|
||||
return mp3;
|
||||
} catch (err) {
|
||||
logger.info({err}, 'synthEvenlabs returned error');
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const testPlayHT = async(logger, synthAudio, credentials) => {
|
||||
try {
|
||||
await synthAudio(
|
||||
{
|
||||
increment: () => {},
|
||||
histogram: () => {}
|
||||
},
|
||||
{
|
||||
vendor: 'playht',
|
||||
credentials,
|
||||
language: 'en-US',
|
||||
voice: 's3://voice-cloning-zero-shot/d9ff78ba-d016-47f6-b0ef-dd630f59414e/female-cs/manifest.json',
|
||||
text: 'Hi there and welcome to jambones!'
|
||||
}
|
||||
);
|
||||
// Test if playHT can fetch voices
|
||||
await fetchLayHTVoices(credentials);
|
||||
} catch (err) {
|
||||
logger.info({err}, 'synth Playht returned error');
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const testRimelabs = async(logger, synthAudio, credentials) => {
|
||||
try {
|
||||
await synthAudio(
|
||||
{
|
||||
increment: () => {},
|
||||
histogram: () => {}
|
||||
},
|
||||
{
|
||||
vendor: 'rimelabs',
|
||||
credentials,
|
||||
language: 'en-US',
|
||||
voice: 'amber',
|
||||
text: 'Hi there and welcome to jambones!'
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
logger.info({err}, 'synth Playht returned error');
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const testWhisper = async(logger, synthAudio, credentials) => {
|
||||
try {
|
||||
await synthAudio({increment: () => {}, histogram: () => {}},
|
||||
{
|
||||
vendor: 'whisper',
|
||||
credentials,
|
||||
language: 'en-US',
|
||||
voice: 'alloy',
|
||||
text: 'Hi there and welcome to jambones!'
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
logger.info({err}, 'synthEvenlabs returned error');
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const testDeepgramTTS = async(logger, synthAudio, credentials) => {
|
||||
try {
|
||||
await synthAudio({increment: () => {}, histogram: () => {}},
|
||||
{
|
||||
vendor: 'deepgram',
|
||||
credentials,
|
||||
model: 'aura-asteria-en',
|
||||
text: 'Hi there and welcome to jambones!'
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
logger.info({err}, 'testDeepgramTTS returned error');
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const testIbmTts = async(logger, getTtsVoices, credentials) => {
|
||||
const {tts_api_key, tts_region} = credentials;
|
||||
const voices = await getTtsVoices({vendor: 'ibm', credentials: {tts_api_key, tts_region}});
|
||||
@@ -237,6 +364,583 @@ const testWellSaidStt = async(logger, credentials) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
const testAssemblyStt = async(logger, credentials) => {
|
||||
const {api_key} = credentials;
|
||||
|
||||
const assemblyai = new AssemblyAI({
|
||||
apiKey: api_key
|
||||
});
|
||||
|
||||
const audioUrl = `${__dirname}/../../data/test_audio.wav`;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
assemblyai.transcripts
|
||||
.create({ audio_url: audioUrl })
|
||||
.then((transcript) => {
|
||||
logger.debug({transcript}, 'got transcription from AssemblyAi');
|
||||
if (transcript.status === 'error') {
|
||||
return reject({message: transcript.error});
|
||||
}
|
||||
return resolve(transcript.text);
|
||||
})
|
||||
.catch((err) => {
|
||||
logger.info({err}, 'failed to get assemblyAI transcription');
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const getSpeechCredential = (credential, logger) => {
|
||||
const {vendor} = credential;
|
||||
logger.info(
|
||||
`Speech vendor: ${credential.vendor} ${credential.label ? `, label: ${credential.label}` : ''} selected`);
|
||||
if ('google' === vendor) {
|
||||
try {
|
||||
const cred = JSON.parse(credential.service_key.replace(/\n/g, '\\n'));
|
||||
return {
|
||||
...credential,
|
||||
credentials: cred
|
||||
};
|
||||
} catch (err) {
|
||||
logger.info({err}, `malformed google service_key provisioned for account ${credential.speech_credential_sid}`);
|
||||
}
|
||||
}
|
||||
else if (['aws', 'polly'].includes(vendor)) {
|
||||
return {
|
||||
...credential,
|
||||
accessKeyId: credential.access_key_id,
|
||||
secretAccessKey: credential.secret_access_key,
|
||||
roleArn: credential.role_arn,
|
||||
region: credential.aws_region || 'us-east-1'
|
||||
};
|
||||
}
|
||||
return credential;
|
||||
};
|
||||
|
||||
function decryptCredential(obj, credential, logger, isObscureKey = true) {
|
||||
if ('google' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
const key_header = '-----BEGIN PRIVATE KEY-----\n';
|
||||
const obscured = {
|
||||
...o,
|
||||
private_key: `${key_header}${isObscureKey ?
|
||||
obscureKey(o.private_key.slice(key_header.length, o.private_key.length)) :
|
||||
o.private_key.slice(key_header.length, o.private_key.length)}`
|
||||
};
|
||||
obj.service_key = JSON.stringify(obscured);
|
||||
}
|
||||
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');
|
||||
}
|
||||
else if ('microsoft' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = isObscureKey ? obscureKey(o.api_key) : o.api_key;
|
||||
obj.region = o.region;
|
||||
obj.use_custom_tts = o.use_custom_tts;
|
||||
obj.custom_tts_endpoint = o.custom_tts_endpoint;
|
||||
obj.custom_tts_endpoint_url = o.custom_tts_endpoint_url;
|
||||
obj.use_custom_stt = o.use_custom_stt;
|
||||
obj.custom_stt_endpoint = o.custom_stt_endpoint;
|
||||
obj.custom_stt_endpoint_url = o.custom_stt_endpoint_url;
|
||||
logger.info({obj, o}, 'retrieving azure speech credential');
|
||||
}
|
||||
else if ('wellsaid' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = isObscureKey ? obscureKey(o.api_key) : o.api_key;
|
||||
}
|
||||
else if ('nuance' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.client_id = o.client_id;
|
||||
obj.secret = o.secret ? (isObscureKey ? obscureKey(o.secret) : o.secret) : null;
|
||||
obj.nuance_tts_uri = o.nuance_tts_uri;
|
||||
obj.nuance_stt_uri = o.nuance_stt_uri;
|
||||
}
|
||||
else if ('deepgram' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
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;
|
||||
}
|
||||
else if ('ibm' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.tts_api_key = isObscureKey ? obscureKey(o.tts_api_key) : o.tts_api_key;
|
||||
obj.tts_region = o.tts_region;
|
||||
obj.stt_api_key = isObscureKey ? obscureKey(o.stt_api_key) : o.stt_api_key;
|
||||
obj.stt_region = o.stt_region;
|
||||
obj.instance_id = o.instance_id;
|
||||
} else if ('nvidia' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.riva_server_uri = o.riva_server_uri;
|
||||
} else if ('cobalt' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.cobalt_server_uri = o.cobalt_server_uri;
|
||||
} else if ('soniox' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = isObscureKey ? obscureKey(o.api_key) : o.api_key;
|
||||
} else if ('elevenlabs' === 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 ('playht' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = isObscureKey ? obscureKey(o.api_key) : o.api_key;
|
||||
obj.user_id = o.user_id;
|
||||
obj.voice_engine = o.voice_engine;
|
||||
obj.options = o.options;
|
||||
} else if ('rimelabs' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = isObscureKey ? obscureKey(o.api_key) : o.api_key;
|
||||
obj.model_id = o.model_id;
|
||||
obj.options = o.options;
|
||||
} else if (obj.vendor.startsWith('custom:')) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.auth_token = isObscureKey ? obscureKey(o.auth_token) : o.auth_token;
|
||||
obj.custom_stt_url = o.custom_stt_url;
|
||||
obj.custom_tts_url = o.custom_tts_url;
|
||||
} else if ('assemblyai' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = isObscureKey ? obscureKey(o.api_key) : o.api_key;
|
||||
} else if ('whisper' === 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} logger logger
|
||||
* @param {*} vendor vendor
|
||||
* @param {*} credential STT/TTS vendor credential, can be null
|
||||
* @returns List of language and coresponding voices for specific vendor follow below format
|
||||
* {
|
||||
"tts": [
|
||||
{
|
||||
code: "ar-XA",
|
||||
name: "Arabic",
|
||||
voices: [
|
||||
{ value: "ar-XA-Standard-A", name: "Standard-A (Female)" },
|
||||
]
|
||||
}
|
||||
],
|
||||
"stt": [
|
||||
{ name: "Afrikaans (South Africa)", code: "af-ZA" },
|
||||
]
|
||||
}
|
||||
*/
|
||||
async function getLanguagesAndVoicesForVendor(logger, vendor, credential, getTtsVoices) {
|
||||
switch (vendor) {
|
||||
case 'google':
|
||||
return await getLanguagesVoicesForGoogle(credential, getTtsVoices, logger);
|
||||
case 'aws':
|
||||
return await getLanguagesVoicesForAws(credential, getTtsVoices, logger);
|
||||
case 'microsoft':
|
||||
return await getLanguagesVoicesForMicrosoft(credential, getTtsVoices, logger);
|
||||
case 'wellsaid':
|
||||
return await getLanguagesVoicesForWellsaid(credential, getTtsVoices, logger);
|
||||
case 'nuance':
|
||||
return await getLanguagesVoicesForNuane(credential, getTtsVoices, logger);
|
||||
case 'deepgram':
|
||||
return await getLanguagesVoicesForDeepgram(credential, getTtsVoices, logger);
|
||||
case 'ibm':
|
||||
return await getLanguagesVoicesForIbm(credential, getTtsVoices, logger);
|
||||
case 'nvidia':
|
||||
return await getLanguagesVoicesForNvida(credential, getTtsVoices, logger);
|
||||
case 'cobalt':
|
||||
return await getLanguagesVoicesForCobalt(credential, getTtsVoices, logger);
|
||||
case 'soniox':
|
||||
return await getLanguagesVoicesForSoniox(credential, getTtsVoices, logger);
|
||||
case 'elevenlabs':
|
||||
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);
|
||||
default:
|
||||
logger.info(`invalid vendor ${vendor}, return empty result`);
|
||||
throw new Error(`Invalid vendor ${vendor}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function getLanguagesVoicesForGoogle(credential, getTtsVoices, logger) {
|
||||
if (credential) {
|
||||
try {
|
||||
const [result] = await getTtsVoices({
|
||||
vendor: 'google',
|
||||
credentials: credential
|
||||
});
|
||||
const tts = parseGooglelanguagesVoices(result.voices);
|
||||
return tranform(tts, SttGoogleLanguagesVoices);
|
||||
} catch (err) {
|
||||
logger.info('Error while fetching google languages, voices, return predefined values', err);
|
||||
}
|
||||
}
|
||||
return tranform(TtsGoogleLanguagesVoices, SttGoogleLanguagesVoices);
|
||||
}
|
||||
|
||||
async function getLanguagesVoicesForAws(credential, getTtsVoices, logger) {
|
||||
if (credential) {
|
||||
try {
|
||||
const result = await getTtsVoices({
|
||||
vendor: 'aws',
|
||||
credentials: {
|
||||
accessKeyId: credential.access_key_id,
|
||||
secretAccessKey: credential.secret_access_key,
|
||||
roleArn: credential.role_arn,
|
||||
region: credential.aws_region || process.env.AWS_REGION
|
||||
}
|
||||
});
|
||||
const tts = parseAwsLanguagesVoices(result.Voices);
|
||||
return tranform(tts, SttAwsLanguagesVoices);
|
||||
} catch (err) {
|
||||
logger.info('Error while fetching AWS languages, voices, return predefined values', err);
|
||||
}
|
||||
}
|
||||
return tranform(TtsAwsLanguagesVoices, SttAwsLanguagesVoices);
|
||||
}
|
||||
|
||||
async function getLanguagesVoicesForMicrosoft(credential, getTtsVoices, logger) {
|
||||
if (credential) {
|
||||
try {
|
||||
const get = bent('https://westus.tts.speech.microsoft.com', 'GET', 'json', {
|
||||
'Ocp-Apim-Subscription-Key' : credential.api_key
|
||||
});
|
||||
|
||||
const voices = await get('/cognitiveservices/voices/list');
|
||||
const tts = parseMicrosoftLanguagesVoices(voices);
|
||||
return tranform(tts, SttMicrosoftLanguagesVoices);
|
||||
} catch (err) {
|
||||
logger.info('Error while fetching Microsoft languages, voices, return predefined values', err);
|
||||
}
|
||||
}
|
||||
return tranform(TtsMicrosoftLanguagesVoices, SttMicrosoftLanguagesVoices);
|
||||
}
|
||||
|
||||
async function getLanguagesVoicesForWellsaid(credential) {
|
||||
return tranform(TtsWellsaidLanguagesVoices);
|
||||
}
|
||||
|
||||
async function getLanguagesVoicesForNuane(credential, getTtsVoices, logger) {
|
||||
if (credential) {
|
||||
try {
|
||||
const result = await getTtsVoices({
|
||||
vendor: 'nuance',
|
||||
credentials: credential
|
||||
});
|
||||
const tts = parseNuanceLanguagesVoices(result.result.voices);
|
||||
return tranform(tts, SttNuanceLanguagesVoices);
|
||||
} catch (err) {
|
||||
logger.info('Error while fetching IBM languages, voices, return predefined values', err);
|
||||
}
|
||||
}
|
||||
return tranform(TtsNuanceLanguagesVoices, SttNuanceLanguagesVoices);
|
||||
}
|
||||
|
||||
async function getLanguagesVoicesForDeepgram(credential) {
|
||||
return tranform(undefined, SttDeepgramLanguagesVoices, TtsModelDeepgram);
|
||||
}
|
||||
|
||||
async function getLanguagesVoicesForIbm(credential, getTtsVoices, logger) {
|
||||
if (credential) {
|
||||
try {
|
||||
const result = await getTtsVoices({
|
||||
vendor: 'ibm',
|
||||
credentials: credential
|
||||
});
|
||||
const tts = parseIBMLanguagesVoices(result.result.voices);
|
||||
return tranform(tts, SttIbmLanguagesVoices);
|
||||
} catch (err) {
|
||||
logger.info('Error while fetching IBM languages, voices, return predefined values', err);
|
||||
}
|
||||
}
|
||||
return tranform(TtsIbmLanguagesVoices, SttIbmLanguagesVoices);
|
||||
}
|
||||
|
||||
async function getLanguagesVoicesForNvida(credential) {
|
||||
return tranform(TtsNvidiaLanguagesVoices, SttNvidiaLanguagesVoices);
|
||||
}
|
||||
|
||||
async function getLanguagesVoicesForCobalt(credential) {
|
||||
return tranform(undefined, SttCobaltLanguagesVoices);
|
||||
}
|
||||
|
||||
async function getLanguagesVoicesForSoniox(credential) {
|
||||
return tranform(undefined, SttSonioxLanguagesVoices);
|
||||
}
|
||||
|
||||
async function getLanguagesVoicesForElevenlabs(credential) {
|
||||
if (credential) {
|
||||
const get = bent('https://api.elevenlabs.io', 'GET', 'json', {
|
||||
'xi-api-key' : credential.api_key
|
||||
});
|
||||
|
||||
const [langResp, voiceResp] = await Promise.all([get('/v1/models'), get('/v1/voices')]);
|
||||
|
||||
const model = langResp.find((m) => m.model_id === credential.model_id);
|
||||
const languages = model ? model.languages.map((l) => {
|
||||
return {
|
||||
value: l.language_id,
|
||||
name: l.name
|
||||
};
|
||||
}).sort((a, b) => a.name.localeCompare(b.name)) : [];
|
||||
|
||||
if (languages && languages.length > 0) {
|
||||
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']},` : ''}
|
||||
`;
|
||||
const lastIndex = name.lastIndexOf(',');
|
||||
if (lastIndex !== -1) {
|
||||
name = name.substring(0, lastIndex);
|
||||
}
|
||||
return {
|
||||
value: v.voice_id,
|
||||
name
|
||||
};
|
||||
}).sort((a, b) => a.name.localeCompare(b.name)) : [];
|
||||
languages[0].voices = voices;
|
||||
}
|
||||
return tranform(languages, undefined, TtsModelElevenLabs);
|
||||
} else {
|
||||
return tranform(TtsElevenlabsLanguagesVoices, undefined, TtsModelElevenLabs);
|
||||
}
|
||||
}
|
||||
|
||||
const concat = (a) => {
|
||||
return a ? ` ${a},` : '';
|
||||
};
|
||||
|
||||
const fetchLayHTVoices = async(credential) => {
|
||||
if (credential) {
|
||||
const get = bent('https://api.play.ht', 'GET', 'json', {
|
||||
'AUTHORIZATION' : credential.api_key,
|
||||
'X-USER-ID': credential.user_id,
|
||||
'Accept': 'application/json'
|
||||
});
|
||||
|
||||
const voices = await get('/api/v2/voices');
|
||||
let clone_voices = [];
|
||||
try {
|
||||
// try if the account has permission to cloned voice
|
||||
//otherwise ignore this.
|
||||
clone_voices = await get('/api/v2/cloned-voices');
|
||||
} catch {}
|
||||
return [clone_voices, voices];
|
||||
}
|
||||
};
|
||||
|
||||
async function getLanguagesVoicesForPlayHT(credential) {
|
||||
if (credential) {
|
||||
const [cloned_voice, voices] = await fetchLayHTVoices(credential);
|
||||
const list_voices = [...cloned_voice, ...voices];
|
||||
|
||||
const buildVoice = (d) => {
|
||||
let name = `${d.name} -${concat(d.accent)}${concat(d.age)}${concat(d.gender)}${concat(d.loudness)}` +
|
||||
`${concat(d.style)}${concat(d.tempo)}${concat(d.texture)}` ;
|
||||
name = name.endsWith(',') ? name.trim().slice(0, -1) : name;
|
||||
return {
|
||||
value: `${d.id}`,
|
||||
name
|
||||
};
|
||||
};
|
||||
|
||||
const ttsVoices = list_voices.reduce((acc, voice) => {
|
||||
if (!credential.voice_engine.includes(voice.voice_engine)) {
|
||||
return acc;
|
||||
}
|
||||
const languageCode = voice.language_code;
|
||||
// custom voice does not have language code
|
||||
if (!languageCode) {
|
||||
voice.language_code = 'en';
|
||||
voice.language = 'Custom-English';
|
||||
}
|
||||
const existingLanguage = acc.find((lang) => lang.value === languageCode);
|
||||
if (existingLanguage) {
|
||||
existingLanguage.voices.push(buildVoice(voice));
|
||||
} else {
|
||||
acc.push({
|
||||
value: voice.language_code,
|
||||
name: voice.language,
|
||||
voices: [buildVoice(voice)]
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
return tranform(ttsVoices, 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) {
|
||||
return tranform(undefined, SttAssemblyaiLanguagesVoices);
|
||||
}
|
||||
|
||||
async function getLanguagesVoicesForWhisper(credential) {
|
||||
return tranform(TtsWhisperLanguagesVoices, undefined, TtsModelWhisper);
|
||||
}
|
||||
|
||||
function tranform(tts, stt, models) {
|
||||
return {
|
||||
...(tts && {tts}),
|
||||
...(stt && {stt}),
|
||||
...(models && {models})
|
||||
};
|
||||
}
|
||||
|
||||
function parseGooglelanguagesVoices(data) {
|
||||
return data.reduce((acc, voice) => {
|
||||
const languageCode = voice.languageCodes[0];
|
||||
const existingLanguage = acc.find((lang) => lang.value === languageCode);
|
||||
|
||||
if (existingLanguage) {
|
||||
existingLanguage.voices.push({
|
||||
value: voice.name,
|
||||
name: `${voice.name.substring(languageCode.length + 1, voice.name.length)} (${voice.ssmlGender})`
|
||||
});
|
||||
} else {
|
||||
acc.push({
|
||||
value: languageCode,
|
||||
name: SttGoogleLanguagesVoices.find((lang) => lang.value === languageCode)?.name || languageCode,
|
||||
voices: [{
|
||||
value: voice.name,
|
||||
name: `${voice.name.substring(languageCode.length + 1, voice.name.length)} (${voice.ssmlGender})`
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
function parseIBMLanguagesVoices(data) {
|
||||
return data.reduce((acc, voice) => {
|
||||
const languageCode = voice.language;
|
||||
const existingLanguage = acc.find((lang) => lang.value === languageCode);
|
||||
if (existingLanguage) {
|
||||
existingLanguage.voices.push({
|
||||
value: voice.name,
|
||||
name: `(${voice.gender}) ${voice.description}`
|
||||
});
|
||||
} else {
|
||||
acc.push({
|
||||
value: languageCode,
|
||||
name: SttGoogleLanguagesVoices.find((lang) => lang.value === languageCode)?.name || languageCode,
|
||||
voices: [{
|
||||
value: voice.name,
|
||||
name: `(${voice.gender}) ${voice.description}`
|
||||
}]
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
function parseAwsLanguagesVoices(data) {
|
||||
return data.reduce((acc, voice) => {
|
||||
const languageCode = voice.LanguageCode;
|
||||
const existingLanguage = acc.find((lang) => lang.value === languageCode);
|
||||
if (existingLanguage) {
|
||||
existingLanguage.voices.push({
|
||||
value: voice.Id,
|
||||
name: `(${voice.Gender}) ${voice.Name}`
|
||||
});
|
||||
} else {
|
||||
acc.push({
|
||||
value: languageCode,
|
||||
name: voice.LanguageName,
|
||||
voices: [{
|
||||
value: voice.Id,
|
||||
name: `(${voice.Gender}) ${voice.Name}`
|
||||
}]
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
function parseNuanceLanguagesVoices(data) {
|
||||
return data.reduce((acc, voice) => {
|
||||
const languageCode = voice.language;
|
||||
const existingLanguage = acc.find((lang) => lang.value === languageCode);
|
||||
if (existingLanguage) {
|
||||
existingLanguage.voices.push({
|
||||
value: voice.name,
|
||||
name: voice.name,
|
||||
model: voice.model
|
||||
});
|
||||
} else {
|
||||
acc.push({
|
||||
value: languageCode,
|
||||
name: SttGoogleLanguagesVoices.find((lang) => lang.value === languageCode)?.name || languageCode,
|
||||
voices: [{
|
||||
value: voice.name,
|
||||
name: voice.name,
|
||||
model: voice.model
|
||||
}]
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
function parseMicrosoftLanguagesVoices(data) {
|
||||
return data.reduce((acc, voice) => {
|
||||
const languageCode = voice.Locale;
|
||||
const existingLanguage = acc.find((lang) => lang.value === languageCode);
|
||||
if (existingLanguage) {
|
||||
existingLanguage.voices.push({
|
||||
value: voice.ShortName,
|
||||
name: `${voice.DisplayName} (${voice.Gender})`,
|
||||
});
|
||||
} else {
|
||||
acc.push({
|
||||
value: voice.Locale,
|
||||
name: voice.LocaleName,
|
||||
voices: [{
|
||||
value: voice.ShortName,
|
||||
name: `${voice.DisplayName} (${voice.Gender})`,
|
||||
}]
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
testGoogleTts,
|
||||
testGoogleStt,
|
||||
@@ -251,5 +955,14 @@ module.exports = {
|
||||
testDeepgramStt,
|
||||
testIbmTts,
|
||||
testIbmStt,
|
||||
testSonioxStt
|
||||
testSonioxStt,
|
||||
testElevenlabs,
|
||||
testPlayHT,
|
||||
testRimelabs,
|
||||
testAssemblyStt,
|
||||
testDeepgramTTS,
|
||||
getSpeechCredential,
|
||||
decryptCredential,
|
||||
testWhisper,
|
||||
getLanguagesAndVoicesForVendor
|
||||
};
|
||||
|
||||
134
lib/utils/storage-utils.js
Normal file
134
lib/utils/storage-utils.js
Normal file
@@ -0,0 +1,134 @@
|
||||
const { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectCommand } = require('@aws-sdk/client-s3');
|
||||
const {Storage} = require('@google-cloud/storage');
|
||||
const fs = require('fs');
|
||||
const { BlobServiceClient } = require('@azure/storage-blob');
|
||||
|
||||
// Azure
|
||||
|
||||
async function testAzureStorage(logger, opts) {
|
||||
const blobServiceClient = BlobServiceClient.fromConnectionString(opts.connection_string);
|
||||
const containerClient = blobServiceClient.getContainerClient(opts.name);
|
||||
const blockBlobClient = containerClient.getBlockBlobClient('jambonz-sample.text');
|
||||
|
||||
await blockBlobClient.uploadFile(`${__dirname}/jambonz-sample.text`);
|
||||
}
|
||||
|
||||
async function getAzureStorageObject(logger, opts) {
|
||||
const blobServiceClient = BlobServiceClient.fromConnectionString(opts.connection_string);
|
||||
const containerClient = blobServiceClient.getContainerClient(opts.name);
|
||||
const blockBlobClient = containerClient.getBlockBlobClient(opts.key);
|
||||
const response = await blockBlobClient.download(0);
|
||||
return response.readableStreamBody;
|
||||
}
|
||||
|
||||
async function deleteAzureStorageObject(logger, opts) {
|
||||
const blobServiceClient = BlobServiceClient.fromConnectionString(opts.connection_string);
|
||||
const containerClient = blobServiceClient.getContainerClient(opts.name);
|
||||
const blockBlobClient = containerClient.getBlockBlobClient(opts.key);
|
||||
await blockBlobClient.delete();
|
||||
}
|
||||
|
||||
// Google
|
||||
|
||||
function _initGoogleClient(opts) {
|
||||
const serviceKey = JSON.parse(opts.service_key);
|
||||
return new Storage({
|
||||
projectId: serviceKey.project_id,
|
||||
credentials: {
|
||||
client_email: serviceKey.client_email,
|
||||
private_key: serviceKey.private_key
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function testGoogleStorage(logger, opts) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const storage = _initGoogleClient(opts);
|
||||
|
||||
const blob = storage.bucket(opts.name).file('jambonz-sample.text');
|
||||
|
||||
fs.createReadStream(`${__dirname}/jambonz-sample.text`)
|
||||
.pipe(blob.createWriteStream())
|
||||
.on('error', (err) => reject(err))
|
||||
.on('finish', () => resolve());
|
||||
});
|
||||
}
|
||||
|
||||
async function getGoogleStorageObject(logger, opts) {
|
||||
const storage = _initGoogleClient(opts);
|
||||
|
||||
const bucket = storage.bucket(opts.name);
|
||||
const file = bucket.file(opts.key);
|
||||
const [exists] = await file.exists();
|
||||
if (exists) {
|
||||
return file.createReadStream();
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteGoogleStorageObject(logger, opts) {
|
||||
const storage = _initGoogleClient(opts);
|
||||
|
||||
const bucket = storage.bucket(opts.name);
|
||||
const file = bucket.file(opts.key);
|
||||
|
||||
await file.delete();
|
||||
}
|
||||
|
||||
// S3
|
||||
|
||||
function _initS3Client(opts) {
|
||||
return new S3Client({
|
||||
credentials: {
|
||||
accessKeyId: opts.access_key_id,
|
||||
secretAccessKey: opts.secret_access_key,
|
||||
},
|
||||
region: opts.region || 'us-east-1',
|
||||
...(opts.vendor === 's3_compatible' && { endpoint: opts.endpoint, forcePathStyle: true })
|
||||
});
|
||||
}
|
||||
|
||||
async function testS3Storage(logger, opts) {
|
||||
const s3 = _initS3Client(opts);
|
||||
const input = {
|
||||
'Body': 'Hello From Jambonz',
|
||||
'Bucket': opts.name,
|
||||
'Key': 'jambonz-sample.text'
|
||||
};
|
||||
const command = new PutObjectCommand(input);
|
||||
await s3.send(command);
|
||||
}
|
||||
|
||||
async function getS3Object(logger, opts) {
|
||||
const s3 = _initS3Client(opts);
|
||||
const command = new GetObjectCommand(
|
||||
{
|
||||
Bucket: opts.name,
|
||||
Key: opts.key
|
||||
}
|
||||
);
|
||||
const res = await s3.send(command);
|
||||
return res.Body;
|
||||
}
|
||||
|
||||
async function deleteS3Object(logger, opts) {
|
||||
const s3 = _initS3Client(opts);
|
||||
const command = new DeleteObjectCommand(
|
||||
{
|
||||
Bucket: opts.name,
|
||||
Key: opts.key
|
||||
}
|
||||
);
|
||||
await s3.send(command);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
testS3Storage,
|
||||
getS3Object,
|
||||
deleteS3Object,
|
||||
testGoogleStorage,
|
||||
getGoogleStorageObject,
|
||||
deleteGoogleStorageObject,
|
||||
testAzureStorage,
|
||||
getAzureStorageObject,
|
||||
deleteAzureStorageObject
|
||||
};
|
||||
15138
package-lock.json
generated
15138
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user