Compare commits

...

155 Commits

Author SHA1 Message Date
Dave Horton
035458ad3c logging 2025-08-13 20:33:40 -04:00
Hoan Luu Huu
fd9dc77a58 support resemble TTS (#488)
* support resemble TTS

* wip

* wip

* update speech utils version

* update resemble voice list
2025-08-13 08:18:08 -04:00
Hoan Luu Huu
2b66a121a0 fixed deepgram river does not return api_key (#486) 2025-07-30 08:29:57 -04:00
Hoan Luu Huu
3a6d10e725 support deepgram river (#481)
* support deepgram river

* update verb specification version
2025-07-29 13:51:36 -04:00
Dave Horton
6f87204d88 bump version 2025-07-15 11:44:19 -04:00
Sam Machin
9854666d4f add new /Callcount endpoint (#480)
* add new /Callcount endpoint

* update db-helpers

* update endpoint
2025-07-03 11:49:27 -04:00
Hoan Luu Huu
0d4b7e88ad update verb specification 0.0.107 (#479) 2025-07-03 07:15:59 -04:00
Dave Horton
819319dbe5 logging 2025-07-01 18:25:54 -04:00
Hoan Luu Huu
0ba69e872b support assemblyai v3 (#475)
* support assemblyai v3

* update verb specification
2025-07-01 15:48:00 -04:00
Sam Machin
9b4f1b67bf Fix/default app tts (#476)
* set default TTS voice for new applicaiton

* add migration to update existing NULL values

* add migration to add default for existing db

* allow null
2025-07-01 07:48:39 -04:00
Sam Machin
542ccfca79 check for whitespace in gateways and phone numbers (#477) 2025-07-01 07:16:27 -04:00
Hoan Luu Huu
5421f1421f support inworld tts (#472)
* support inworld tts

* inworld tts voices
2025-06-27 11:12:00 -04:00
Sam Machin
0842793aea Update users.js (#474)
* Update users.js

* Update users.js
2025-06-27 07:13:14 -04:00
Sam Machin
781179bf0e fix unhandled exception (#473)
* handle error and return 400

* Update accounts.js

* Update accounts.js

* new test passing
2025-06-26 13:09:42 -04:00
Sam Machin
1532a4ab9c update db_helpers (#468) 2025-06-23 10:02:25 -04:00
Sam Machin
5fd89b1d65 remove pino.destination (#465) 2025-06-23 09:23:53 -04:00
Hoan Luu Huu
e2fc0216e1 support mod cartesia transcribe (#463) 2025-06-17 20:53:22 +02:00
Sam Machin
fcff3d4b32 add proxy detail from registered client (#458) 2025-06-02 08:08:48 -04:00
Hoan Luu Huu
2dd06df641 Fix Application Model Pagination Issue with LEFT JOINs (#461) 2025-06-01 08:31:27 -04:00
Hoan Luu Huu
579a586a03 fixed filter for carriers for an account (#460)
* fixed filter for carriers for an account

* wip

* wip
2025-05-30 07:24:55 -04:00
Hoan Luu Huu
3e1b383284 fix microsoft fetch list voice from hardcode westus region (#459) 2025-05-29 10:07:58 -04:00
Hoan Luu Huu
c51b7bab82 admin can create call on behalf of account (#446)
* admin and service provider can create call on behalf of account

* wip

* admin and service provider can create call on behalf of account

* wip

* wip

* wip
2025-05-28 10:22:52 -04:00
Hoan Luu Huu
bb5dba7c20 support fetch tts/stt deepgram models from rest api (#457) 2025-05-28 09:59:02 -04:00
Hoan Luu Huu
c7e279d0ee support S3 compatible region (#453)
* support S3 compatible region

* wip
2025-05-28 08:04:15 -04:00
Hoan Luu Huu
6700ff35be support fetching application with pagination (#450)
* support fetching application with pagination

* pagination for voip carrier

* wip

* wip

* wip

* support phone number pagination

* wip

* wip

* wip
2025-05-28 07:28:48 -04:00
Sam Machin
3f2a304830 add rate limit by real ip or apikey (#455) 2025-05-23 12:36:35 -04:00
Hoan Luu Huu
f23c4fbd48 forward updateCall error response from feature server to client (#454)
* forward updateCall error response from feature server to client

* wip

* wip

* update review comment
2025-05-23 06:27:12 -04:00
Hoan Luu Huu
0c2f5becdc fixed updateCall cannot response 202 Accepted (#451) 2025-05-21 08:13:04 -04:00
Hoan Luu Huu
cd6772c10f wip (#449) 2025-05-19 09:51:06 -04:00
Dave Horton
b708f7beb6 update version 2025-05-14 15:39:38 -04:00
Hoan Luu Huu
431cc9e4f4 support filter phone numbers (#447) 2025-05-14 07:54:48 -04:00
Vasudev Anubrolu
35b10d55d5 feat/864 speech utils version up (#445) 2025-05-13 11:02:34 -04:00
Dave Horton
533e202474 update db-helpers 2025-05-13 10:35:00 -04:00
Dave Horton
e506fc8b66 fix bug updating application with env vars (#440) 2025-05-13 10:15:55 -04:00
Hoan Luu Huu
76a2054745 update speech-utils and verb specification (#444) 2025-05-13 09:44:21 -04:00
Hoan Luu Huu
be300ebd51 fixed save obscurbed azure connection_string for bucket credential (#443)
* fixed save obscurbed azure connection_string for bucket credential

* wip
2025-05-09 07:46:59 -04:00
Hoan Luu Huu
27c3664391 fixed API server crash when admin query voip-carrier (#442) 2025-05-09 07:42:24 -04:00
Dave Horton
48e39f37d3 when app url is websocket we still need to send to http schema not ws (#439)
* when app url is websocket we still need to send to http schema not ws

* fix bug from a previous PR
2025-05-08 13:41:36 -04:00
Vasudev Anubrolu
2b4b3056e9 feat/864 update speech utils version (#438) 2025-05-08 12:47:52 -04:00
Vasudev Anubrolu
3cad5219b4 feat/864 playht on prem (#432)
* feat/864 playht on prem

* feat/864 update speech utils version

---------

Co-authored-by: vasudevan-Kore <vasudev.anubrolu@kore.com>
2025-05-08 10:00:07 -04:00
Sam Machin
30a799030c Feat/app env vars (#430)
* initial changes for env var support

* WIP

* Update applications.js

* JSON stringify before encrypting

* use call_hook.url

* env vars working

GET /v1/AppEnv?url=[URL] to trigger options request to URL and return app-schema
POST /v1/Applications with {env_vars: [OBJECT} to create app with env vars
PUT /v1/Applications/[SID] with {env_vars: [OBJECT} to change env vars
GET returns env vars

POST and PUT will also trigger an OPTIONS request to the call_hook url to get schema and then validate the env_vars against it

* update appenv cannot finish request.

* wip

* wip

* wip

* wip

---------

Co-authored-by: Dave Horton <daveh@beachdognet.com>
Co-authored-by: Quan HL <quan.luuhoang8@gmail.com>
Co-authored-by: Hoan Luu Huu <110280845+xquanluu@users.noreply.github.com>
2025-05-08 08:41:50 -04:00
Hoan Luu Huu
e41caf8887 Fixed download pcap and jaeger trace when using fetch lib (#436)
* fixed cannot download pcap file

* wip
2025-05-07 20:08:06 -04:00
Anton Voylenko
217c11a5e1 fix: missing token update and missing return (#435) 2025-05-07 14:14:40 -04:00
Hoan Luu Huu
561de0532f support rime Arcana (#434) 2025-05-06 08:49:32 -04:00
Sam Machin
ce2ea8bd62 validate call hook urls (#431) 2025-05-01 07:22:01 -04:00
Hoan Luu Huu
c21f5b871f add new voice for nvidia tts (#428)
* add new voice for nvidia tts

* update nvidia tts voices
2025-04-29 09:33:05 -04:00
Sam Machin
9a2e48b538 update AWS transcribe languages (#427)
Using info from https://docs.aws.amazon.com/transcribe/latest/dg/supported-languages.html
2025-04-28 09:11:47 -04:00
Sam Machin
29adbfc6ae Add Aura-2 Voices (#426) 2025-04-28 07:26:15 -04:00
Hoan Luu Huu
ffda2398f4 replace bent by native node fetch (#401)
* replace bent by native node fetch

* wip

* wip

* wip
2025-04-24 06:50:15 -04:00
Sam Machin
b05b32d73e Update speech-utils.js (#425) 2025-04-23 10:29:19 -04:00
Hoan Luu Huu
b8bf18f8ca update speech utils version 0.2.6 (#424) 2025-04-23 08:23:19 -04:00
Hoan Luu Huu
1e532212f9 support whisper tts model (#405) 2025-04-22 09:47:13 -04:00
Hoan Luu Huu
92347c26bf fix speechmatic cannot update stt url (#410) 2025-04-22 09:35:02 -04:00
Sam Machin
bc51b60e9b fetch voip carriers where api key doesn't have the service_provdier_sid availible (#412) 2025-04-22 09:34:36 -04:00
Sam Machin
f0ec0a916f Update sip-gateways.js (#421) 2025-04-22 09:30:55 -04:00
rammohan-y
c94f14f27d Added new model Playht Dialog-1-0 model (#423)
https://github.com/jambonz/jambonz-api-server/issues/419
2025-04-22 09:30:18 -04:00
Sam Machin
06873186ac fix the creds test and return correct error (#420) 2025-04-22 07:38:54 -04:00
rammohan-y
956da4334f Support ssl connection to mysql (#414)
* Support ssl connection to mysql
https://github.com/jambonz/jambonz-api-server/issues/413

* using 0/1 for rejectUnauthorized

* added validations for JAMBONES_MYSQL_REJECT_UNAUTHORIZED

* simplied assignment of opts.ssl

* Updated db-helpers to 0.9.11

https://github.com/jambonz/jambonz-feature-server/issues/1151
2025-04-14 08:21:55 -04:00
Hoan Luu Huu
c144758d44 fix: reject if user.provider !== local when login (#408) 2025-04-09 09:53:24 -04:00
rammohan-y
e24f3472ae user should be able to change the temprary password (#407)
Refer to https://github.com/jambonz/jambonz-api-server/issues/406
2025-04-09 09:32:15 -04:00
rammohan-y
4c935c7fda Feat/371 view only user implementation using user_permissions (#381)
* https://github.com/jambonz/jambonz-api-server/issues/371

Implemented view_only permission feature

* calling prepare-permissions in create-test-db.js

* check if there is only 1 permission and if it is VIEW_ONLY then consider user as read-only user

* setting is_view_only flag for view user by userid
2025-04-01 09:29:06 -04:00
Hoan Luu Huu
1c55bad04f support openai stt (#402)
* support openai stt

* wip

* wip

* add stt languages for openai
2025-03-28 10:14:50 -04:00
Hoan Luu Huu
32a2bfcdb5 support cartesia sonic-2 model (#403)
* support cartesia sonic-2 model

* wip

* fix typo

---------

Co-authored-by: Dave Horton <daveh@beachdognet.com>
2025-03-28 09:52:01 -04:00
Hoan Luu Huu
becc1636b7 deepgram milti languages (#397) 2025-03-17 21:10:22 -04:00
Sam Machin
68a9b4226d fix min 25 (#396) 2025-03-11 07:48:04 -04:00
rammohan-y
b154b56064 updated realtimedb-helper to 0.8.13 (#395) 2025-03-10 09:54:24 -04:00
Hoan Luu Huu
556d5c3526 support fetching logs from cloudwatch for call_sid (#393)
* support fetching logs from cloudwatch for call_sid

* wip

* wip

* wip

* ưip

* wip

* fix review comments
2025-03-10 08:31:24 -04:00
Hoan Luu Huu
2ac0da0d14 Should not allow create phone_number on different voip_carrier and account (#394)
* shouldnt create phonenumber on different carrier and account

* wip
2025-03-06 07:40:54 -05:00
Dave Horton
5a02346c71 #390: correct column name is outbound_sip_proxy (#391) 2025-02-24 15:58:34 -05:00
Hoan Luu Huu
d872d9ee87 suport voip carrier sip_proxy (#389)
* suport voip carrier sip_proxy

* fix review comment
2025-02-17 09:46:20 -05:00
Dave Horton
d81a0167cf scripts for import and export of database 2025-02-12 08:28:43 -05:00
Hoan Luu Huu
c7c56d8ea0 update speech utils version (#388) 2025-02-07 08:00:21 -05:00
Hoan Luu Huu
9cfe990bb8 support rimelabs new voices (#387) 2025-02-07 07:22:42 -05:00
Hoan Luu Huu
6c7d2c9074 support rime mistv2 model (#386) 2025-02-06 21:36:34 -05:00
Hoan Luu Huu
73e35c84c5 support voxist stt (#384) 2025-02-05 08:32:36 -05:00
Dave Horton
86d50d94cb optionally send invites to our slack channel on signup (#385) 2025-02-05 00:06:58 -05:00
Hoan Luu Huu
b8f4ad6b27 support fetching elevenlabs models by api-key (#383) 2025-02-03 08:28:48 -05:00
Dave Horton
ad3ec926ee proper version 2025-02-02 17:11:33 -05:00
rammohan-y
66bd9a442c feat/379: added tts property to deepgram response (#380) 2025-01-21 08:15:32 -05:00
Hoan Luu Huu
fa81d179a1 verbio list voice api is changed, fixed (#378) 2025-01-15 06:54:55 -05:00
Dave Horton
fab8a391b7 update deps 2025-01-14 10:46:42 -05:00
Hoan Luu Huu
89288acf6e support custom tts streaming vendor (#377) 2025-01-14 07:14:07 -05:00
Markus Frindt
23cd4408a5 Feat/obscure sensitive bucket credentials (#375)
* obscure sensitive bucket credentials

* npm audit fix

* fix condition

* add test suite encrypt-decrypt.test

* revert docker-compose

* update pipeline

---------

Co-authored-by: mfrindt <m.frindt@cognigy.com>
2025-01-03 07:12:44 -05:00
Dave Horton
ce4618523c correct clients.password col name (#373)
* correct clients.password col name

* fix #372
2024-12-28 16:42:32 -05:00
Hoan Luu Huu
0eb8097e32 support tts cartesia (#370)
* support tts cartesia

* update speech utils

* revert reset password

* revert serve-integration
2024-12-19 09:19:28 -05:00
rammohan-y
8851b3fac0 feat/367 added support for name query parameter for retrieving application (#368)
* feat/367 added support for name query parameter for getting application by name

* feat/367 - Updated test case and modified retrieveAll method to make name independent of service_provider_id and account_sid

* updated query to use WHERE 1=1 to avoid whereFlag variable

* feat/367: removed empty line added accidently
2024-12-18 08:18:48 -05:00
Dave Horton
e080118b6a minor logging changes 2024-12-13 10:47:25 -05:00
rammohan-y
75c27e3f80 feat/982: fixed issue where vendor's API is not called if vendor is defined at service provider level, and also made format of name consistent (#365) 2024-12-12 17:06:45 -05:00
Dave Horton
843980c7f6 #361 (#362)
* #361

* fix typo in db upgrade script

---------

Co-authored-by: Hoan Luu Huu <110280845+xquanluu@users.noreply.github.com>
2024-11-26 20:25:25 -05:00
Hoan Luu Huu
f9990da468 allow dub as http updateCall request (#363) 2024-11-14 07:20:10 -05:00
Hoan Luu Huu
e8d5655abb update speech utils version (#360) 2024-11-04 08:07:43 -05:00
Hoan Luu Huu
e908f5830c support add google voice cloning key (#358)
* support add google voice cloning key

* update upgrade db script

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

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

* support speechmatics region authentication

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

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

* db schema upgrade to add system_information.private_network_cidr in 0.9.2

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

* wip

* update speech utils version

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

* wip

* wip

* wip
2024-07-24 13:04:12 -04:00
Hoan Luu Huu
48e1a72ef3 support use sips scheme for outbound tls gateway (#332)
* support use sips scheme for outbound tls gateway

* support use sips scheme for outbound tls gateway

* update license
2024-06-15 09:17:05 -04:00
Hoan Luu Huu
4337a55a27 update getAwsAuthToken to use parameters as object (#330)
* update getAwsAuthToken to use parameters as object

* update speech utils version
2024-06-15 08:10:58 -04:00
Hoan Luu Huu
6041b1d595 fix cannot update verbio engine_version (#327) 2024-06-04 09:48:24 -04:00
Hoan Luu Huu
d33d0aa519 support verbio speech (#323)
* support verbio speech

* wip

* update speech version

* update verb specification
2024-05-29 07:35:40 -04:00
Dave Horton
ffe9cb23eb update speech-utils (#325) 2024-05-28 18:24:57 -04:00
Hoan Luu Huu
dbbc894832 support list conference (#321)
* support list conference

* add test case

* fix conference action requires tag

* fix failing test case
2024-05-28 10:31:16 -04:00
Hoan Luu Huu
82c16380f5 fix Speech credential test for azure (#322) 2024-05-14 06:56:06 -04:00
Hoan Luu Huu
c0fab2880b fix cannot send multipart to aws due to min size (#319) 2024-05-03 07:37:38 -04:00
Hoan Luu Huu
ce2fa392a4 support aws speech by roleArn (#313)
* support aws speech by roleArn

* support 3 types of aws  credentials

* wip

* wip

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

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

* wip

* add testcase for synthesize text

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

* wip

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

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

* wip

* wip

* wip

* wip
2024-04-12 07:01:13 -04:00
Hoan Luu Huu
b21d10eb3e fetch playht custom voice (#307) 2024-04-09 08:48:18 -04:00
Hoan Luu Huu
7875eb51b9 playht should return list of voice match voice engine configured at speech credentials (#306) 2024-04-09 06:53:17 -04:00
Hoan Luu Huu
e2c1383723 support mod_playht_tts (#304)
* support mod_playht_tts

* wip

* wip

* wip

* wip

* wip

* update speech utils version
2024-04-08 10:21:29 -04:00
Dave Horton
40de2c5945 option_ping was incorrectly removed, adding back (#305) 2024-04-08 08:56:31 -04:00
Dave Horton
3a299bc3ca update to speech utils with azure 1.36.0 (#303) 2024-04-07 17:45:33 -04:00
Dave Horton
70c9407742 update to speech utils with azure 1.36.0 2024-04-07 12:16:55 -04:00
Dave Horton
dba66d58fc back out column addition of -register_use_tls 2024-04-06 13:48:26 -04:00
Dave Horton
0ff3d22faf Revert "feat send options ping for sip gateway (#273)"
This reverts commit a4792a521f.
2024-04-06 13:27:32 -04:00
Hoan Luu Huu
187a428a75 register use tls (#302) 2024-04-04 08:02:29 -04:00
Hoan Luu Huu
a4792a521f feat send options ping for sip gateway (#273)
* feat send options ping for sip gateway

* update upgrade db script to have 8006
2024-03-30 09:14:29 -04:00
Dave Horton
3ac9693735 update speech-utils with fixes for deepgram production api and tts streaming 2024-03-24 08:15:00 -04:00
Dave Horton
3ad54a0e72 update to released deepgram tts voices (#299) 2024-03-13 09:16:12 -04:00
Hoan Luu Huu
bd8fb2f9db remove use_streaming from speech credential (#294)
* remove use_streaming from speech credential

* wip
2024-02-20 08:01:33 -05:00
Dave Horton
32b317ae68 update to latest speech-utils 2024-02-12 21:11:49 -05:00
Hoan Luu Huu
40e8d08727 support deepgram tts onprem (#292)
* support deepgram tts onprem

* wip

* wip

* deepgram disable speech test if api_key is missng
2024-02-12 09:27:13 -05:00
Hoan Luu Huu
256ca440a0 add use_streaming flag for elevenlabs and whisper (#290)
* add use_streaming flag for elevenlabs (not for whisper yet)
---------

Co-authored-by: Dave Horton <daveh@beachdognet.com>
2024-02-12 09:18:49 -05:00
Markus Frindt
68d73345ef Improve Swagger file, add login route, fix swagger linting (#291)
Co-authored-by: Markus Frindt <m.frindt@cognigy.com>
2024-02-06 12:10:31 -05:00
Hoan Luu Huu
54dd72ff66 fetch list of tts voices from provider (#289)
* fetch list of tts voices from provider

* revert serve-integration

* fix

* fix for aws

* fix for aws

* fix for aws

* update speech-utils version
2024-01-25 12:03:02 -05:00
Dave Horton
832a4e8032 update db-helpers 2024-01-17 13:24:51 -05:00
Hoan Luu Huu
33c3b99e2e update paid account to active if it's in deactivated (#287)
* update paid account to active if it's in deactivated

* fix review comment
2024-01-17 09:20:38 -05:00
Hoan Luu Huu
8b2a2e196e Feat/record upload buffer (#285)
* uploader with buffer for google and azure

* wip

* wip

* wip
2024-01-15 09:51:10 -05:00
Hoan Luu Huu
556717a9a4 enable convert raw to mp3 (#284) 2024-01-11 07:47:41 -05:00
Dave Horton
f2c635268f #283: fix for db upgrade script 2024-01-10 10:11:47 -05:00
Hoan Luu Huu
c8999a5929 fix tts stt langs and voices when credential is provided (#282) 2024-01-09 07:31:33 -05:00
Hoan Luu Huu
7e046ac7f3 TTS/STT languages and voices for each provider (#281)
* wip

* wip

* wip

* wip

* wip

* wip

* add testcase
2024-01-08 18:15:40 -05:00
Hoan Luu Huu
997ff05f3c support tts deepgram (#277) 2023-12-26 07:47:19 -05:00
Hoan Luu Huu
55d8fdef1c fix nuance onprem stt does not have data (#276) 2023-12-19 09:12:40 -05:00
Dave Horton
7d355f2fac enable azure stt through a proxy (#275) 2023-12-18 12:37:10 -05:00
Hoan Luu Huu
c6b8ec1b28 fix upload recording (#274) 2023-12-18 08:36:25 -05:00
137 changed files with 24936 additions and 14448 deletions

View File

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

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021 Drachtio Communications Services, LLC
Copyright (c) 2018-2024 FirstFive8, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

44
app.js
View File

@@ -7,6 +7,7 @@ const nocache = require('nocache');
const rateLimit = require('express-rate-limit');
const cors = require('cors');
const passport = require('passport');
const {verifyViewOnlyUser} = require('./lib/middleware');
const routes = require('./lib/routes');
const Registrar = require('@jambonz/mw-registrar');
@@ -46,12 +47,16 @@ const {
addKey,
retrieveKey,
deleteKey,
incrKey
incrKey,
listConferences,
getCallCount
} = require('./lib/helpers/realtimedb-helpers');
const {
getTtsVoices,
getTtsSize,
purgeTtsCache,
getAwsAuthToken,
getVerbioAccessToken,
synthAudio
} = require('@jambonz/speech-utils')({}, logger);
const {
@@ -87,6 +92,7 @@ app.locals = {
deleteCall,
listCalls,
listSortedSets,
listConferences,
purgeCalls,
retrieveSet,
addKey,
@@ -95,6 +101,8 @@ app.locals = {
deleteKey,
getTtsVoices,
getTtsSize,
getAwsAuthToken,
getVerbioAccessToken,
purgeTtsCache,
synthAudio,
lookupAppBySid,
@@ -111,7 +119,8 @@ app.locals = {
queryAlertsSP,
writeCdrs,
writeAlerts,
AlertType
AlertType,
getCallCount
};
const unless = (paths, middleware) => {
@@ -121,11 +130,27 @@ const unless = (paths, middleware) => {
};
};
const RATE_LIMIT_BY = process.env.RATE_LIMIT_BY || 'system';
const limiter = rateLimit({
windowMs: (process.env.RATE_LIMIT_WINDOWS_MINS || 5) * 60 * 1000, // 5 minutes
max: process.env.RATE_LIMIT_MAX_PER_WINDOW || 600, // Limit each IP to 600 requests per `window`
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
keyGenerator: (req, res) => {
switch (RATE_LIMIT_BY) {
case 'system':
return '127.0.0.1';
case 'apikey':
// uses shared limit for requests without an authorization header
const token = req.headers.authorization?.split(' ')[1] || '127.0.0.1';
return token;
case 'ip':
return req.headers['x-real-ip'];
default:
return '127.0.0.1';
}
}
});
// Setup websocket for recording audio
@@ -166,6 +191,19 @@ app.use('/v1', unless(
'/InviteCodes',
'/PredefinedCarriers'
], passport.authenticate('bearer', {session: false})));
app.use('/v1', unless(
[
'/register',
'/forgot-password',
'/signin',
'/login',
'/messaging',
'/outboundSMS',
'/AccountTest',
'/InviteCodes',
'/PredefinedCarriers',
'/logout'
], verifyViewOnlyUser));
app.use('/', routes);
app.use((err, req, res, next) => {
logger.error(err, 'burped error');
@@ -216,7 +254,7 @@ server.on('upgrade', (request, socket, head) => {
/* complete the upgrade */
wsServer.handleUpgrade(request, socket, head, (ws) => {
logger.info(`upgraded to websocket, url: ${request.url}`);
logger.debug(`upgraded to websocket, url: ${request.url}`);
wsServer.emit('connection', ws, request.url);
});
});

22
db/export_jambonz.sh Executable file
View File

@@ -0,0 +1,22 @@
#!/bin/bash
# This script exports the 'jambones' database (schema and data)
# from the source MySQL server into a file.
# Configuration variables
SOURCE_HOST=
DB_USER=
DB_PASS=
DB_NAME=
EXPORT_FILE="jambones_export.sql"
# Export the database using mysqldump
echo "Exporting database '$DB_NAME' from $SOURCE_HOST..."
mysqldump -h "$SOURCE_HOST" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" > "$EXPORT_FILE"
# Check for errors
if [ $? -eq 0 ]; then
echo "Database export successful. Export file created: $EXPORT_FILE"
else
echo "Error exporting database '$DB_NAME'."
exit 1
fi

31
db/import_jambonz.sh Executable file
View File

@@ -0,0 +1,31 @@
#!/bin/bash
# This script imports the SQL dump file into the target MySQL server.
# It first drops the existing 'jambones' database (if it exists),
# recreates it, and then imports the dump file.
# Configuration variables
TARGET_HOST=
DB_USER=
DB_PASS=
DB_NAME=
IMPORT_FILE="jambones_export.sql"
# Drop the existing database (if any) and create a new one
echo "Dropping and recreating database '$DB_NAME' on $TARGET_HOST..."
mysql -h "$TARGET_HOST" -u "$DB_USER" -p"$DB_PASS" -e "DROP DATABASE IF EXISTS \`$DB_NAME\`; CREATE DATABASE \`$DB_NAME\`;"
if [ $? -ne 0 ]; then
echo "Error dropping/creating database '$DB_NAME'."
exit 1
fi
# Import the SQL dump into the newly created database
echo "Importing dump file '$IMPORT_FILE' into database '$DB_NAME'..."
mysql -h "$TARGET_HOST" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" < "$IMPORT_FILE"
if [ $? -eq 0 ]; then
echo "Database import successful."
else
echo "Error importing the database."
exit 1
fi

View File

@@ -162,7 +162,7 @@ regex VARCHAR(32) NOT NULL COMMENT 'regex-based pattern match against dialed num
description VARCHAR(1024),
priority INTEGER NOT NULL COMMENT 'lower priority routes are attempted first',
PRIMARY KEY (lcr_route_sid)
) COMMENT='An ordered list of digit patterns in an LCR table. The pat';
) COMMENT='An ordered list of digit patterns in an LCR table. The patterns are tested in sequence until one matches';
CREATE TABLE lcr
(
@@ -173,7 +173,7 @@ default_carrier_set_entry_sid CHAR(36) COMMENT 'default carrier/route to use whe
service_provider_sid CHAR(36),
account_sid CHAR(36),
PRIMARY KEY (lcr_sid)
) COMMENT='An LCR (least cost routing) table that is used by a service ';
) 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
(
@@ -351,6 +351,8 @@ speech_credential_sid CHAR(36) NOT NULL,
model VARCHAR(512) NOT NULL,
reported_usage ENUM('REPORTED_USAGE_UNSPECIFIED','REALTIME','OFFLINE') DEFAULT 'REALTIME',
name VARCHAR(64) NOT NULL,
voice_cloning_key MEDIUMTEXT,
use_voice_cloning_key BOOLEAN DEFAULT false,
PRIMARY KEY (google_custom_voice_sid)
);
@@ -358,7 +360,9 @@ CREATE TABLE system_information
(
domain_name VARCHAR(255),
sip_domain_name VARCHAR(255),
monitoring_domain_name VARCHAR(255)
monitoring_domain_name VARCHAR(255),
private_network_cidr VARCHAR(8192),
log_level ENUM('info', 'debug') NOT NULL DEFAULT 'info'
);
CREATE TABLE users
@@ -412,6 +416,8 @@ register_from_user VARCHAR(128),
register_from_domain VARCHAR(255),
register_public_ip_in_contact BOOLEAN NOT NULL DEFAULT false,
register_status VARCHAR(4096),
dtmf_type ENUM('rfc2833','tones','info') NOT NULL DEFAULT 'rfc2833',
outbound_sip_proxy VARCHAR(255),
PRIMARY KEY (voip_carrier_sid)
) COMMENT='A Carrier or customer PBX that can send or receive calls';
@@ -458,6 +464,8 @@ inbound BOOLEAN NOT NULL COMMENT 'if true, whitelist this IP to allow inbound ca
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,
use_sips_scheme BOOLEAN NOT NULL DEFAULT 0,
pad_crypto BOOLEAN NOT NULL DEFAULT 0,
protocol ENUM('udp','tcp','tls', 'tls/srtp') DEFAULT 'udp' COMMENT 'Outbound call protocol',
PRIMARY KEY (sip_gateway_sid)
@@ -495,7 +503,7 @@ 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) DEFAULT 'en-US-Standard-C',
speech_synthesis_label VARCHAR(64),
speech_recognizer_vendor VARCHAR(64) NOT NULL DEFAULT 'google',
speech_recognizer_language VARCHAR(64) NOT NULL DEFAULT 'en-US',
@@ -503,11 +511,12 @@ 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(64),
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),
env_vars TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
record_all_calls BOOLEAN NOT NULL DEFAULT false,
PRIMARY KEY (application_sid)
@@ -550,6 +559,7 @@ siprec_hook_sid CHAR(36),
record_all_calls BOOLEAN NOT NULL DEFAULT false,
record_format VARCHAR(16) NOT NULL DEFAULT 'mp3',
bucket_credential VARCHAR(8192) COMMENT 'credential used to authenticate with storage service',
enable_debug_log BOOLEAN NOT NULL DEFAULT false,
PRIMARY KEY (account_sid)
) COMMENT='An enterprise that uses the platform for comm services';
@@ -734,4 +744,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;

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,11 @@
/* remove VIEW_ONLY permission for admin user as it will prevent write operations*/
delete from user_permissions;
delete from permissions;
insert into permissions (permission_sid, name, description)
values
('ffbc342a-546a-11ed-bdc3-0242ac120002', 'VIEW_ONLY', 'Can view data but not make changes'),
('ffbc3a10-546a-11ed-bdc3-0242ac120002', 'PROVISION_SERVICES', 'Can provision services'),
('ffbc3c5e-546a-11ed-bdc3-0242ac120002', 'PROVISION_USERS', 'Can provision users');

View File

@@ -6,6 +6,7 @@ const {readFile} = require('fs/promises');
const {execSync} = require('child_process');
const {version:desiredVersion} = require('../package.json');
const logger = require('pino')();
const fs = require('fs');
logger.info(`upgrade-jambonz-db: desired version ${desiredVersion}`);
@@ -22,6 +23,20 @@ const opts = {
port: process.env.JAMBONES_MYSQL_PORT || 3306,
multipleStatements: true
};
const rejectUnauthorized = process.env.JAMBONES_MYSQL_REJECT_UNAUTHORIZED;
const ssl_ca_file = process.env.JAMBONES_MYSQL_SSL_CA_FILE;
const ssl_cert_file = process.env.JAMBONES_MYSQL_SSL_CERT_FILE;
const ssl_key_file = process.env.JAMBONES_MYSQL_SSL_KEY_FILE;
const sslFilesProvided = Boolean(ssl_ca_file && ssl_cert_file && ssl_key_file);
if (rejectUnauthorized !== undefined || sslFilesProvided) {
opts.ssl = {
...(rejectUnauthorized !== undefined && { rejectUnauthorized: rejectUnauthorized === '0' ? false : true }),
...(ssl_ca_file && { ca: fs.readFileSync(ssl_ca_file) }),
...(ssl_cert_file && { cert: fs.readFileSync(ssl_cert_file) }),
...(ssl_key_file && { key: fs.readFileSync(ssl_key_file) })
};
}
const sql = {
'7006': [
@@ -88,7 +103,7 @@ const sql = {
'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: [
'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',
@@ -140,7 +155,7 @@ const sql = {
'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: [
'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\'',
@@ -160,7 +175,7 @@ const sql = {
'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: [
'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)',
@@ -190,7 +205,33 @@ const sql = {
'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'
'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)',
'ALTER TABLE sip_gateways ADD COLUMN use_sips_scheme BOOLEAN NOT NULL DEFAULT 0',
],
9002: [
'ALTER TABLE system_information ADD COLUMN private_network_cidr VARCHAR(8192)',
'ALTER TABLE system_information ADD COLUMN log_level ENUM(\'info\', \'debug\') NOT NULL DEFAULT \'info\'',
'ALTER TABLE accounts ADD COLUMN enable_debug_log BOOLEAN NOT NULL DEFAULT false',
'ALTER TABLE google_custom_voices ADD COLUMN use_voice_cloning_key BOOLEAN DEFAULT false',
'ALTER TABLE google_custom_voices ADD COLUMN voice_cloning_key MEDIUMTEXT',
],
9003: [
'ALTER TABLE google_custom_voices ADD COLUMN voice_cloning_key MEDIUMTEXT',
'ALTER TABLE google_custom_voices ADD COLUMN use_voice_cloning_key BOOLEAN DEFAULT false',
'ALTER TABLE voip_carriers ADD COLUMN dtmf_type ENUM(\'rfc2833\',\'tones\',\'info\') NOT NULL DEFAULT \'rfc2833\'',
'ALTER TABLE voip_carriers ADD COLUMN outbound_sip_proxy VARCHAR(255)',
],
9004: [
'ALTER TABLE applications ADD COLUMN env_vars TEXT',
],
9005: [
'UPDATE applications SET speech_synthesis_voice = \'en-US-Standard-C\' WHERE speech_synthesis_voice IS NULL AND speech_synthesis_vendor = \'google\' AND speech_synthesis_language = \'en-US\'',
'ALTER TABLE applications MODIFY COLUMN speech_synthesis_voice VARCHAR(255) DEFAULT \'en-US-Standard-C\''
]
};
@@ -223,6 +264,9 @@ const doIt = async() => {
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']);
if (val < 9002) upgrades.push(...sql['9002']);
if (val < 9003) upgrades.push(...sql['9003']);
// perform all upgrades
logger.info({upgrades}, 'applying schema upgrades..');

View File

@@ -35,8 +35,8 @@ function makeStrategy(logger) {
debug(err);
logger.info({err}, 'Error checking redis for jwt');
}
const { user_sid, service_provider_sid, account_sid, email, name, scope, permissions } = decoded;
const { user_sid, service_provider_sid, account_sid, email,
name, scope, permissions, is_view_only } = decoded;
const user = {
service_provider_sid,
account_sid,
@@ -45,6 +45,7 @@ function makeStrategy(logger) {
email,
name,
permissions,
is_view_only,
hasScope: (s) => s === scope,
hasAdminAuth: scope === 'admin',
hasServiceProviderAuth: scope === 'service_provider',

View File

@@ -13,6 +13,8 @@ const {
deleteKey,
incrKey,
client: redisClient,
listConferences,
getCallCount
} = require('@jambonz/realtimedb-helpers')({}, logger);
module.exports = {
@@ -27,5 +29,7 @@ module.exports = {
retrieveKey,
deleteKey,
redisClient,
incrKey
incrKey,
listConferences,
getCallCount
};

View File

@@ -2,6 +2,6 @@ const opts = {
level: process.env.JAMBONES_LOGLEVEL || 'info'
};
const pino = require('pino');
const logger = pino(opts, pino.destination(1, {sync: false}));
const logger = pino(opts);
module.exports = logger;

View File

@@ -1,4 +1,5 @@
const logger = require('./logger');
const {UserPermissionError} = require('./utils/errors');
function delayLoginMiddleware(req, res, next) {
if (req.path.includes('/login') || req.path.includes('/signin')) {
@@ -27,6 +28,26 @@ function delayLoginMiddleware(req, res, next) {
next();
}
function verifyViewOnlyUser(req, res, next) {
// Skip check for GET requests
if (req.method === 'GET') {
return next();
}
// current user is changing their password which shuould be allowed
if (req.body?.old_password && req.body?.new_password) {
return next();
}
// Check if user is read-only
if (req.user && !!req.user.is_view_only) {
const upError = new UserPermissionError('User has view-only access');
upError.status = 403;
throw upError;
}
next();
}
module.exports = {
delayLoginMiddleware
delayLoginMiddleware,
verifyViewOnlyUser
};

View File

@@ -56,9 +56,13 @@ AND effective_end_date IS NULL
AND pending = 0`;
const extractBucketCredential = (obj) => {
const {bucket_credential} = obj;
if (bucket_credential) {
obj.bucket_credential = JSON.parse(decrypt(bucket_credential));
try {
const {bucket_credential} = obj;
if (bucket_credential) {
obj.bucket_credential = JSON.parse(decrypt(bucket_credential));
}
} catch (error) {
console.error('Error while decrypting data', error);
}
};
@@ -199,8 +203,9 @@ 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;
}

View File

@@ -36,20 +36,98 @@ class Application extends Model {
super();
}
/**
* list all applications - for all service providers, for one service provider, or for one account
*/
static retrieveAll(service_provider_sid, account_sid) {
let sql = retrieveSql;
static _criteriaBuilder(obj, args) {
let sql = '';
if (obj.account_sid) {
sql += ' AND app.account_sid = ?';
args.push(obj.account_sid);
}
if (obj.service_provider_sid) {
sql += ' AND app.account_sid in (SELECT account_sid from accounts WHERE service_provider_sid = ?)';
args.push(obj.service_provider_sid);
}
if (obj.name) {
sql += ' AND app.name LIKE ?';
args.push(`%${obj.name}%`);
}
return sql;
}
static countAll(obj) {
let sql = 'SELECT COUNT(*) AS count FROM applications app WHERE 1 = 1';
const args = [];
if (account_sid) {
sql = `${sql} WHERE app.account_sid = ?`;
args.push(account_sid);
}
else if (service_provider_sid) {
sql = `${sql} WHERE account_sid in (SELECT account_sid from accounts WHERE service_provider_sid = ?)`;
args.push(service_provider_sid);
sql += Application._criteriaBuilder(obj, args);
return new Promise((resolve, reject) => {
getMysqlConnection((err, conn) => {
if (err) return reject(err);
conn.query({sql}, args, (err, results) => {
conn.release();
if (err) return reject(err);
resolve(results[0].count);
});
});
});
}
/**
* list all applications - for all service providers, for one service provider, or for one account,
* or by an optional name
*/
static retrieveAll(obj) {
const { page, page_size = 50 } = obj || {};
// If pagination is requested, first get the application IDs
if (page !== null && page !== undefined) {
let idSql = 'SELECT application_sid, name FROM applications app WHERE 1 = 1';
const idArgs = [];
idSql += Application._criteriaBuilder(obj, idArgs);
idSql += ' ORDER BY app.name';
const limit = Number(page_size);
const offset = Number(page > 0 ? (page - 1) : page) * limit;
idSql += ' LIMIT ? OFFSET ?';
idArgs.push(limit);
idArgs.push(offset);
return new Promise((resolve, reject) => {
getMysqlConnection((err, conn) => {
if (err) return reject(err);
// Get paginated application IDs
conn.query(idSql, idArgs, (err, idResults) => {
if (err) {
conn.release();
return reject(err);
}
if (idResults.length === 0) {
conn.release();
return resolve([]);
}
// Get full data for these applications
const appIds = idResults.map((row) => row.application_sid);
const placeholders = appIds.map(() => '?').join(',');
const fullSql = `${retrieveSql}
WHERE app.application_sid IN (${placeholders}) ORDER BY app.name`;
conn.query({sql: fullSql, nestTables: true}, appIds, (err, results) => {
conn.release();
if (err) return reject(err);
const r = transmogrifyResults(results);
resolve(r);
});
});
});
});
}
// No pagination - use original query
let sql = retrieveSql + ' WHERE 1 = 1';
const args = [];
sql += Application._criteriaBuilder(obj, args);
sql += ' ORDER BY app.application_sid';
return new Promise((resolve, reject) => {
getMysqlConnection((err, conn) => {
if (err) return reject(err);

View File

@@ -13,9 +13,9 @@ class Client extends Model {
}
static async retrieveAllByServiceProviderSid(service_provider_sid) {
const sql = `SELECT c.client_sid, c.account_sid, c.is_active, c.username, c.hashed_password
const sql = `SELECT c.client_sid, c.account_sid, c.is_active, c.username, c.password
FROM ${this.table} AS c LEFT JOIN accounts AS acc ON c.account_sid = acc.account_sid
LEFT JOIN service_providers AS sp ON sp.service_provider_sid = accs.service_provider_sid
LEFT JOIN service_providers AS sp ON sp.service_provider_sid = acc.service_provider_sid
WHERE sp.service_provider_sid = ?`;
const [rows] = await promisePool.query(sql, service_provider_sid);
return rows;

45
lib/models/permissions.js Normal file
View File

@@ -0,0 +1,45 @@
const Model = require('./model');
const {promisePool} = require('../db');
const sqlAll = `
SELECT * from permissions
`;
const sqlByName = `
SELECT * from permissions where name = ?
`;
class Permissions extends Model {
constructor() {
super();
}
static async retrieveAll() {
const [rows] = await promisePool.query(sqlAll);
return rows;
}
static async retrieveByName(name) {
const [rows] = await promisePool.query(sqlByName, [name]);
return rows;
}
}
Permissions.table = 'permissions';
Permissions.fields = [
{
name: 'permission_sid',
type: 'string',
primaryKey: true
},
{
name: 'name',
type: 'string',
required: true
},
{
name: 'description',
type: 'string',
required: true
}
];
module.exports = Permissions;

View File

@@ -26,6 +26,49 @@ class PhoneNumber extends Model {
return rows;
}
static _criteriaBuilder(obj, params) {
let sql = '';
if (obj.service_provider_sid) {
sql += ' AND account_sid IN (SELECT account_sid FROM accounts WHERE service_provider_sid = ?)';
params.push(obj.service_provider_sid);
}
if (obj.account_sid) {
sql += ' AND account_sid = ?';
params.push(obj.account_sid);
}
if (obj.filter) {
sql += ' AND number LIKE ?';
params.push(`%${obj.filter}%`);
}
return sql;
}
static async countAll(obj) {
let sql = 'SELECT COUNT(*) AS count FROM phone_numbers WHERE 1 = 1';
const args = [];
sql += PhoneNumber._criteriaBuilder(obj, args);
const [rows] = await promisePool.query(sql, args);
return rows[0].count;
}
static async retrieveAllByCriteria(obj) {
let sql = 'SELECT * FROM phone_numbers WHERE 1=1';
const params = [];
const { page, page_size = 50 } = obj || {};
sql += PhoneNumber._criteriaBuilder(obj, params);
sql += ' ORDER BY number';
if (page !== null && page !== undefined) {
const limit = Number(page_size);
const offset = Number(page > 0 ? (page - 1) : page) * limit;
sql += ' LIMIT ? OFFSET ?';
params.push(limit);
params.push(offset);
}
const [rows] = await promisePool.query(sql, params);
return rows;
}
/**
* retrieve a phone number
*/

View File

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

View File

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

View File

@@ -0,0 +1,53 @@
const Model = require('./model');
const {promisePool} = require('../db');
const sqlAll = `
SELECT * from user_permissions
`;
const sqlByUserIdPermissionSid = `
SELECT * from user_permissions where user_sid = ? and permission_sid = ?
`;
const sqlByUserId = `
SELECT * from user_permissions where user_sid = ?
`;
class UserPermissions extends Model {
constructor() {
super();
}
static async retrieveAll() {
const [rows] = await promisePool.query(sqlAll);
return rows;
}
static async retrieveByUserIdPermissionSid(user_sid, permission_sid) {
const [rows] = await promisePool.query(sqlByUserIdPermissionSid, [user_sid, permission_sid]);
return rows;
}
static async retrieveByUserId(user_sid) {
const [rows] = await promisePool.query(sqlByUserId, [user_sid]);
return rows;
}
}
UserPermissions.table = 'user_permissions';
UserPermissions.fields = [
{
name: 'user_permissions_sid',
type: 'string',
primaryKey: true
},
{
name: 'user_sid',
type: 'string',
required: true
},
{
name: 'permission_sid',
type: 'string',
required: true
}
];
module.exports = UserPermissions;

View File

@@ -8,6 +8,57 @@ class VoipCarrier extends Model {
constructor() {
super();
}
static _criteriaBuilder(obj, args) {
let sql = '';
if (obj.account_sid) {
// carrier belong to an account when
// 1. account_sid is set
// 2. account_sid is null and service_provider_sid matches the account's service_provider_sid
sql += ` AND (vc.account_sid = ? OR
(vc.account_sid IS NULL AND vc.service_provider_sid IN
(SELECT service_provider_sid FROM accounts WHERE account_sid = ?))
)`;
args.push(obj.account_sid);
args.push(obj.account_sid);
}
if (obj.service_provider_sid) {
sql += ' AND vc.service_provider_sid = ?';
args.push(obj.service_provider_sid);
}
if (obj.name) {
sql += ' AND vc.name LIKE ?';
args.push(`%${obj.name}%`);
}
return sql;
}
static async countAll(obj) {
let sql = 'SELECT COUNT(*) AS count FROM voip_carriers vc WHERE 1 = 1';
const args = [];
sql += VoipCarrier._criteriaBuilder(obj, args);
const [rows] = await promisePool.query(sql, args);
return rows[0].count;
}
static async retrieveByCriteria(obj) {
let sql = 'SELECT * from voip_carriers vc WHERE 1 =1';
const args = [];
sql += VoipCarrier._criteriaBuilder(obj, args);
if (obj.page !== null && obj.page !== undefined) {
const limit = Number(obj.page_size || 50);
const offset = (Number(obj.page) - 1) * limit;
sql += ' LIMIT ? OFFSET ?';
args.push(limit, offset);
}
const [rows] = await promisePool.query(sql, args);
if (rows) {
rows.map((r) => r.register_status = JSON.parse(r.register_status || '{}'));
}
return rows;
}
static async retrieveAll(account_sid) {
if (!account_sid) return super.retrieveAll();
const [rows] = await promisePool.query(retrieveSql, account_sid);
@@ -61,6 +112,10 @@ VoipCarrier.fields = [
name: 'requires_register',
type: 'number'
},
{
name: 'register_use_tls',
type: 'number'
},
{
name: 'register_username',
type: 'string'
@@ -132,6 +187,14 @@ VoipCarrier.fields = [
{
name: 'register_status',
type: 'string'
},
{
name: 'dtmf_type',
type: 'string'
},
{
name: 'sip_proxy',
type: 'string'
}
];

View File

@@ -1,28 +1,58 @@
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) {
const blockID = uuidv4().replace(/-/g, '');
this.blocks.push(blockID);
try {
await this.blockBlobClient.stageBlock(blockID, chunk, chunk.length);
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();
} catch (error) {
callback(error);
}
}
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

View File

@@ -1,5 +1,6 @@
const { Storage } = require('@google-cloud/storage');
const { Writable } = require('stream');
const streamBuffers = require('stream-buffers');
class GoogleStorageUploadStream extends Writable {
@@ -12,18 +13,38 @@ class GoogleStorageUploadStream extends Writable {
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.logger.info('Google storage Upload completed.');
this._addMetadata();
});
}
_write(chunk, encoding, callback) {
this.writeStream.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);
}
@@ -33,7 +54,7 @@ class GoogleStorageUploadStream extends Writable {
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');
this.logger.error(err, 'Google storage An error occurred while setting metadata');
}
}
}

View File

@@ -11,7 +11,7 @@ class S3MultipartUploadStream extends Writable {
super(opts);
this.logger = logger;
this.bucketName = opts.bucketName;
this.objectKey = opts.objectKey;
this.objectKey = opts.Key;
this.uploadId = null;
this.partNumber = 1;
this.multipartETags = [];

View File

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

View File

@@ -5,7 +5,7 @@ const S3MultipartUploadStream = require('./s3-multipart-upload-stream');
const getUploader = (key, metadata, bucket_credential, logger) => {
const uploaderOpts = {
bucketName: bucket_credential.name,
objectKey: key,
Key: key,
metadata
};
try {
@@ -26,7 +26,7 @@ const getUploader = (key, metadata, bucket_credential, logger) => {
accessKeyId: bucket_credential.access_key_id,
secretAccessKey: bucket_credential.secret_access_key,
},
region: 'us-east-1',
region: bucket_credential.region || 'us-east-1',
forcePathStyle: true
};
return new S3MultipartUploadStream(logger, uploaderOpts);

View File

@@ -1,6 +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');
const Application = require('../../models/application');
@@ -19,11 +18,13 @@ const {
parseCallSid,
enableSubspace,
disableSubspace,
parseVoipCarrierSid
parseVoipCarrierSid,
hasValue,
} = require('./utils');
const short = require('short-uuid');
const VoipCarrier = require('../../models/voip-carrier');
const { encrypt } = require('../../utils/encrypt-decrypt');
const { encrypt, obscureBucketCredentialsSensitiveData,
isObscureKey, decrypt } = require('../../utils/encrypt-decrypt');
const { testS3Storage, testGoogleStorage, testAzureStorage } = require('../../utils/storage-utils');
const translator = short();
@@ -42,7 +43,7 @@ const getFsUrl = async(logger, retrieveSet, setName) => {
return ;
}
const f = fs[idx++ % fs.length];
logger.info({fs}, `feature servers available for createCall API request, selecting ${f}`);
logger.debug({fs}, `feature servers available for createCall API request, selecting ${f}`);
return `${f}/v1/createCall`;
} catch (err) {
logger.error({err}, 'getFsUrl: error retreving feature servers from redis');
@@ -92,8 +93,34 @@ router.get('/:sid/Applications', async(req, res) => {
try {
const account_sid = parseAccountSid(req);
await validateRequest(req, account_sid);
const results = await Application.retrieveAll(null, account_sid);
res.status(200).json(results);
const {page, page_size, name} = req.query || {};
const isPaginationRequest = page !== null && page !== undefined;
let results = [];
let total = 0;
if (isPaginationRequest) {
total = await Application.countAll({account_sid, name});
results = await Application.retrieveAll({
account_sid, name, page, page_size
});
} else {
results = await Application.retrieveAll({account_sid});
}
const ret = results.map((a) => {
if (a.env_vars) {
a.env_vars = JSON.parse(decrypt(a.env_vars));
return a;
} else {
return a;
}
});
const body = isPaginationRequest ? {
total,
page: Number(page),
page_size: Number(page_size),
data: ret
} : ret;
res.status(200).json(body);
} catch (err) {
sysError(logger, res, err);
}
@@ -221,7 +248,8 @@ router.get('/:sid/RegisteredSipUsers/:client', async(req, res) => {
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'
registered_status: user ? 'active' : 'inactive',
proxy: user ? user.proxy : null
});
} catch (err) {
sysError(logger, res, err);
@@ -265,7 +293,9 @@ function validateUpdateCall(opts) {
'sip_request',
'record',
'tag',
'dtmf'
'dtmf',
'conferenceParticipantAction',
'dub'
]
.reduce((acc, prop) => (opts[prop] ? ++acc : acc), 0);
@@ -316,6 +346,19 @@ function validateUpdateCall(opts) {
if (opts.tag && (typeof opts.tag !== 'object' || Array.isArray(opts.tag) || opts.tag === null)) {
throw new DbErrorBadRequest('invalid tag data');
}
if (opts.conferenceParticipantAction) {
if (!['tag', 'untag', 'coach', 'uncoach', 'mute', 'unmute', 'hold', 'unhold']
.includes(opts.conferenceParticipantAction.action)) {
throw new DbErrorBadRequest(
`conferenceParticipantAction invalid action property ${opts.conferenceParticipantAction.action}`);
}
if ('tag' == opts.conferenceParticipantAction.action && !opts.conferenceParticipantAction.tag) {
throw new DbErrorBadRequest('conferenceParticipantAction requires tag property when action is \'tag\'');
}
if ('coach' == opts.conferenceParticipantAction.action && !opts.conferenceParticipantAction.tag) {
throw new DbErrorBadRequest('conferenceParticipantAction requires tag property when action is \'coach\'');
}
}
}
function validateTo(to) {
@@ -340,7 +383,10 @@ async function validateCreateCall(logger, sid, req) {
const {lookupAppBySid} = req.app.locals;
const obj = req.body;
if (req.user.account_sid !== sid) throw new DbErrorBadRequest(`unauthorized createCall request for account ${sid}`);
if (req.user.hasServiceProviderAuth ||
req.user.hasAccountAuth && req.user.account_sid !== sid) {
throw new DbErrorBadRequest(`unauthorized createCall request for account ${sid}`);
}
obj.account_sid = sid;
if (!obj.from) throw new DbErrorBadRequest('missing from parameter');
@@ -540,9 +586,12 @@ router.get('/:sid', async(req, res) => {
const account_sid = parseAccountSid(req);
await validateRequest(req, account_sid);
const service_provider_sid = req.user.hasServiceProviderAuth ? req.user.service_provider_sid : null;
const results = await Account.retrieve(account_sid, service_provider_sid);
if (results.length === 0) return res.status(404).end();
return res.status(200).json(results[0]);
const [result] = await Account.retrieve(account_sid, service_provider_sid) || [];
if (!result) return res.status(404).end();
result.bucket_credential = obscureBucketCredentialsSensitiveData(result.bucket_credential);
return res.status(200).json(result);
}
catch (err) {
sysError(logger, res, err);
@@ -624,18 +673,20 @@ router.delete('/:sid/SubspaceTeleport', async(req, res) => {
}
});
function encryptBucketCredential(obj) {
if (!obj.bucket_credential) return;
function encryptBucketCredential(obj, storedCredentials = {}) {
if (!hasValue(obj?.bucket_credential)) return;
const {
vendor,
region,
name,
access_key_id,
secret_access_key,
tags,
endpoint,
} = obj.bucket_credential;
let {
secret_access_key,
service_key,
connection_string,
endpoint
} = obj.bucket_credential;
switch (vendor) {
@@ -644,6 +695,9 @@ function encryptBucketCredential(obj) {
assert(secret_access_key, 'invalid aws S3 bucket credential: secret_access_key is required');
assert(name, 'invalid aws bucket name: name is required');
assert(region, 'invalid aws bucket region: region is required');
if (isObscureKey(obj.bucket_credential) && hasValue(storedCredentials)) {
secret_access_key = storedCredentials.secret_access_key;
}
const awsData = JSON.stringify({vendor, region, name, access_key_id,
secret_access_key, tags});
obj.bucket_credential = encrypt(awsData);
@@ -653,18 +707,29 @@ function encryptBucketCredential(obj) {
assert(secret_access_key, 'invalid aws S3 bucket credential: secret_access_key is required');
assert(name, 'invalid aws bucket name: name is required');
assert(endpoint, 'invalid endpoint uri: endpoint is required');
if (isObscureKey(obj.bucket_credential) && hasValue(storedCredentials)) {
secret_access_key = storedCredentials.secret_access_key;
}
const s3Data = JSON.stringify({vendor, endpoint, name, access_key_id,
secret_access_key, tags});
secret_access_key, tags,
...(region && {region})
});
obj.bucket_credential = encrypt(s3Data);
break;
case 'google':
assert(service_key, 'invalid google cloud storage credential: service_key is required');
if (isObscureKey(obj.bucket_credential) && hasValue(storedCredentials)) {
service_key = storedCredentials.service_key;
}
const googleData = JSON.stringify({vendor, name, service_key, tags});
obj.bucket_credential = encrypt(googleData);
break;
case 'azure':
assert(name, 'invalid azure container name: name is required');
assert(connection_string, 'invalid azure cloud storage credential: connection_string is required');
if (isObscureKey(obj.bucket_credential) && hasValue(storedCredentials)) {
connection_string = storedCredentials.connection_string;
}
const azureData = JSON.stringify({vendor, name, connection_string, tags});
obj.bucket_credential = encrypt(azureData);
break;
@@ -722,7 +787,17 @@ router.put('/:sid', async(req, res) => {
delete obj.registration_hook;
delete obj.queue_event_hook;
encryptBucketCredential(obj);
let storedBucketCredentials = {};
if (isObscureKey(obj?.bucket_credential)) {
const [account] = await Account.retrieve(sid) || [];
/* to avoid overwriting valid credentials with the obscured secret,
* that the frontend might send, we pass the stored account bucket credentials
* in the case it is a obscured key, we replace it with the stored one
*/
storedBucketCredentials = account.bucket_credential;
}
encryptBucketCredential(obj, storedBucketCredentials);
const rowsAffected = await Account.update(sid, obj);
if (rowsAffected === 0) {
@@ -822,6 +897,13 @@ router.post('/:sid/BucketCredentialTest', async(req, res) => {
try {
const account_sid = parseAccountSid(req);
await validateRequest(req, account_sid);
/* if the req.body bucket credentials contain an obscured key, replace with stored account.bucket_credential */
if (isObscureKey(req.body)) {
const service_provider_sid = req.user.hasServiceProviderAuth ? req.user.service_provider_sid : null;
const [account] = await Account.retrieve(account_sid, service_provider_sid) || [];
if (!account) return res.status(404).end();
req.body = account.bucket_credential;
}
const {vendor, name, region, access_key_id, secret_access_key, service_key, connection_string, endpoint} = req.body;
const ret = {
status: 'not tested'
@@ -890,24 +972,25 @@ router.post('/:sid/Calls', async(req, res) => {
await validateCreateCall(logger, sid, req);
updateLastUsed(logger, sid, req).catch((err) => {});
request({
url: serviceUrl,
const response = await fetch(serviceUrl, {
method: 'POST',
json: true,
body: Object.assign(req.body, {account_sid: sid})
}, (err, response, body) => {
if (err) {
logger.error(err, `Error sending createCall POST to ${serviceUrl}`);
return res.sendStatus(500);
}
if (response.statusCode !== 201) {
logger.error({statusCode: response.statusCode}, `Non-success response returned by createCall ${serviceUrl}`);
return res.sendStatus(500);
}
return res.status(201).json(body);
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(Object.assign(req.body, {account_sid: sid}))
});
if (!response.ok) {
logger.error(`Error sending createCall POST to ${serviceUrl}`);
return res.sendStatus(500);
}
if (response.status !== 201) {
logger.error(`Non-success response returned by createCall ${serviceUrl}`);
return res.sendStatus(500);
}
const body = await response.json();
return res.status(201).json(body);
} catch (err) {
sysError(logger, res, err);
}
@@ -998,22 +1081,54 @@ const updateCall = async(req, res) => {
await validateRequest(req, accountSid);
const callSid = parseCallSid(req);
validateUpdateCall(req.body);
updateLastUsed(logger, accountSid, req).catch((err) => {});
const call = await retrieveCall(accountSid, callSid);
if (call) {
const url = `${call.serviceUrl}/${process.env.JAMBONES_API_VERSION || 'v1'}/updateCall/${callSid}`;
logger.debug({call, url, payload: req.body}, `updateCall: retrieved call info for call sid ${callSid}`);
request({
url: url,
const response = await fetch(url, {
method: 'POST',
json: true,
body: req.body
}).pipe(res);
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(req.body)
});
if (!response.ok) {
try {
const text = await response.text();
logger.error(`Error sending updateCall POST to ${url}, status: ${response.status} body: ${text}`);
// Try to parse as JSON if there's content
if (text) {
try {
const body = JSON.parse(text);
return res.status(response.status).json(body);
} catch {
// Not valid JSON
return res.status(response.status).send(text);
}
}
return res.sendStatus(response.status);
} catch (err) {
logger.error({err}, `updateCall: error reading response from ${url}`);
return res.sendStatus(response.status);
}
}
if (response.status === 200) {
// feature server return json for sip_request command
// with 200 OK
const body = await response.json();
return res.status(200).json(body);
} else {
// rest commander returns 202 Accepted for all other commands
return res.sendStatus(response.status);
}
}
else {
logger.debug(`updateCall: call not found for call sid ${callSid}`);
res.sendStatus(404);
}
updateLastUsed(logger, accountSid, req).catch((err) => {});
} catch (err) {
sysError(logger, res, err);
}
@@ -1040,7 +1155,9 @@ router.post('/:sid/Messages', async(req, res) => {
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);
if (!serviceUrl) {
return res.status(480).json({msg: 'no available feature servers at this time'});
}
await validateCreateMessage(logger, account_sid, req);
const payload = {
@@ -1050,22 +1167,19 @@ router.post('/:sid/Messages', async(req, res) => {
};
logger.debug({payload}, `sending createMessage API request to to ${serviceUrl}`);
updateLastUsed(logger, account_sid, req).catch(() => {});
request({
url: serviceUrl,
const response = await fetch(serviceUrl, {
method: 'POST',
json: true,
body: payload
}, (err, response, body) => {
if (err) {
logger.error(err, `Error sending createMessage POST to ${serviceUrl}`);
return res.sendStatus(500);
}
if (response.statusCode !== 200) {
logger.error({statusCode: response.statusCode}, `Non-success response returned by createMessage ${serviceUrl}`);
return body ? res.status(response.statusCode).json(body) : res.sendStatus(response.statusCode);
}
res.status(201).json(body);
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
if (!response.ok) {
logger.error(`Error sending createMessage POST to ${serviceUrl}`);
return res.sendStatus(500);
}
const body = await response.json();
return res.status(response.status).json(body);
} catch (err) {
sysError(logger, res, err);
}
@@ -1089,5 +1203,40 @@ router.get('/:sid/Queues', async(req, res) => {
}
});
/**
* retrieve info for a list of conferences under an account
*/
router.get('/:sid/Conferences', async(req, res) => {
const {logger, listConferences} = req.app.locals;
try {
const accountSid = parseAccountSid(req);
await validateRequest(req, accountSid);
const conferences = await listConferences(accountSid);
logger.debug(`retrieved ${conferences.length} queues for account sid ${accountSid}`);
res.status(200).json(conferences.map((c) => c.split(':').pop()));
updateLastUsed(logger, accountSid, req).catch((err) => {});
} catch (err) {
sysError(logger, res, err);
}
});
/**
* retrieve counts of calls under an account
*/
router.get('/:sid/CallCount', async(req, res) => {
const {logger, getCallCount} = req.app.locals;
try {
const accountSid = parseAccountSid(req);
await validateRequest(req, accountSid);
const count = await getCallCount(accountSid);
count.outbound = Number(count.outbound);
count.inbound = Number(count.inbound);
logger.debug(`retrieved, outbound: ${count.outbound}, inbound: ${count.inbound}, for account sid ${accountSid}`);
res.status(200).json(count);
updateLastUsed(logger, accountSid, req).catch((err) => {});
} catch (err) {
sysError(logger, res, err);
}
});
module.exports = router;

View File

@@ -16,7 +16,7 @@ router.get('/', async(req, res) => {
const service_provider_sid = account_sid ? null : parseServiceProviderSid(req.originalUrl);
const {page, count, alert_type, days, start, end} = 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');
if (!count || count > 500) throw new DbErrorBadRequest('missing or invalid "count" query arg');
if (account_sid) {
const data = await queryAlerts({

47
lib/routes/api/appenv.js Normal file
View File

@@ -0,0 +1,47 @@
const router = require('express').Router();
const sysError = require('../error');
const { fetchAppEnvSchema, validateAppEnvSchema } = require('../../utils/appenv_utils');
const URL = require('url').URL;
const isValidUrl = (s) => {
const protocols = ['https:', 'http:', 'ws:', 'wss:'];
try {
const url = new URL(s);
if (protocols.includes(url.protocol)) {
return true;
}
else {
return false;
}
} catch (err) {
return false;
}
};
/* get appenv schema for endpoint */
router.get('/', async(req, res) => {
const logger = req.app.locals.logger;
const url = req.query.url;
if (!isValidUrl(url)) {
sysError(logger, res, 'Invalid URL');
} else {
try {
const appenv = await fetchAppEnvSchema(logger, url);
if (appenv && validateAppEnvSchema(appenv)) {
return res.status(200).json(appenv);
} else if (appenv) {
return res.status(400).json({
msg: 'Invalid appenv schema',
});
} else {
return res.status(204).end(); //No appenv returned from url, normal scenario
}
}
catch (err) {
sysError(logger, res, err);
}
}
});
module.exports = router;

View File

@@ -7,11 +7,13 @@ const {promisePool} = require('../../db');
const decorate = require('./decorate');
const sysError = require('../error');
const { validate } = require('@jambonz/verb-specifications');
const { parseApplicationSid } = require('./utils');
const { parseApplicationSid, isInvalidUrl } = require('./utils');
const preconditions = {
'add': validateAdd,
'update': validateUpdate
};
const { fetchAppEnvSchema, validateAppEnvData } = require('../../utils/appenv_utils');
const {decrypt, encrypt} = require('../../utils/encrypt-decrypt');
const validateRequest = async(req, account_sid) => {
try {
@@ -62,6 +64,14 @@ async function validateAdd(req) {
if (req.body.call_status_hook && typeof req.body.call_hook !== 'object') {
throw new DbErrorBadRequest('\'call_status_hook\' must be an object when adding an application');
}
let urlError = await isInvalidUrl(req.body.call_hook.url);
if (urlError) {
throw new DbErrorBadRequest(`call_hook ${urlError}`);
}
urlError = await isInvalidUrl(req.body.call_status_hook.url);
if (urlError) {
throw new DbErrorBadRequest(`call_status_hook ${urlError}`);
}
}
async function validateUpdate(req, sid) {
@@ -142,6 +152,16 @@ router.post('/', async(req, res) => {
throw new DbErrorBadRequest(err);
}
}
// validate env_vars data if required
if (obj['env_vars']) {
const appenvschema = await fetchAppEnvSchema(logger, req.body.call_hook.url);
const errors = await validateAppEnvData(appenvschema, obj['env_vars']);
if (errors) {
throw new DbErrorBadRequest(errors);
} else {
obj['env_vars'] = encrypt(JSON.stringify(obj['env_vars']));
}
}
const uuid = await Application.make(obj);
res.status(201).json({sid: uuid});
@@ -153,11 +173,34 @@ router.post('/', async(req, res) => {
/* list */
router.get('/', async(req, res) => {
const logger = req.app.locals.logger;
const {page, page_size, name} = req.query || {};
const isPaginationRequest = page !== null && page !== undefined;
try {
const service_provider_sid = req.user.hasServiceProviderAuth ? req.user.service_provider_sid : null;
const account_sid = req.user.hasAccountAuth ? req.user.account_sid : null;
const results = await Application.retrieveAll(service_provider_sid, account_sid);
res.status(200).json(results);
let results = [];
let total = 0;
if (isPaginationRequest) {
total = await Application.countAll({service_provider_sid, account_sid, name});
}
results = await Application.retrieveAll({
service_provider_sid, account_sid, name, page, page_size
});
const ret = results.map((a) => {
if (a.env_vars) {
a.env_vars = JSON.parse(decrypt(a.env_vars));
return a;
} else {
return a;
}
});
const body = isPaginationRequest ? {
total,
page: Number(page),
page_size: Number(page_size),
data: ret
} : ret;
res.status(200).json(body);
} catch (err) {
sysError(logger, res, err);
}
@@ -173,6 +216,9 @@ router.get('/:sid', async(req, res) => {
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);
if (results[0].env_vars) {
results[0].env_vars = JSON.parse(decrypt(results[0].env_vars));
}
return res.status(200).json(results[0]);
}
catch (err) {
@@ -227,6 +273,8 @@ router.put('/:sid', async(req, res) => {
try {
const sid = parseApplicationSid(req);
await validateUpdate(req, sid);
const service_provider_sid = req.user.hasServiceProviderAuth ? req.user.service_provider_sid : null;
const account_sid = req.user.hasAccountAuth ? req.user.account_sid : null;
// create webhooks if provided
const obj = Object.assign({}, req.body);
@@ -258,6 +306,21 @@ router.put('/:sid', async(req, res) => {
}
}
// validate env_vars data if required
if (obj['env_vars']) {
const applications = await Application.retrieve(sid, service_provider_sid, account_sid);
const call_hook_url = req.body.call_hook ?
(req.body.call_hook.url || req.body.call_hook) :
applications[0].call_hook.url;
const appenvschema = await fetchAppEnvSchema(logger, call_hook_url);
const errors = await validateAppEnvData(appenvschema, obj['env_vars']);
if (errors) {
throw new DbErrorBadRequest(errors);
} else {
obj['env_vars'] = encrypt(JSON.stringify(obj['env_vars']));
}
}
const rowsAffected = await Application.update(sid, obj);
if (rowsAffected === 0) {
return res.status(404).end();

View File

@@ -52,20 +52,21 @@ router.post('/', async(req, res) => {
let obj;
try {
if (!email || !validateEmail(email)) {
logger.info({email}, 'Bad POST to /forgot-password is missing email or invalid email');
return res.status(400).json({error: 'invalid or missing email'});
}
const [r] = await promisePool.query({sql, nestTables: true}, email);
if (0 === r.length) {
logger.info('user not found');
logger.info(`user not found: ${email}`);
return res.status(400).json({error: 'failed to reset your password'});
}
obj = r[0];
if (!obj.user.is_active) {
logger.info(obj.user.name, 'user is inactive');
logger.info({user: obj.user.name, obj}, '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');
logger.info({account_sid: obj.acc.account_sid, obj}, 'account is inactive');
return res.status(400).json({error: 'failed to reset your password'});
}
res.sendStatus(204);

View File

@@ -4,6 +4,9 @@ const SpeechCredential = require('../../models/speech-credential');
const decorate = require('./decorate');
const {DbErrorBadRequest, DbErrorForbidden} = require('../../utils/errors');
const sysError = require('../error');
const multer = require('multer');
const upload = multer({ dest: '/tmp/csv/' });
const fs = require('fs');
const validateCredentialPermission = async(req) => {
const credential = await SpeechCredential.retrieve(req.body.speech_credential_sid);
@@ -41,6 +44,33 @@ const preconditions = {
decorate(router, GoogleCustomVoice, ['add', 'retrieve', 'update', 'delete'], preconditions);
const voiceCloningKeySubString = (voice_cloning_key) => {
return voice_cloning_key ? voice_cloning_key.substring(0, 100) + '...' : undefined;
};
router.get('/: sid', async(req, res) => {
const logger = req.app.locals.logger;
try {
const {sid} = req.params;
const account_sid = req.user.account_sid;
const service_provider_sid = req.user.service_provider_sid;
const google_voice = await GoogleCustomVoice.retrieve(sid);
google_voice.voice_cloning_key = voiceCloningKeySubString(google_voice.voice_cloning_key);
if (!google_voice) {
return res.sendStatus(404);
}
if (req.user.hasScope('service_provider') && google_voice.service_provider_sid !== service_provider_sid ||
req.user.hasScope('account') && google_voice.account_sid !== account_sid) {
throw new DbErrorForbidden('Insufficient privileges');
}
return res.status(200).json(google_voice);
} catch (err) {
sysError(logger, res, err);
}
});
router.get('/', async(req, res) => {
const logger = req.app.locals.logger;
const account_sid = req.user.account_sid || req.query.account_sid;
@@ -67,7 +97,37 @@ router.get('/', async(req, res) => {
}
results = await GoogleCustomVoice.retrieveAllByLabel(service_provider_sid, account_sid, label);
}
res.status(200).json(results);
res.status(200).json(results.map((r) => {
r.voice_cloning_key = voiceCloningKeySubString(r.voice_cloning_key);
return r;
}));
} catch (err) {
sysError(logger, res, err);
}
});
router.post('/:sid/VoiceCloningKey', upload.single('file'), async(req, res) => {
const {logger} = req.app.locals;
const {sid} = req.params;
const account_sid = req.user.account_sid;
const service_provider_sid = req.user.service_provider_sid;
try {
const google_voice = await GoogleCustomVoice.retrieve(sid);
if (!google_voice) {
return res.sendStatus(404);
}
if (req.user.hasScope('service_provider') && google_voice.service_provider_sid !== service_provider_sid ||
req.user.hasScope('account') && google_voice.account_sid !== account_sid) {
throw new DbErrorForbidden('Insufficient privileges');
}
const voice_cloning_key = Buffer.from(fs.readFileSync(req.file.path)).toString();
await GoogleCustomVoice.update(sid, {
voice_cloning_key
});
fs.unlinkSync(req.file.path);
return res.sendStatus(204);
} catch (err) {
sysError(logger, res, err);
}

View File

@@ -39,6 +39,7 @@ api.use('/change-password', require('./change-password'));
api.use('/ActivationCode', require('./activation-code'));
api.use('/Availability', require('./availability'));
api.use('/AccountTest', require('./account-test'));
api.use('/AppEnv', require('./appenv'));
//api.use('/Products', require('./products'));
api.use('/Prices', require('./prices'));
api.use('/StripeCustomerId', require('./stripe-customer-id'));

View File

@@ -29,6 +29,9 @@ router.post('/', async(req, res) => {
return res.sendStatus(403);
}
logger.info({r}, 'successfully retrieved user account');
if (r[0].provider !== 'local') {
return res.sendStatus(403);
}
const maxLoginAttempts = process.env.LOGIN_ATTEMPTS_MAX_RETRIES || 6;
const loginAttempsBlocked = await retrieveKey(`login:${r[0].user_sid}`) >= maxLoginAttempts;
@@ -71,9 +74,13 @@ router.post('/', async(req, res) => {
obj.service_provider_sid = r[0].service_provider_sid;
obj.service_provider_name = service_provider[0].name;
}
// if there is only one permission and it is VIEW_ONLY, then the user is view only
// this is to prevent write operations on the API
const is_view_only = permissions.length === 1 && permissions.includes('VIEW_ONLY');
const payload = {
scope: obj.scope,
permissions,
is_view_only,
...(obj.service_provider_sid && {
service_provider_sid: obj.service_provider_sid,
service_provider_name: obj.service_provider_name
@@ -83,7 +90,8 @@ router.post('/', async(req, res) => {
account_name: obj.account_name,
service_provider_name: obj.service_provider_name
}),
user_sid: obj.user_sid
user_sid: obj.user_sid,
name: username
};
const expiresIn = parseInt(process.env.JWT_EXPIRES_IN || 60) * 60;

View File

@@ -13,6 +13,7 @@ const preconditions = {
};
const sysError = require('../error');
const { parsePhoneNumberSid } = require('./utils');
const hasWhitespace = (str) => /\s/.test(str);
/* check for required fields when adding */
@@ -28,6 +29,7 @@ async function validateAdd(req) {
}
if (!req.body.number) throw new DbErrorBadRequest('number is required');
if (hasWhitespace(req.body.number)) throw new DbErrorBadRequest('number cannot contain whitespace');
const formattedNumber = e164(req.body.number);
req.body.number = formattedNumber;
} catch (err) {
@@ -40,6 +42,10 @@ async function validateAdd(req) {
if (!result || result.length === 0) {
throw new DbErrorBadRequest(`voip_carrier not found for sid ${req.body.voip_carrier_sid}`);
}
const carrier = result[0];
if (carrier.account_sid && req.body.account_sid && req.body.account_sid !== carrier.account_sid) {
throw new DbErrorBadRequest('voip_carrier_sid does not belong to the account');
}
}
}
@@ -93,11 +99,35 @@ decorate(router, PhoneNumber, ['add', 'update', 'delete'], preconditions);
/* list */
router.get('/', async(req, res) => {
const logger = req.app.locals.logger;
const {service_provider_sid: query_service_provider_sid,
account_sid: query_account_sid, filter, page, page_size} = req.query;
const isPaginationRequest = page !== null && page !== undefined;
let service_provider_sid = null, account_sid = query_account_sid;
if (req.user.hasAccountAuth) {
account_sid = req.user.account_sid;
} else if (req.user.hasServiceProviderAuth) {
service_provider_sid = req.user.service_provider_sid;
} else {
// admin user can query all phone numbers
service_provider_sid = query_service_provider_sid;
account_sid = query_account_sid;
}
try {
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);
let total = 0;
if (isPaginationRequest) {
total = await PhoneNumber.countAll({service_provider_sid, account_sid, filter});
}
const results = await PhoneNumber.retrieveAllByCriteria({
service_provider_sid, account_sid, filter, page, page_size
});
const body = isPaginationRequest ? {
total,
page: Number(page),
page_size: Number(page_size),
data: results,
} : results;
res.status(200).json(body);
} catch (err) {
sysError(logger, res, err);
}

View File

@@ -4,6 +4,7 @@ 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 { CloudWatchLogsClient, FilterLogEventsCommand } = require('@aws-sdk/client-cloudwatch-logs');
const {
getS3Object,
getGoogleStorageObject,
@@ -31,7 +32,7 @@ router.get('/', async(req, res) => {
const service_provider_sid = account_sid ? null : parseServiceProviderSid(req.originalUrl);
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');
if (!count || count > 500) throw new DbErrorBadRequest('missing or invalid "count" query arg');
if (account_sid) {
const data = await queryCdrs({
@@ -106,6 +107,71 @@ router.get('/:call_id/:method/pcap', async(req, res) => {
}
});
router.get('/:call_sid/logs', async(req, res) => {
const {logger, queryCdrs} = req.app.locals;
const aws_region = process.env.AWS_REGION;
const {call_sid} = req.params;
const {logGroupName = 'jambonz-feature_server'} = req.query;
const account_sid = parseAccountSid(req.originalUrl);
if (!aws_region) {
return res.status(400).send({msg: 'Logs are only available in AWS environments'});
}
if (!account_sid) {
return res.status(400).send({msg: 'account_sid is required,' +
'please use /Accounts/{account_sid}/RecentCalls/{call_sid}/logs'});
}
try {
//find back the call in CDR to get timestame of the call
// this allow us limit search in cloudwatch logs
const data = await queryCdrs({
account_sid,
filter: call_sid,
page: 0,
page_size: 50
});
if (!data || data.data.length === 0) {
return res.status(404).send({msg: 'Call not found'});
}
const {
attempted_at, //2025-02-24T13:11:51.969Z
terminated_at, //2025-02-24T13:11:56.153Z
sip_callid
} = data.data[0];
const TIMEBUFFER = 60; //60 seconds
const startTime = new Date(attempted_at).getTime() - TIMEBUFFER * 1000;
const endTime = new Date(terminated_at).getTime() + TIMEBUFFER * 1000;
const client = new CloudWatchLogsClient({ region: aws_region });
let params = {
logGroupName,
startTime,
endTime,
filterPattern: `{ ($.callSid = "${call_sid}") || ($.callId = "${sip_callid}") }`
};
const command = new FilterLogEventsCommand(params);
const response = await client.send(command);
// if response have nextToken, we need to fetch all logs
while (response.nextToken) {
params = {
...params,
nextToken: response.nextToken
};
const command = new FilterLogEventsCommand(params);
const response2 = await client.send(command);
response.events = response.events.concat(response2.events);
response.nextToken = response2.nextToken;
}
let logs = [];
if (response.events && response.events.length > 0) {
logs = response.events.map((e) => e.message);
}
res.status(200).json(logs);
} catch (err) {
logger.error({err}, 'Cannot fetch logs from cloudwatch');
res.status(500).send({msg: err.message});
}
});
router.get('/trace/:trace_id', async(req, res) => {
const {logger} = req.app.locals;
const {trace_id} = req.params;

View File

@@ -3,7 +3,7 @@ const debug = require('debug')('jambonz:api-server');
const {DbErrorBadRequest, DbErrorUnprocessableRequest} = require('../../utils/errors');
const {promisePool} = require('../../db');
const {doGithubAuth, doGoogleAuth, doLocalAuth} = require('../../utils/oauth-utils');
const {validateEmail} = require('../../utils/email-utils');
const {validateEmail, emailSimpleText} = require('../../utils/email-utils');
const {cacheClient} = require('../../helpers');
const { v4: uuid } = require('uuid');
const short = require('short-uuid');
@@ -12,6 +12,7 @@ const jwt = require('jsonwebtoken');
const {setupFreeTrial, createTestCdrs, createTestAlerts} = require('./utils');
const {generateHashedPassword} = require('../../utils/password-utils');
const sysError = require('../error');
const insertUserSql = `INSERT into users
(user_sid, account_sid, name, email, provider, provider_userid, email_validated)
values (?, ?, ?, ?, ?, ?, 1)`;
@@ -36,6 +37,17 @@ const insertSignupHistorySql = `INSERT into signup_history
(email, name)
values (?, ?)`;
const slackEmail = `Hi there and welcome to jambonz!
We are excited to have you on board. Feel free to join the community on Slack at https://joinslack.jambonz.org,
where you can connect with other jambonz users, ask questions, share your experiences, and learn from others.
Hope to see you there!
Best,
DaveH and the jambonz team`;
const addLocalUser = async(logger, user_sid, account_sid,
name, email, email_activation_code, passwordHash, service_provider_sid) => {
const [r] = await promisePool.execute(insertUserLocalSql,
@@ -324,6 +336,15 @@ router.post('/', async(req, res) => {
tutorial_completion: 0,
scope: 'read-write'
});
// send invite to Slack
if (process.env.SEND_SLACK_INVITE_ON_SIGNUP) {
try {
emailSimpleText(logger, userProfile.email, 'Welcome to jambonz!', slackEmail);
} catch (err) {
logger.info({err}, 'Error sending slack invite');
}
}
}
else if (user_sid) {
/* add a new user for existing account */

View File

@@ -149,13 +149,30 @@ router.get('/:sid/VoipCarriers', async(req, res) => {
try {
await validateRetrieve(req);
const service_provider_sid = parseServiceProviderSid(req);
const carriers = await VoipCarrier.retrieveAllForSP(service_provider_sid);
if (req.user.hasScope('account')) {
return res.status(200).json(carriers.filter((c) => c.account_sid === req.user.account_sid || !c.account_sid));
const {account_sid: query_account_sid, name, page, page_size} = req.query || {};
const isPaginationRequest = page !== null && page !== undefined;
const account_sid = req.user.hasAccountAuth ? req.user.account_sid : query_account_sid || null;
let carriers = [];
let total = 0;
if (isPaginationRequest) {
total = await VoipCarrier.countAll({service_provider_sid, account_sid, name});
}
carriers = await VoipCarrier.retrieveByCriteria({
service_provider_sid,
account_sid,
name,
page,
page_size,
});
res.status(200).json(carriers);
const body = isPaginationRequest ? {
total,
page: Number(page),
page_size: Number(page_size),
data: carriers,
} : carriers;
res.status(200).json(body);
} catch (err) {
sysError(logger, res, err);
}

View File

@@ -4,7 +4,9 @@ const {DbErrorBadRequest, DbErrorForbidden} = require('../../utils/errors');
//const {parseSipGatewaySid} = require('./utils');
const decorate = require('./decorate');
const sysError = require('../error');
const net = require('net');
const hasWhitespace = (str) => /\s/.test(str);
const checkUserScope = async(req, voip_carrier_sid) => {
const {lookupCarrierBySid} = req.app.locals;
if (!voip_carrier_sid) {
@@ -41,6 +43,7 @@ const checkUserScope = async(req, voip_carrier_sid) => {
const validate = async(req, sid) => {
const {lookupSipGatewayBySid} = req.app.locals;
const {netmask, ipv4, inbound, outbound} = req.body;
let voip_carrier_sid;
if (sid) {
@@ -52,6 +55,21 @@ const validate = async(req, sid) => {
voip_carrier_sid = req.body.voip_carrier_sid;
if (!voip_carrier_sid) throw new DbErrorBadRequest('missing voip_carrier_sid');
}
if (netmask &&
process.env.JAMBONZ_MIN_GATEWAY_NETMASK &&
parseInt(netmask) < process.env.JAMBONZ_MIN_GATEWAY_NETMASK) {
throw new DbErrorBadRequest(
`netmask required to have value equal or greater than ${process.env.JAMBONZ_MIN_GATEWAY_NETMASK}`);
}
if (hasWhitespace(ipv4)) {
throw new DbErrorBadRequest('Gateway must not contain whitespace');
}
if (inbound && !net.isIPv4(ipv4)) {
throw new DbErrorBadRequest('Inbound gateway must be IPv4 address');
}
if (!inbound && outbound && (netmask && netmask != 32)) {
throw new DbErrorBadRequest('For outbound only gateway netmask can only be 32');
}
await checkUserScope(req, voip_carrier_sid);
};

View File

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

View File

@@ -1,5 +1,4 @@
const router = require('express').Router();
const request = require('request');
const getProvider = require('../../utils/sms-provider');
const { v4: uuidv4 } = require('uuid');
const sysError = require('../error');
@@ -122,25 +121,19 @@ router.post('/:provider', async(req, res) => {
logger.info({payload, url: serviceUrl}, `sending incomingSms API request to FS at ${serviceUrl}`);
request({
url: serviceUrl,
const response = await fetch(serviceUrl, {
method: 'POST',
json: true,
body: payload,
},
async(err, response, body) => {
if (err) {
logger.error(err, `Error sending incomingSms POST to ${serviceUrl}`);
return res.sendStatus(500);
}
if (200 === response.statusCode) {
// success
logger.info({body}, 'sending response to provider for incomingSMS');
return doSendResponse(res, respondFn, body);
}
logger.error({statusCode: response.statusCode}, `Non-success response returned by incomingSms ${serviceUrl}`);
return res.sendStatus(500);
body: JSON.stringify(payload),
headers: {'Content-Type': 'application/json'},
});
if (!response.ok) {
logger.error({response}, `Error sending incomingSms POST to ${serviceUrl}`);
return res.sendStatus(500);
}
const body = await response.json();
logger.info({body}, 'sending response to provider for incomingSMS');
return doSendResponse(res, respondFn, body);
} catch (err) {
sysError(logger, res, err);
}

View File

@@ -5,8 +5,19 @@ const SpeechCredential = require('../../models/speech-credential');
const sysError = require('../error');
const {decrypt, encrypt} = require('../../utils/encrypt-decrypt');
const {parseAccountSid, parseServiceProviderSid, parseSpeechCredentialSid} = require('./utils');
const {decryptCredential, testWhisper} = require('../../utils/speech-utils');
const {DbErrorUnprocessableRequest, DbErrorForbidden} = require('../../utils/errors');
const {decryptCredential, testWhisper, testDeepgramTTS,
getLanguagesAndVoicesForVendor,
testPlayHT,
testRimelabs,
testVerbioTts,
testVerbioStt,
testSpeechmaticsStt,
testCartesia,
testVoxistStt,
testOpenAiStt,
testInworld,
testResembleTTS} = require('../../utils/speech-utils');
const {DbErrorUnprocessableRequest, DbErrorForbidden, DbErrorBadRequest} = require('../../utils/errors');
const {
testGoogleTts,
testGoogleStt,
@@ -24,7 +35,6 @@ const {
testElevenlabs,
testAssemblyStt
} = require('../../utils/speech-utils');
const bent = require('bent');
const {promisePool} = require('../../db');
const validateAdd = async(req) => {
@@ -111,15 +121,26 @@ const encryptCredential = (obj) => {
secret_access_key,
aws_region,
api_key,
role_arn,
region,
client_id,
client_secret,
secret,
nuance_tts_uri,
nuance_stt_uri,
speechmatics_stt_uri,
deepgram_stt_uri,
deepgram_stt_use_tls,
deepgram_tts_uri,
playht_tts_uri,
resemble_tts_uri,
resemble_tts_use_tls,
use_custom_tts,
custom_tts_endpoint,
custom_tts_endpoint_url,
use_custom_stt,
use_for_stt,
use_for_tts,
custom_stt_endpoint,
custom_stt_endpoint_url,
tts_api_key,
@@ -130,9 +151,17 @@ const encryptCredential = (obj) => {
instance_id,
custom_stt_url,
custom_tts_url,
custom_tts_streaming_url,
auth_token = '',
cobalt_server_uri,
// For most vendors, model_id is being used for both TTS and STT, or one of them.
// for Cartesia, model_id is used for TTS only. introduce stt_model_id for STT
model_id,
stt_model_id,
user_id,
voice_engine,
engine_version,
service_version,
options
} = obj;
@@ -149,10 +178,17 @@ const encryptCredential = (obj) => {
return encrypt(service_key);
case 'aws':
assert(access_key_id, 'invalid aws speech credential: access_key_id is required');
assert(secret_access_key, 'invalid aws speech credential: secret_access_key is required');
assert(aws_region, 'invalid aws speech credential: aws_region is required');
const awsData = JSON.stringify({aws_region, access_key_id, secret_access_key});
// AWS polly can work for 3 types of credentials:
// 1/ access_key_id and secret_access_key
// 2/ RoleArn Assume role
// 3/ RoleArn assigned to instance profile where will run this application
const awsData = JSON.stringify(
{
aws_region,
...(access_key_id && {access_key_id}),
...(secret_access_key && {secret_access_key}),
...(role_arn && {role_arn}),
});
return encrypt(awsData);
case 'microsoft':
@@ -185,10 +221,28 @@ 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 || !deepgram_tts_uri) {
assert(api_key, 'invalid deepgram speech credential: api_key is required');
}
const deepgramData = JSON.stringify({api_key, deepgram_stt_uri,
deepgram_stt_use_tls, deepgram_tts_uri, model_id});
return encrypt(deepgramData);
case 'resemble':
assert(api_key, 'invalid resemble speech credential: api_key is required');
const resembleData = JSON.stringify({
api_key,
...(resemble_tts_uri && {resemble_tts_uri}),
...(resemble_tts_use_tls && {resemble_tts_use_tls})
});
return encrypt(resembleData);
case 'deepgramriver':
assert(api_key, 'invalid deepgram river speech credential: api_key is required');
const deepgramriverData = JSON.stringify({api_key});
return encrypt(deepgramriverData);
case 'ibm':
const ibmData = JSON.stringify({tts_api_key, tts_region, stt_api_key, stt_region, instance_id});
return encrypt(ibmData);
@@ -214,20 +268,78 @@ const encryptCredential = (obj) => {
const elevenlabsData = JSON.stringify({api_key, model_id, options});
return encrypt(elevenlabsData);
case 'speechmatics':
assert(api_key, 'invalid speechmatics speech credential: api_key is required');
assert(speechmatics_stt_uri, 'invalid speechmatics speech credential: speechmatics_stt_uri is required');
const speechmaticsData = JSON.stringify({api_key, speechmatics_stt_uri, options});
return encrypt(speechmaticsData);
case 'playht':
assert(api_key, 'invalid playht speech credential: api_key is required');
assert(user_id, 'invalid playht speech credential: user_id is required');
assert(voice_engine, 'invalid voice_engine speech credential: voice_engine is required');
const playhtData = JSON.stringify({api_key, user_id, voice_engine, playht_tts_uri, options});
return encrypt(playhtData);
case 'cartesia':
assert(api_key, 'invalid cartesia speech credential: api_key is required');
if (use_for_tts) {
assert(model_id, 'invalid cartesia speech credential: model_id is required');
}
if (use_for_stt) {
assert(stt_model_id, 'invalid cartesia speech credential: stt_model_id is required');
}
const cartesiaData = JSON.stringify({
api_key,
...(model_id && {model_id}),
...(stt_model_id && {stt_model_id}),
options});
return encrypt(cartesiaData);
case 'rimelabs':
assert(api_key, 'invalid rimelabs speech credential: api_key is required');
assert(model_id, 'invalid rimelabs speech credential: model_id is required');
const rimelabsData = JSON.stringify({api_key, model_id, options});
return encrypt(rimelabsData);
case 'inworld':
assert(api_key, 'invalid inworld speech credential: api_key is required');
assert(model_id, 'invalid inworld speech credential: model_id is required');
const inworldData = JSON.stringify({api_key, model_id, options});
return encrypt(inworldData);
case 'assemblyai':
assert(api_key, 'invalid assemblyai speech credential: api_key is required');
const assemblyaiData = JSON.stringify({api_key});
const assemblyaiData = JSON.stringify({api_key, service_version});
return encrypt(assemblyaiData);
case 'voxist':
assert(api_key, 'invalid voxist speech credential: api_key is required');
const voxistData = JSON.stringify({api_key});
return encrypt(voxistData);
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);
case 'openai':
assert(api_key, 'invalid openai speech credential: api_key is required');
assert(model_id, 'invalid openai speech credential: model_id is required');
const openaiData = JSON.stringify({api_key, model_id});
return encrypt(openaiData);
case 'verbio':
assert(engine_version, 'invalid verbio speech credential: client_id is required');
assert(client_id, 'invalid verbio speech credential: client_id is required');
assert(client_secret, 'invalid verbio speech credential: secret is required');
const verbioData = JSON.stringify({client_id, client_secret, engine_version});
return encrypt(verbioData);
default:
if (vendor.startsWith('custom:')) {
const customData = JSON.stringify({auth_token, custom_stt_url, custom_tts_url});
const customData = JSON.stringify({auth_token, custom_stt_url, custom_tts_url, custom_tts_streaming_url});
return encrypt(customData);
}
else assert(false, `invalid or missing vendor: ${vendor}`);
@@ -411,9 +523,21 @@ router.put('/:sid', async(req, res) => {
custom_stt_endpoint_url,
custom_stt_url,
custom_tts_url,
custom_tts_streaming_url,
cobalt_server_uri,
model_id,
options
stt_model_id,
voice_engine,
options,
deepgram_stt_uri,
deepgram_stt_use_tls,
deepgram_tts_uri,
playht_tts_uri,
engine_version,
service_version,
speechmatics_stt_uri,
resemble_tts_use_tls,
resemble_tts_uri
} = req.body;
const newCred = {
@@ -434,9 +558,21 @@ router.put('/:sid', async(req, res) => {
nuance_tts_uri,
custom_stt_url,
custom_tts_url,
custom_tts_streaming_url,
cobalt_server_uri,
model_id,
options
stt_model_id,
voice_engine,
options,
deepgram_stt_uri,
deepgram_stt_use_tls,
deepgram_tts_uri,
playht_tts_uri,
engine_version,
service_version,
speechmatics_stt_uri,
resemble_tts_uri,
resemble_tts_use_tls
};
logger.info({o, newCred}, 'updating speech credential with this new credential');
obj.credential = encryptCredential(newCred);
@@ -465,7 +601,7 @@ router.put('/:sid', async(req, res) => {
* Test a credential
*/
router.get('/:sid/test', async(req, res) => {
const {logger, synthAudio} = req.app.locals;
const {logger, synthAudio, getVerbioAccessToken} = req.app.locals;
try {
const sid = parseSpeechCredentialSid(req);
const creds = await SpeechCredential.retrieve(sid);
@@ -513,12 +649,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';
@@ -530,9 +667,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';
@@ -575,7 +713,7 @@ router.get('/:sid/test', async(req, res) => {
}
if (cred.use_for_stt) {
try {
await testMicrosoftStt(logger, {api_key, region});
await testMicrosoftStt(logger, {api_key, region, use_custom_stt, custom_stt_endpoint_url});
results.stt.status = 'ok';
SpeechCredential.sttTestResult(sid, true);
} catch (err) {
@@ -634,10 +772,43 @@ router.get('/:sid/test', async(req, res) => {
SpeechCredential.sttTestResult(sid, false);
}
}
}
else if (cred.vendor === 'deepgram') {
} else if (cred.vendor === 'resemble') {
if (cred.use_for_tts) {
try {
await testResembleTTS(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 === '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';
SpeechCredential.sttTestResult(sid, true);
} catch (err) {
results.stt = {status: 'fail', reason: err.message};
SpeechCredential.sttTestResult(sid, false);
}
}
}
else if (cred.vendor === 'deepgramriver') {
const {api_key} = credential;
if (cred.use_for_stt && api_key) {
try {
await testDeepgramStt(logger, {api_key});
results.stt.status = 'ok';
@@ -705,6 +876,81 @@ router.get('/:sid/test', async(req, res) => {
SpeechCredential.ttsTestResult(sid, false);
}
}
} else if (cred.vendor === 'speechmatics') {
const {api_key} = credential;
if (cred.use_for_stt) {
try {
await testSpeechmaticsStt(logger, {api_key});
results.stt.status = 'ok';
SpeechCredential.ttsTestResult(sid, true);
} catch (err) {
results.stt = {status: 'fail', reason: err.message};
SpeechCredential.ttsTestResult(sid, false);
}
}
} else if (cred.vendor === 'playht') {
if (cred.use_for_tts) {
try {
await testPlayHT(logger, synthAudio, credential);
results.tts.status = 'ok';
SpeechCredential.ttsTestResult(sid, true);
} catch (err) {
let reason = err.message;
try {
reason = await err.text();
} catch {}
results.tts = {status: 'fail', reason};
SpeechCredential.ttsTestResult(sid, false);
}
}
} else if (cred.vendor === 'cartesia') {
if (cred.use_for_tts || cred.use_for_stt) {
try {
// Cartesia does not have API for testing STT, same key is used for both TTS and STT
await testCartesia(logger, synthAudio, credential);
if (cred.use_for_tts) {
results.tts.status = 'ok';
}
if (cred.use_for_stt) {
results.stt.status = 'ok';
}
SpeechCredential.ttsTestResult(sid, true);
} catch (err) {
let reason = err.message;
try {
reason = await err.text();
} catch {}
if (cred.use_for_tts) {
results.tts = {status: 'fail', reason};
}
if (cred.use_for_stt) {
results.stt = {status: 'fail', reason};
}
SpeechCredential.ttsTestResult(sid, false);
}
}
} else if (cred.vendor === 'inworld') {
if (cred.use_for_tts) {
try {
await testInworld(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 === '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) {
@@ -717,6 +963,18 @@ router.get('/:sid/test', async(req, res) => {
SpeechCredential.sttTestResult(sid, false);
}
}
} else if (cred.vendor === 'voxist') {
const {api_key} = credential;
if (cred.use_for_stt) {
try {
await testVoxistStt(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 {
@@ -728,6 +986,38 @@ router.get('/:sid/test', async(req, res) => {
SpeechCredential.ttsTestResult(sid, false);
}
}
} else if (cred.vendor === 'openai') {
if (cred.use_for_stt) {
try {
await testOpenAiStt(logger, credential);
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 === 'verbio') {
if (cred.use_for_tts) {
try {
await testVerbioTts(logger, synthAudio, credential);
results.tts.status = 'ok';
SpeechCredential.ttsTestResult(sid, true);
} catch (err) {
results.tts = {status: 'fail', reason: err.message};
SpeechCredential.ttsTestResult(sid, false);
}
}
if (cred.use_for_stt) {
try {
await testVerbioStt(logger, getVerbioAccessToken, credential);
results.stt.status = 'ok';
SpeechCredential.sttTestResult(sid, true);
} catch (err) {
results.stt = {status: 'fail', reason: err.message};
SpeechCredential.sttTestResult(sid, false);
}
}
}
res.status(200).json(results);
@@ -741,89 +1031,30 @@ router.get('/:sid/test', async(req, res) => {
* Fetch speech voices and languages
*/
router.post('/voices', async(req, res) => {
const logger = req.app.locals.logger;
const {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);
router.get('/speech/supportedLanguagesAndVoices', async(req, res) => {
const {logger, getTtsVoices} = req.app.locals;
try {
res.status(200).json(await getTtsVoices(vendor, label, service_provider_sid, account_sid));
const {vendor, label, create_new} = 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 = create_new ? null : await SpeechCredential.getSpeechCredentialsByVendorAndLabel(
service_provider_sid, account_sid, vendor, label);
const tmp = credentials && credentials.length > 0 ? credentials[0] : null;
const cred = tmp ? JSON.parse(decrypt(tmp.credential)) : null;
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);
}
});
router.post('/languages', async(req, res) => {
const logger = req.app.locals.logger;
const {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);
try {
res.status(200).json(await getTtsLanguages(vendor, label, service_provider_sid, account_sid));
} catch (err) {
sysError(logger, res, err);
}
});
const getTtsVoices = async(vendor, label, service_provider_sid, account_sid) => {
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;
if (vendor === 'elevenlabs') {
const get = bent('https://api.elevenlabs.io', 'GET', 'json', {
...(cred && {
'xi-api-key' : cred.api_key
})
});
const resp = await get('/v1/voices');
return resp ? resp.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)) : [];
}
return [];
};
const getTtsLanguages = async(vendor, label, service_provider_sid, account_sid) => {
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;
if (vendor === 'elevenlabs') {
if (!cred) {
return [];
}
const get = bent('https://api.elevenlabs.io', 'GET', 'json', {
'xi-api-key' : cred.api_key
});
const resp = await get('/v1/models');
if (!resp || resp.length === 0) {
return [];
}
const model = resp.find((m) => m.model_id === cred.model_id);
return model ? model.languages.map((l) => {
return {
value: l.language_id,
name: l.name
};
}).sort((a, b) => a.name.localeCompare(b.name)) : [];
}
};
module.exports = router;

View File

@@ -229,7 +229,7 @@ const updateQuantities = async(req, res) => {
const obj = {
quantity: product.quantity,
};
return Object.assign(obj, existingItem ? {id: existingItem.id} : {price_id: product.price_id});
return Object.assign(obj, existingItem ? {id: existingItem.id} : {price: product.price_id});
});
if (dry_run) {

View File

@@ -9,6 +9,8 @@ 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;
@@ -38,6 +40,7 @@ router.post('/Synthesize', async(req, res) => {
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');
}
@@ -67,6 +70,8 @@ router.post('/Synthesize', async(req, res) => {
voice = arr[1];
model = arr[2];
}
} else if (cred.vendor === 'deepgram') {
model = voice;
}
const stats = {
histogram: () => {},
@@ -82,23 +87,44 @@ router.post('/Synthesize', async(req, res) => {
model,
salt,
credentials: cred,
disableTtsCache: false
disableTtsCache: false,
disableTtsStreaming: true
});
const stat = fs.statSync(filePath);
let contentType = 'audio/mpeg';
let readStream = fs.createReadStream(filePath);
if (['nuance', 'nvidia'].includes(cred.vendor) ||
(
process.env.JAMBONES_TTS_TRIM_SILENCE &&
['microsoft', 'azure'].includes(cred.vendor)
)
) {
if (encodingMp3) {
readStream = readStream
.pipe(new PCMToMP3Encoder({
channels: 1,
sampleRate: 8000,
bitRate: 128
}, logger));
} else {
contentType = 'application/octet-stream';
}
}
res.writeHead(200, {
'Content-Type': 'audio/mpeg',
'Content-Length': stat.size,
'Content-Type': contentType,
});
const readStream = fs.createReadStream(filePath);
// We replaced all the event handlers with a simple call to readStream.pipe()
readStream.pipe(res);
pipeline(readStream, res, (err) => {
if (err) {
logger.error('ttscache/Synthesize failed:', err);
if (!res.headersSent) {
res.status(500).end('Server error');
}
}
readStream.on('end', () => {
// Delete the file after it's been read
fs.unlink(filePath, (err) => {
if (err) throw err;
fs.unlink(filePath, (unlinkErr) => {
if (unlinkErr) throw unlinkErr;
logger.info(`${filePath} was deleted`);
});
});

View File

@@ -1,5 +1,7 @@
const router = require('express').Router();
const User = require('../../models/user');
const UserPermissions = require('../../models/user-permissions');
const Permissions = require('../../models/permissions');
const {DbErrorBadRequest, BadRequestError, DbErrorForbidden} = require('../../utils/errors');
const {generateHashedPassword, verifyPassword} = require('../../utils/password-utils');
const {promisePool} = require('../../db');
@@ -38,7 +40,8 @@ const validateRequest = async(user_sid, req) => {
email,
email_activation_code,
force_change,
is_active
is_active,
is_view_only
} = payload;
const [r] = await promisePool.query(retrieveSql, user_sid);
@@ -93,7 +96,8 @@ const validateRequest = async(user_sid, req) => {
if (email_activation_code && !email) {
throw new DbErrorBadRequest('email and email_activation_code both required');
}
if (!name && !new_password && !email && !initial_password && !force_change && !is_active)
if (!name && !new_password && !email && !initial_password && !force_change && !is_active &&
is_view_only === undefined)
throw new DbErrorBadRequest('no updates requested');
return user;
@@ -140,7 +144,35 @@ const ensureUserRetrievalIsAllowed = (req, user) => {
throw error;
}
};
async function updateViewOnlyUserPermission(is_view_only, user_sid) {
try {
const [viewOnlyPermission] = await Permissions.retrieveByName('VIEW_ONLY');
if (!viewOnlyPermission) {
throw new Error('VIEW_ONLY permission not found');
}
const existingPermissions = await UserPermissions.retrieveByUserIdPermissionSid(
user_sid,
viewOnlyPermission.permission_sid
);
if (is_view_only && existingPermissions.length === 0) {
await UserPermissions.make({
user_sid,
permission_sid: viewOnlyPermission.permission_sid,
});
} else if (!is_view_only && existingPermissions.length > 0) {
await UserPermissions.remove(existingPermissions[0].user_permissions_sid);
}
} catch (err) {
throw new Error(`Failed to update user permissions: ${err.message}`);
}
}
async function removeViewOnlyUserPermission(user_id) {
const [viewOnlyPermission] = await Permissions.retrieveByName('VIEW_ONLY');
if (viewOnlyPermission) {
await UserPermissions.remove(user_id, viewOnlyPermission.permission_sid);
}
}
router.get('/', async(req, res) => {
const logger = req.app.locals.logger;
@@ -293,6 +325,7 @@ router.get('/me', async(req, res) => {
res.json(payload);
} catch (err) {
sysError(logger, res, err);
logger.info({err, payload}, 'payload');
}
});
@@ -308,7 +341,14 @@ router.get('/:user_sid', async(req, res) => {
}
ensureUserRetrievalIsAllowed(req, user);
const [viewOnlyPermission] = await Permissions.retrieveByName('VIEW_ONLY');
const existingPermissions = await UserPermissions.retrieveByUserId(
user_sid
);
logger.debug(`existingPermissions of ${user_sid}: ${JSON.stringify(existingPermissions)}`);
user.is_view_only = existingPermissions.length === 1 &&
existingPermissions[0].permission_sid === viewOnlyPermission.permission_sid;
logger.debug(`User ${user_sid} is view-only user: ${user.is_view_only}`);
// eslint-disable-next-line no-unused-vars
const { hashed_password, ...rest } = user;
return res.status(200).json(rest);
@@ -332,9 +372,9 @@ router.put('/:user_sid', async(req, res) => {
is_active,
force_change,
account_sid,
service_provider_sid
service_provider_sid,
is_view_only
} = req.body;
//if (req.user.user_sid && req.user.user_sid !== user_sid) return res.sendStatus(403);
if (!hasAdminAuth &&
@@ -427,6 +467,8 @@ router.put('/:user_sid', async(req, res) => {
//TODO: send email with activation code
}
}
// update user permissions
await updateViewOnlyUserPermission(is_view_only, user_sid);
res.sendStatus(204);
} catch (err) {
sysError(logger, res, err);
@@ -443,6 +485,8 @@ router.post('/', async(req, res) => {
};
const allUsers = await User.retrieveAll();
delete payload.initial_password;
const is_view_only = payload.is_view_only;
delete payload.is_view_only;
try {
if (req.body.initial_password) {
@@ -464,6 +508,7 @@ router.post('/', async(req, res) => {
if (req.user.hasAdminAuth) {
logger.debug({payload}, 'POST /users');
const uuid = await User.make(payload);
await updateViewOnlyUserPermission(is_view_only, uuid);
res.status(201).json({user_sid: uuid});
}
else if (req.user.hasAccountAuth) {
@@ -472,6 +517,7 @@ router.post('/', async(req, res) => {
...payload,
account_sid: req.user.account_sid,
});
await updateViewOnlyUserPermission(is_view_only, uuid);
res.status(201).json({user_sid: uuid});
}
else if (req.user.hasServiceProviderAuth) {
@@ -480,6 +526,7 @@ router.post('/', async(req, res) => {
...payload,
service_provider_sid: req.user.service_provider_sid,
});
await updateViewOnlyUserPermission(is_view_only, uuid);
res.status(201).json({user_sid: uuid});
}
} catch (err) {
@@ -497,6 +544,8 @@ router.delete('/:user_sid', async(req, res) => {
const user = allUsers.filter((user) => user.user_sid === user_sid);
ensureUserDeletionIsAllowed(req, activeAdminUsers, user);
logger.debug(`Removing view-only permission for user ${user_sid}`);
await removeViewOnlyUserPermission(user_sid);
await User.remove(user_sid);
/* invalidate the jwt of the deleted user */

View File

@@ -1,5 +1,6 @@
const { v4: uuid, validate } = require('uuid');
const bent = require('bent');
const URL = require('url').URL;
const isValidHostname = require('is-valid-hostname');
const Account = require('../../models/account');
const {promisePool} = require('../../db');
const {cancelSubscription, detachPaymentMethod} = require('../../utils/stripe-utils');
@@ -11,8 +12,6 @@ values (?, ?)`;
const replaceOldSubscriptionSql = `UPDATE account_subscriptions
SET effective_end_date = CURRENT_TIMESTAMP, change_reason = ?
WHERE account_subscription_sid = ?`;
//const request = require('request');
//require('request-debug')(request);
const setupFreeTrial = async(logger, account_sid, isReturningUser) => {
const sid = uuid();
@@ -287,7 +286,11 @@ const hasAccountPermissions = async(req, res, next) => {
message: 'insufficient privileges'
});
} catch (error) {
throw error;
// return 400 on errors
res.status(400).json({
status: 'fail',
message: error.message
});
}
};
@@ -370,35 +373,44 @@ const checkLimits = async(req, res, next) => {
};
const getSubspaceJWT = async(id, secret) => {
const postJwt = bent('https://id.subspace.com', 'POST', 'json', 200);
const jwt = await postJwt('/oauth/token',
{
const response = await fetch('https://id.subspace.com/oauth/token', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
client_id: id,
client_secret: secret,
audience: 'https://api.subspace.com/',
grant_type: 'client_credentials',
}
);
})
});
if (!response.ok) {
throw new Error(`Failed to get JWT: ${response.status} ${response.statusText}`);
}
const jwt = await response.json();
return jwt.access_token;
};
const enableSubspace = async(opts) => {
const {subspace_client_id, subspace_client_secret, destination} = opts;
const accessToken = await getSubspaceJWT(subspace_client_id, subspace_client_secret);
const postTeleport = bent('https://api.subspace.com', 'POST', 'json', 200);
const teleport = await postTeleport('/v1/sipteleport',
{
const response = await fetch('https://api.subspace.com/v1/sipteleport', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`
},
body: JSON.stringify({
name: 'Jambonz',
destination,
status: 'ENABLED'
},
{
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`
}
);
})
});
if (!response.ok) {
throw new Error(`Failed to enable teleport: ${response.status} ${response.statusText}`);
}
const teleport = await response.json();
return teleport;
};
@@ -406,13 +418,15 @@ const disableSubspace = async(opts) => {
const {subspace_client_id, subspace_client_secret, subspace_sip_teleport_id} = opts;
const accessToken = await getSubspaceJWT(subspace_client_id, subspace_client_secret);
const relativeUrl = `/v1/sipteleport/${subspace_sip_teleport_id}`;
const deleteTeleport = bent('https://api.subspace.com', 'DELETE', 'json', 200);
await deleteTeleport(relativeUrl, {},
{
const response = await fetch(`https://api.subspace.com${relativeUrl}`, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${accessToken}`
}
);
return;
});
if (!response.ok) {
throw new Error(`Failed to delete teleport: ${response.status} ${response.statusText}`);
}
};
const validatePasswordSettings = async(password) => {
@@ -440,6 +454,44 @@ const validatePasswordSettings = async(password) => {
return;
};
function hasValue(data) {
if (typeof data === 'string') {
return data && data.length > 0;
} else if (Array.isArray(data)) {
return data && data.length > 0;
} else if (typeof data === 'object') {
return data && Object.keys(data).length > 0;
} else if (typeof data === 'number') {
return data !== null;
} else if (typeof data === 'boolean') {
return data !== null;
} else {
return false;
}
}
const isInvalidUrl = async(s) => {
const protocols = ['https:', 'http:', 'ws:', 'wss:'];
try {
const url = new URL(s);
if (s.length != s.trim().length) {
return 'URL contains leading/trailing whitespace';
}
else if (!isValidHostname(url.hostname)) {
return `URL has invalid hostname ${url.hostname}`;
}
else if (!protocols.includes(url.protocol)) {
return `URL has missing or invalid protocol ${url.protocol}`;
}
else {
return false;
}
} catch (err) {
return 'URL is invalid';
}
};
module.exports = {
setupFreeTrial,
createTestCdrs,
@@ -460,5 +512,7 @@ module.exports = {
checkLimits,
enableSubspace,
disableSubspace,
validatePasswordSettings
validatePasswordSettings,
hasValue,
isInvalidUrl
};

View File

@@ -73,16 +73,36 @@ decorate(router, VoipCarrier, ['add', 'update', 'delete'], preconditions);
/* list */
router.get('/', async(req, res) => {
const logger = req.app.locals.logger;
const {account_sid: query_account_sid, name, page, page_size} = req.query || {};
const isPaginationRequest = page !== null && page !== undefined;
let service_provider_sid = null, account_sid = query_account_sid;
if (req.user.hasAccountAuth) {
account_sid = req.user.account_sid;
} else if (req.user.hasServiceProviderAuth) {
service_provider_sid = req.user.service_provider_sid;
}
try {
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));
let total = 0;
if (isPaginationRequest) {
total = await VoipCarrier.countAll({service_provider_sid, account_sid, name});
}
res.status(200).json(results);
const carriers = await VoipCarrier.retrieveByCriteria({
service_provider_sid,
account_sid,
name,
page,
page_size,
});
const body = isPaginationRequest ? {
total,
page: Number(page),
page_size: Number(page_size),
data: carriers,
} : carriers;
res.status(200).json(body);
} catch (err) {
sysError(logger, res, err);
}

View File

@@ -382,11 +382,35 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
/login:
post:
tags:
- Authentication
summary: login and retrieve a JWT
operationId: login
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Login'
responses:
200:
description: user logged in
content:
application/json:
schema:
$ref: '#/components/schemas/SuccessfulLogin'
500:
description: system error
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
/logout:
post:
tags:
- Authentication
summary: log out and deactivate jwt
summary: log out and deactivate the JWT
operationId: logoutUser
responses:
204:
@@ -584,10 +608,9 @@ paths:
content:
application/json:
schema:
type:
array
type: array
items:
$ref: '#/components/schemas/Users'
$ref: '#/components/schemas/UserList'
403:
description: unauthorized
500:
@@ -610,27 +633,13 @@ paths:
- Users
summary: retrieve user information
operationId: getUser
requestBody:
content:
application/json:
schema:
type: object
properties:
name:
type: string
email:
type: string
is_active:
type: boolean
force_change:
type: boolean
scope:
type: string
permissions:
type: array
responses:
204:
200:
description: user information
content:
application/json:
schema:
$ref: '#/components/schemas/UserProfile'
403:
description: user information
content:
@@ -674,6 +683,8 @@ paths:
type: string
permissions:
type: array
items:
type: string
responses:
204:
description: user updated
@@ -712,6 +723,8 @@ paths:
type: string
permissions:
type: array
items:
type: string
old_password:
type: string
description: existing password, which is to be replaced
@@ -998,7 +1011,7 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
/AccountTest/:ServiceProviderSid:
/AccountTest/{ServiceProviderSid}:
parameters:
- name: ServiceProviderSid
in: path
@@ -1095,6 +1108,9 @@ paths:
requires_register:
type: boolean
description: wehther this provider requires us to send a REGISTER to them in order to receive calls
register_use_tls:
type: boolean
description: wehther this provider requires us to send a REGISTER use TLS protocol
register_username:
type: string
description: sip username to authenticate with, if registration is required
@@ -1971,7 +1987,7 @@ paths:
tags:
- Service Providers
summary: add a VoiPCarrier to a service provider based on PredefinedCarrier template
operationId: createVoipCarrierFromTemplate
operationId: createVoipCarrierFromTemplateBySP
responses:
201:
description: voip carrier successfully created
@@ -2072,6 +2088,41 @@ paths:
description: credential successfully deleted
404:
description: credential not found
/ServiceProviders/{ServiceProviderSid}/SpeechCredentials/speech/supportedLanguagesAndVoices:
get:
tags:
- Service Providers
summary: get supported languages, voices and models
operationId: supportedLanguagesAndVoices
parameters:
- name: ServiceProviderSid
in: path
required: true
schema:
type: string
format: uuid
- name: vendor
in: query
required: true
schema:
type: string
- name: label
in: query
schema:
type: string
responses:
200:
description: get supported languages, voices and models
content:
application/json:
schema:
$ref: '#/components/schemas/SpeechLanguagesVoices'
500:
description: system error
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
/ServiceProviders/{ServiceProviderSid}/SpeechCredentials/{SpeechCredentialSid}/test:
get:
tags:
@@ -2891,7 +2942,7 @@ paths:
tags:
- Accounts
summary: get a specific speech credential
operationId: getSpeechCredential
operationId: getSpeechCredentialByAccount
responses:
200:
description: retrieve speech credentials for a specified account
@@ -2905,7 +2956,7 @@ paths:
tags:
- Accounts
summary: update a speech credential
operationId: updateSpeechCredential
operationId: updateSpeechCredentialByAccount
requestBody:
content:
application/json:
@@ -2926,18 +2977,53 @@ paths:
tags:
- Accounts
summary: delete a speech credential
operationId: deleteSpeechCredential
operationId: deleteSpeechCredentialByAccount
responses:
204:
description: credential successfully deleted
404:
description: credential not found
/Accounts/{AccountSid}/SpeechCredentials/speech/supportedLanguagesAndVoices:
get:
tags:
- Accounts
summary: get supported languages, voices and models
operationId: supportedLanguagesAndVoicesByAccount
parameters:
- name: AccountSid
in: path
required: true
schema:
type: string
format: uuid
- name: vendor
in: query
required: true
schema:
type: string
- name: label
in: query
schema:
type: string
responses:
200:
description: get supported languages, voices and models
content:
application/json:
schema:
$ref: '#/components/schemas/SpeechLanguagesVoices'
500:
description: system error
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
/Accounts/{AccountSid}/SpeechCredentials/{SpeechCredentialSid}/test:
get:
tags:
- Accounts
summary: test a speech credential
operationId: testSpeechCredential
operationId: testSpeechCredentialByAccount
parameters:
- name: AccountSid
in: path
@@ -3183,7 +3269,7 @@ paths:
tags:
- Service Providers
summary: retrieve pcap for a call
operationId: getRecentCallTrace
operationId: getRecentCallTraceBySP
responses:
200:
description: retrieve sip trace data
@@ -3269,7 +3355,7 @@ paths:
tags:
- Service Providers
summary: retrieve recent calls for an account
operationId: listRecentCalls
operationId: listRecentCallsBySP
responses:
200:
description: retrieve recent call records for a specified account
@@ -3370,7 +3456,7 @@ paths:
tags:
- Service Providers
summary: retrieve sip trace detail for a call
operationId: getRecentCallTrace
operationId: getRecentCallTraceByCallId
responses:
200:
description: retrieve sip trace data
@@ -3397,7 +3483,7 @@ paths:
tags:
- Accounts
summary: retrieve pcap for a call
operationId: getRecentCallTrace
operationId: getRecentCallTraceByAccount
responses:
200:
description: retrieve sip trace data
@@ -3583,7 +3669,7 @@ paths:
tags:
- Accounts
summary: retrieve alerts for an account
operationId: listAlerts
operationId: listAlertsByAccount
responses:
200:
description: retrieve alerts for a specified account
@@ -3796,6 +3882,34 @@ paths:
$ref: '#/components/schemas/GeneralError'
/Accounts/{AccountSid}/Conferences:
get:
tags:
- Conferences
summary: list conferences
operationId: listConferences
parameters:
- name: AccountSid
in: path
required: true
schema:
type: string
responses:
200:
description: list of conferences for a specified account
content:
application/json:
schema:
type: array
items:
type: string
500:
description: system error
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
/Accounts/{AccountSid}/Calls:
post:
tags:
@@ -4066,6 +4180,22 @@ paths:
type: string
siprecServerURL:
type: string
conferenceParticipantAction:
type: object
properties:
action:
type: string
enum:
- tag
- untag
- coach
- uncoach
- mute
- unmute
- hold
- unhold
tag:
type: string
responses:
200:
description: Accepted
@@ -4182,7 +4312,7 @@ paths:
tags:
- Accounts
summary: retrieve online sip users for an account
operationId: listQueues
operationId: listRegisteredSipUsers
responses:
200:
description: retrieve online sip users for an account
@@ -4196,7 +4326,7 @@ paths:
tags:
- Accounts
summary: retrieve online sip users for an account by list of sip username
operationId: listRegisteredSipUsers
operationId: listRegisteredSipUsersByUsername
requestBody:
content:
application/json:
@@ -4215,6 +4345,12 @@ paths:
$ref: '#/components/schemas/RegisteredClient'
/Accounts/{AccountSid}/RegisteredSipUsers/{Client}:
parameters:
- name: AccountSid
in: path
required: true
schema:
type: string
format: uuid
- name: Client
in: path
required: true
@@ -4235,6 +4371,13 @@ paths:
schema:
$ref: '#/components/schemas/RegisteredClient'
/Accounts/{AccountSid}/TtsCache/Synthesize:
parameters:
- name: AccountSid
in: path
required: true
schema:
type: string
format: uuid
post:
tags:
- Accounts
@@ -4262,6 +4405,10 @@ paths:
type: string
description: voice ID
example: en-US-Standard-C
encodingMp3:
type: boolean
description: convert audio to mp3.
example: true
required:
- speech_credential_sid
- text
@@ -4969,17 +5116,32 @@ components:
scheme: bearer
bearerFormat: token
schemas:
SuccessfulLogin:
type: object
required:
- username
- password
properties:
token:
type: string
user_sid:
type: string
scope:
type: string
force_change:
type: boolean
Login:
type: object
properties:
user_sid:
username:
type: string
api_token:
type: string
change_password:
type: boolean
password:
type: string
required:
- user_sid
- username
- password
SuccessfulApiKeyAdd:
type: object
required:
@@ -5987,6 +6149,70 @@ components:
- name
- reported_usage
- model
TtsModel:
type: object
properties:
name:
type: string
example: Turbo v2
value:
type: string
example: eleven_turbo_v2
LanguageVoice:
type: object
properties:
name:
type: string
example: Standard-A (Female)
value:
type: string
example: ar-XA-Standard-A
LanguageVoices:
type: object
properties:
name:
type: string
example: English (US)
value:
type: string
example: en-US
voices:
type: array
items:
$ref: '#/components/schemas/LanguageVoice'
SpeechLanguagesVoices:
type: object
properties:
tts:
type: array
items:
$ref: '#/components/schemas/LanguageVoices'
stt:
type: array
items:
$ref: '#/components/schemas/LanguageVoice'
ttsModel:
type: array
items:
$ref: '#/components/schemas/TtsModel'
UserList:
type: object
properties:
name:
type: string
email:
type: string
is_active:
type: boolean
force_change:
type: boolean
scope:
type: string
permissions:
type: array
items:
type: string
security:
- bearerAuth: []

View File

@@ -0,0 +1,50 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"additionalProperties": false,
"minProperties": 1,
"patternProperties": {
"^[a-zA-Z0-9_]+$": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"string",
"number",
"boolean"
]
},
"required": {
"type": "boolean"
},
"default": {
"oneOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
}
]
},
"enum": {
"type": "array"
},
"obscure": {
"type": "boolean"
}
},
"required": [
"type",
"description"
]
}
}
}

78
lib/utils/appenv_utils.js Normal file
View File

@@ -0,0 +1,78 @@
const Ajv = require('ajv');
const assert = require('assert');
const ajv = new Ajv();
const schemaSchema = require('./appenv_schemaSchema.json');
const validateAppEnvSchema = (schema) => {
const validate = ajv.compile(schemaSchema);
return validate(schema);
};
//Currently this request is not signed with the webhook secret as it is outside an account
const fetchAppEnvSchema = async(logger, url) => {
// Translate WebSocket URLs to HTTP equivalents (case-insensitive)
let fetchUrl = url;
if (url.toLowerCase().startsWith('ws://')) {
fetchUrl = 'http://' + url.substring(5);
} else if (url.toLowerCase().startsWith('wss://')) {
fetchUrl = 'https://' + url.substring(6);
}
try {
const response = await fetch(fetchUrl, {
method: 'OPTIONS',
headers: {
Accept: 'application/json'
}
});
if (!response.ok) {
logger.info(`Failure to fetch app env schema ${response.status} ${response.statusText}`);
return false;
}
const schema = await response.json();
return schema;
}
catch (e) {
logger.info(`Failure to fetch app env schema ${e}`);
return false;
}
};
const validateAppEnvData = async(schema, data) => {
const schemaKeys = Object.keys(schema);
const dataKeys = Object.keys(data);
let errorMsg = false;
// Check for required keys
schemaKeys.forEach((k) => {
if (schema[k].required) {
if (!dataKeys.includes(k)) {
errorMsg = `Missing required value env_vars.${k}`;
console.log(errorMsg);
}
}
});
//Validate the values
dataKeys.forEach((k) => {
if (schemaKeys.includes(k)) {
try {
// Check value is correct type
assert(typeof data[k] == schema[k].type);
// if enum check value is valid
if (schema[k].enum) {
assert(schema[k].enum.includes(data[k]));
}
} catch (error) {
errorMsg = `Invalid value/type for env_vars.${k}`;
}
}
});
return errorMsg;
};
module.exports = {
validateAppEnvSchema,
fetchAppEnvSchema,
validateAppEnvData
};

View File

@@ -1,6 +1,5 @@
if (!process.env.JAMBONES_HOSTING) return;
const bent = require('bent');
const crypto = require('crypto');
const assert = require('assert');
const domains = new Map();
@@ -26,17 +25,20 @@ const createAuthHeaders = () => {
const getDnsDomainId = async(logger, name) => {
checkAsserts();
const headers = createAuthHeaders();
const get = bent(process.env.DME_BASE_URL, 'GET', 'json', headers);
try {
const result = await get('/dns/managed');
debug(result, 'getDnsDomainId: all domains');
if (Array.isArray(result.data)) {
const domain = result.data.find((o) => o.name === name);
if (domain) return domain.id;
debug(`getDnsDomainId: failed to find domain ${name}`);
}
} catch (err) {
logger.error({err}, 'Error retrieving domains');
const response = await fetch(`${process.env.DME_BASE_URL}/dns/managed`, {
method: 'GET',
headers
});
if (!response.ok) {
logger.error({response}, 'Error retrieving domains');
return;
}
const result = await response.json();
debug(result, 'getDnsDomainId: all domains');
if (Array.isArray(result.data)) {
const domain = result.data.find((o) => o.name === name);
if (domain) return domain.id;
debug(`getDnsDomainId: failed to find domain ${name}`);
}
};
@@ -80,21 +82,20 @@ const createDnsRecords = async(logger, domain, name, value, ttl = 3600) => {
];
const headers = createAuthHeaders();
const records = [...a_records, ...srv_records];
const post = bent(process.env.DME_BASE_URL, 'POST', 201, 400, headers);
logger.debug({records}, 'Attemting to create dns records');
const res = await post(`/dns/managed/${domainId}/records/createMulti`,
[...a_records, ...srv_records]);
if (201 === res.statusCode) {
const str = await res.text();
return JSON.parse(str);
const response = await fetch(`${process.env.DME_BASE_URL}/dns/managed/${domainId}/records/createMulti`, {
method: 'POST',
headers,
body: JSON.stringify(records)
});
if (!response.ok) {
logger.error({response}, 'Error creating records');
return;
}
let body;
try {
body = await res.json();
} catch (err) {
const result = await response.json();
logger.debug({result}, 'createDnsRecords: created records');
if (201 === response.status) {
return result;
}
logger.error({headers: res.headers, body}, `Error creating records, status ${res.statusCode}`);
} catch (err) {
logger.error({err}, 'Error retrieving domains');
}
@@ -103,7 +104,6 @@ const createDnsRecords = async(logger, domain, name, value, ttl = 3600) => {
const deleteDnsRecords = async(logger, domain, recIds) => {
checkAsserts();
const headers = createAuthHeaders();
const del = bent(process.env.DME_BASE_URL, 'DELETE', 200, headers);
try {
if (!domains.has(domain)) {
const domainId = await getDnsDomainId(logger, domain);
@@ -112,7 +112,10 @@ const deleteDnsRecords = async(logger, domain, recIds) => {
}
const domainId = domains.get(domain);
const url = `/dns/managed/${domainId}/records?${recIds.map((r) => `ids=${r}`).join('&')}`;
await del(url);
await fetch(`${process.env.DME_BASE_URL}${url}`, {
method: 'DELETE',
headers
});
return true;
} catch (err) {
console.error(err);

View File

@@ -1,7 +1,6 @@
const formData = require('form-data');
const Mailgun = require('mailgun.js');
const mailgun = new Mailgun(formData);
const bent = require('bent');
const validateEmail = (email) => {
// eslint-disable-next-line max-len
const re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
@@ -19,8 +18,9 @@ const emailSimpleText = async(logger, to, subject, text) => {
};
const sendEmailByCustomVendor = async(logger, from, to, subject, text) => {
try {
const post = bent('POST', {
const response = await fetch(process.env.CUSTOM_EMAIL_VENDOR_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...((process.env.CUSTOM_EMAIL_VENDOR_USERNAME && process.env.CUSTOM_EMAIL_VENDOR_PASSWORD) &&
({
@@ -28,22 +28,22 @@ const sendEmailByCustomVendor = async(logger, from, to, subject, text) => {
`${process.env.CUSTOM_EMAIL_VENDOR_USERNAME}:${process.env.CUSTOM_EMAIL_VENDOR_PASSWORD}`
).toString('base64')}`
}))
});
const res = await post(process.env.CUSTOM_EMAIL_VENDOR_URL, {
},
body: JSON.stringify({
from,
to,
subject,
text
});
logger.debug({
res
}, 'sent email to custom vendor.');
} catch (err) {
logger.info({
err
}, 'Error sending email From Custom email vendor');
})
});
if (!response.ok) {
logger.error({response}, 'Error sending email to custom vendor');
return;
}
const res = await response.json();
logger.debug({
res
}, 'sent email to custom vendor.');
};
const sendEmailByMailgun = async(logger, from, to, subject, text) => {

View File

@@ -17,10 +17,15 @@ const encrypt = (text) => {
};
const decrypt = (data) => {
const hash = JSON.parse(data);
const decipher = crypto.createDecipheriv(algorithm, secretKey, Buffer.from(hash.iv, 'hex'));
const decrpyted = Buffer.concat([decipher.update(Buffer.from(hash.content, 'hex')), decipher.final()]);
return decrpyted.toString();
try {
const hash = JSON.parse(data);
const decipher = crypto.createDecipheriv(algorithm, secretKey, Buffer.from(hash.iv, 'hex'));
const decrpyted = Buffer.concat([decipher.update(Buffer.from(hash.content, 'hex')), decipher.final()]);
return decrpyted.toString();
} catch (error) {
console.error('Error while decrypting data', error);
return '{}';
}
};
const obscureKey = (key, key_spoiler_length = 6) => {
@@ -33,8 +38,85 @@ const obscureKey = (key, key_spoiler_length = 6) => {
return `${key.slice(0, key_spoiler_length)}${key_spoiler_char.repeat(key.length - key_spoiler_length)}`;
};
function isObscureKey(bucketCredentials) {
if (!bucketCredentials) {
return false;
}
try {
const {
vendor,
secret_access_key = '',
service_key = '',
connection_string = ''
} = bucketCredentials || {};
let pattern;
switch (vendor) {
case 'aws_s3':
case 's3_compatible':
pattern = /^([A-Za-z0-9]{4,6}X+$)/;
return pattern.test(secret_access_key);
case 'azure':
pattern = /^([A-Za-z0-9:]{4,6}X+$)/;
return pattern.test(connection_string);
case 'google': {
pattern = /^([A-Za-z0-9]{4,6}X+$)/;
let {private_key} = JSON.parse(service_key);
const key_header = '-----BEGIN PRIVATE KEY-----\n';
private_key = private_key.slice(key_header.length, private_key.length);
return pattern.test(private_key || '');
}
}
return false;
} catch (error) {
console.log('Error in isObscureKey', error);
return false;
}
}
/**
* obscure sensitive data in bucket credentials
* an obscured key contains of 6 'spoiled' characters of the key followed by 'X' characters
* '123456XXXXXXXXXXXXXXXXXXXXXXXX'
* @param {*} obj
* @returns
*/
function obscureBucketCredentialsSensitiveData(obj) {
if (!obj) return obj;
const {vendor, service_key, connection_string, secret_access_key} = obj;
switch (vendor) {
case 'aws_s3':
case 's3_compatible':
obj.secret_access_key = obscureKey(secret_access_key);
break;
case 'google':
const o = JSON.parse(service_key);
let private_key = o.private_key;
if (!isObscureKey(obj)) {
const key_header = '-----BEGIN PRIVATE KEY-----\n';
private_key = o.private_key.slice(key_header.length, o.private_key.length);
private_key = `${key_header}${obscureKey(private_key)}`;
}
const obscured = {
...o,
private_key
};
obj.service_key = JSON.stringify(obscured);
break;
case 'azure':
obj.connection_string = obscureKey(connection_string);
break;
}
return obj;
}
module.exports = {
encrypt,
decrypt,
obscureKey
obscureKey,
isObscureKey,
obscureBucketCredentialsSensitiveData,
};

View File

@@ -27,11 +27,17 @@ class DbErrorForbidden extends DbError {
super(msg);
}
}
class UserPermissionError extends Error {
constructor(msg) {
super(msg);
}
}
module.exports = {
BadRequestError,
DbError,
DbErrorBadRequest,
DbErrorUnprocessableRequest,
DbErrorForbidden
DbErrorForbidden,
UserPermissionError
};

View File

@@ -1,15 +1,11 @@
const debug = require('debug')('jambonz:api-server');
const bent = require('bent');
const basicAuth = (apiKey) => {
const header = `Bearer ${apiKey}`;
return {Authorization: header};
};
const postJSON = bent(process.env.HOMER_BASE_URL || 'http://127.0.0.1', 'POST', 'json', 200, 201);
const postPcap = bent(process.env.HOMER_BASE_URL || 'http://127.0.0.1', 'POST', 200, {
'Content-Type': 'application/json',
'Accept': 'application/json, text/plain, */*',
});
const { Readable } = require('stream');
const SEVEN_DAYS_IN_MS = (1000 * 3600 * 24 * 7);
const HOMER_BASE_URL = process.env.HOMER_BASE_URL || 'http://127.0.0.1';
const getHomerApiKey = async(logger) => {
if (!process.env.HOMER_BASE_URL || !process.env.HOMER_USERNAME || !process.env.HOMER_PASSWORD) {
@@ -17,11 +13,21 @@ const getHomerApiKey = async(logger) => {
}
try {
const obj = await postJSON('/api/v3/auth', {
username: process.env.HOMER_USERNAME,
password: process.env.HOMER_PASSWORD
const response = await fetch(`${HOMER_BASE_URL}/api/v3/auth`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: process.env.HOMER_USERNAME,
password: process.env.HOMER_PASSWORD
})
});
debug(obj);
if (!response.ok) {
logger.error({response}, 'Error retrieving apikey');
return;
}
const obj = await response.json();
logger.debug({obj}, `getHomerApiKey for user ${process.env.HOMER_USERNAME}`);
return obj.token;
} catch (err) {
@@ -36,28 +42,40 @@ const getHomerSipTrace = async(logger, apiKey, callId) => {
}
try {
const now = Date.now();
const obj = await postJSON('/api/v3/call/transaction', {
param: {
transaction: {
call: true,
registration: true,
rest: false
},
orlogic: true,
search: {
'1_call': {
callid: [callId]
},
'1_registration': {
callid: [callId]
}
},
const response = await fetch(`${HOMER_BASE_URL}/api/v3/call/transaction`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...basicAuth(apiKey)
},
timestamp: {
from: now - SEVEN_DAYS_IN_MS,
to: now
}
}, basicAuth(apiKey));
body: JSON.stringify({
param: {
transaction: {
call: true,
registration: true,
rest: false
},
orlogic: true,
search: {
'1_call': {
callid: [callId]
},
'1_registration': {
callid: [callId]
}
},
},
timestamp: {
from: now - SEVEN_DAYS_IN_MS,
to: now
}
})
});
if (!response.ok) {
logger.error({response}, 'Error retrieving messages');
return;
}
const obj = await response.json();
return obj;
} catch (err) {
logger.info({err}, `getHomerSipTrace: Error retrieving messages for callid ${callId}`);
@@ -70,34 +88,45 @@ const getHomerPcap = async(logger, apiKey, callIds, method) => {
}
try {
const now = Date.now();
const stream = await postPcap('/api/v3/export/call/messages/pcap', {
param: {
transaction: {
call: method === 'invite',
registration: method === 'register',
rest: false
},
orlogic: true,
search: {
...(method === 'invite' && {
'1_call': {
callid: callIds
}
})
,
...(method === 'register' && {
'1_registration': {
callid: callIds
}
})
},
const response = await fetch(`${HOMER_BASE_URL}/api/v3/export/call/messages/pcap`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...basicAuth(apiKey)
},
timestamp: {
from: now - SEVEN_DAYS_IN_MS,
to: now
}
}, basicAuth(apiKey));
return stream;
body: JSON.stringify({
param: {
transaction: {
call: method === 'invite',
registration: method === 'register',
rest: false
},
orlogic: true,
search: {
...(method === 'invite' && {
'1_call': {
callid: callIds
}
})
,
...(method === 'register' && {
'1_registration': {
callid: callIds
}
})
},
},
timestamp: {
from: now - SEVEN_DAYS_IN_MS,
to: now
}
})
});
if (!response.ok) {
logger.error({response}, 'Error retrieving messages');
return;
}
return Readable.fromWeb(response.body);
} catch (err) {
logger.info({err}, `getHomerPcap: Error retrieving messages for callid ${callIds}`);
}

View File

@@ -1,5 +1,4 @@
const bent = require('bent');
const getJSON = bent(process.env.JAEGER_BASE_URL || 'http://127.0.0.1', 'GET', 'json', 200);
const JAEGER_BASE_URL = process.env.JAEGER_BASE_URL || 'http://127.0.0.1';
const getJaegerTrace = async(logger, traceId) => {
if (!process.env.JAEGER_BASE_URL) {
@@ -7,7 +6,12 @@ const getJaegerTrace = async(logger, traceId) => {
return null;
}
try {
return await getJSON(`/api/v3/traces/${traceId}`);
const response = await fetch(`${JAEGER_BASE_URL}/api/v3/traces/${traceId}`);
if (!response.ok) {
logger.error({response}, 'Error retrieving spans');
return;
}
return await response.json();
} catch (err) {
const url = `${process.env.JAEGER_BASE_URL}/api/traces/${traceId}`;
logger.error({err, traceId}, `getJaegerTrace: Error retrieving spans from ${url}`);

View File

@@ -1,7 +1,4 @@
const assert = require('assert');
const bent = require('bent');
const postJSON = bent('POST', 'json', 200);
const getJSON = bent('GET', 'json', 200);
const {emailSimpleText} = require('./email-utils');
const {DbErrorForbidden} = require('../utils/errors');
@@ -10,13 +7,26 @@ const doGithubAuth = async(logger, payload) => {
try {
/* exchange the code for an access token */
const obj = await postJSON('https://github.com/login/oauth/access_token', {
client_id: payload.oauth2_client_id,
client_secret: process.env.GITHUB_CLIENT_SECRET,
code: payload.oauth2_code,
state: payload.oauth2_state,
redirect_uri: payload.oauth2_redirect_uri
const response = await fetch('https://github.com/login/oauth/access_token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json'
},
body: JSON.stringify({
client_id: payload.oauth2_client_id,
client_secret: process.env.GITHUB_CLIENT_SECRET,
code: payload.oauth2_code,
state: payload.oauth2_state,
redirect_uri: payload.oauth2_redirect_uri
})
});
if (!response.ok) {
logger.error({response}, 'Error retrieving access_token from github');
throw new DbErrorForbidden(await response.text());
}
const obj = await response.json();
if (!obj.access_token) {
logger.error({obj}, 'Error retrieving access_token from github');
if (obj.error === 'bad_verification_code') throw new Error('bad verification code');
@@ -25,17 +35,31 @@ const doGithubAuth = async(logger, payload) => {
logger.debug({obj}, 'got response from github for access_token');
/* use the access token to get basic public info as well as primary email */
const userDetails = await getJSON('https://api.github.com/user', null, {
Authorization: `Bearer ${obj.access_token}`,
Accept: 'application/json',
'User-Agent': 'jambonz 1.0'
const userResponse = await fetch('https://api.github.com/user', {
headers: {
Authorization: `Bearer ${obj.access_token}`,
Accept: 'application/json',
'User-Agent': 'jambonz 1.0'
}
});
if (!userResponse.ok) {
logger.error({userResponse}, 'Error retrieving user details from github');
throw new DbErrorForbidden(await userResponse.text());
}
const userDetails = await userResponse.json();
const emails = await getJSON('https://api.github.com/user/emails', null, {
Authorization: `Bearer ${obj.access_token}`,
Accept: 'application/json',
'User-Agent': 'jambonz 1.0'
const emailsResponse = await fetch('https://api.github.com/user/emails', {
headers: {
Authorization: `Bearer ${obj.access_token}`,
Accept: 'application/json',
'User-Agent': 'jambonz 1.0'
}
});
if (!emailsResponse.ok) {
logger.error({emailsResponse}, 'Error retrieving emails from github');
throw new DbErrorForbidden(await emailsResponse.text());
}
const emails = await emailsResponse.json();
const primary = emails.find((e) => e.primary);
if (primary) Object.assign(userDetails, {
email: primary.email,
@@ -55,14 +79,26 @@ const doGoogleAuth = async(logger, payload) => {
try {
/* exchange the code for an access token */
const obj = await postJSON('https://oauth2.googleapis.com/token', {
client_id: payload.oauth2_client_id,
client_secret: process.env.GOOGLE_OAUTH_CLIENT_SECRET,
code: payload.oauth2_code,
state: payload.oauth2_state,
redirect_uri: payload.oauth2_redirect_uri,
grant_type: 'authorization_code'
const response = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json'
},
body: JSON.stringify({
client_id: payload.oauth2_client_id,
client_secret: process.env.GOOGLE_OAUTH_CLIENT_SECRET,
code: payload.oauth2_code,
state: payload.oauth2_state,
redirect_uri: payload.oauth2_redirect_uri,
grant_type: 'authorization_code'
})
});
if (!response.ok) {
logger.error({response}, 'Error retrieving access_token from google');
throw new DbErrorForbidden(await response.text());
}
const obj = await response.json();
if (!obj.access_token) {
logger.error({obj}, 'Error retrieving access_token from github');
if (obj.error === 'bad_verification_code') throw new Error('bad verification code');
@@ -71,12 +107,18 @@ const doGoogleAuth = async(logger, payload) => {
logger.debug({obj}, 'got response from google for access_token');
/* use the access token to get basic public info as well as primary email */
const userDetails = await getJSON('https://www.googleapis.com/oauth2/v2/userinfo', null, {
Authorization: `Bearer ${obj.access_token}`,
Accept: 'application/json',
'User-Agent': 'jambonz 1.0'
const userDetailsResponse = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
headers: {
Authorization: `Bearer ${obj.access_token}`,
Accept: 'application/json',
'User-Agent': 'jambonz 1.0'
}
});
if (!userDetailsResponse.ok) {
logger.error({userDetailsResponse}, 'Error retrieving user details from google');
throw new DbErrorForbidden(await userDetailsResponse.text());
}
const userDetails = await userDetailsResponse.json();
logger.info({userDetails}, 'retrieved user details from google');
return userDetails;
} catch (err) {

View 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' },
];

View File

@@ -0,0 +1,57 @@
module.exports = [
{ name: 'Arabic, Gulf', value: 'ar-AE' },
{ name: 'Arabic, Modern Standard', value: 'ar-SA' },
{ name: 'Afrikaans', value: 'af-ZA' },
{ name: 'Basque', value: 'eu-ES' },
{ name: 'Catalan', value: 'ca-ES' },
{ name: 'Chinese, Simplified', value: 'zh-CN' },
{ name: 'Chinese, Traditional', value: 'zh-TW' },
{ name: 'Chinese (Cantonese), Hong-Kong', value: 'zh-HK' },
{ name: 'Croatian', value: 'hr-HR' },
{ name: 'Czech', value: 'cs-CZ' },
{ name: 'Danish', value: 'da-DK' },
{ name: 'Dutch', value: 'nl-NL' },
{ name: 'Australian English', value: 'en-AU' },
{ name: 'British English', value: 'en-GB' },
{ name: 'US English', value: 'en-US' },
{ name: 'Indian English', value: 'en-IN' },
{ name: 'Irish English', value: 'en-IE' },
{ name: 'New Zealand English', value: 'en-NZ' },
{ name: 'Scottish English', value: 'en-AB' },
{ name: 'South African English', value: 'en-ZA' },
{ name: 'Welsh English', value: 'en-WL' },
{ name: 'Farsi', value: 'fa-IR' },
{ name: 'Finnish', value: 'fi-FI' },
{ name: 'French', value: 'fr-FR' },
{ name: 'Canadian French', value: 'fr-CA' },
{ name: 'Galician', value: 'gl-ES' },
{ name: 'German', value: 'de-DE' },
{ name: 'Swiss German', value: 'de-CH' },
{ name: 'Greek', value: 'el-GR' },
{ name: 'Hindi', value: 'hi-IN' },
{ name: 'Hebrew', value: 'he-IL' },
{ name: 'Italian', value: 'it-IT' },
{ name: 'Indian Hindi', value: 'hi-IN' },
{ name: 'Indonesian', value: 'id-ID' },
{ name: 'Japanese', value: 'ja-JP' },
{ name: 'Korean', value: 'ko-KR' },
{ name: 'Latvian', value: 'lv-LV' },
{ name: 'Malay', value: 'ms-MY' },
{ name: 'Norwegian Bokmål', value: 'no-NO' },
{ name: 'Polish', value: 'pl-PL' },
{ name: 'Portuguese', value: 'pt-PT' },
{ name: 'Brazilian Portuguese', value: 'pt-BR' },
{ name: 'Romanian', value: 'ro-RO' },
{ name: 'Russian', value: 'ru-RU' },
{ name: 'Serbian', value: 'sr-RS' },
{ name: 'Slovak', value: 'sk-SK' },
{ name: 'Somali', value: 'so-SO' },
{ name: 'Spanish', value: 'es-ES' },
{ name: 'US Spanish', value: 'es-US' },
{ name: 'Swedish', value: 'sv-SE' },
{ name: 'Tagalog/Filipino', value: 'tl-PH' },
{ name: 'Thai', value: 'th-TH' },
{ name: 'Ukrainian', value: 'uk-UA' },
{ name: 'Vietnamese', value: 'vi-VN' },
{ name: 'Zulu', value: 'zu-ZA' }
];

View 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',
},
];

View File

@@ -0,0 +1,56 @@
module.exports = [
{ name: 'Multilingual', value: 'multi' },
{ name: 'Bulgarian', value: 'bg' },
{ name: 'Catalan', value: 'ca' },
{ name: 'Chinese (Mandarin, Simplified)', value: 'zh' },
{ name: 'Chinese (Mandarin, Simplified - China)', value: 'zh-CN' },
{ name: 'Chinese (Mandarin, Simplified - Hans)', value: 'zh-Hans' },
{ name: 'Chinese (Mandarin, Traditional)', value: 'zh-TW' },
{ name: 'Chinese (Mandarin, Traditional - Hant)', value: 'zh-Hant' },
{ name: 'Chinese (Cantonese, Traditional - Hong Kong)', value: 'zh-HK' },
{ name: 'Czech', value: 'cs' },
{ name: 'Danish', value: 'da' },
{ name: 'Danish (Denmark)', value: 'da-DK' },
{ name: 'Dutch', value: 'nl' },
{ name: 'English', value: 'en' },
{ name: 'English (United States)', value: 'en-US' },
{ name: 'English (Australia)', value: 'en-AU' },
{ name: 'English (United Kingdom)', value: 'en-GB' },
{ name: 'English (New Zealand)', value: 'en-NZ' },
{ name: 'English (India)', value: 'en-IN' },
{ name: 'Estonian', value: 'et' },
{ name: 'Finnish', value: 'fi' },
{ name: 'Flemish', value: 'nl-BE' },
{ name: 'French', value: 'fr' },
{ name: 'French (Canada)', value: 'fr-CA' },
{ name: 'German', value: 'de' },
{ name: 'German (Switzerland)', value: 'de-CH' },
{ name: 'Greek', value: 'el' },
{ name: 'Hindi', value: 'hi' },
{ name: 'Hungarian', value: 'hu' },
{ name: 'Indonesian', value: 'id' },
{ name: 'Italian', value: 'it' },
{ name: 'Japanese', value: 'ja' },
{ name: 'Korean', value: 'ko' },
{ name: 'Korean (South Korea)', value: 'ko-KR' },
{ name: 'Latvian', value: 'lv' },
{ name: 'Lithuanian', value: 'lt' },
{ name: 'Malay', value: 'ms' },
{ name: 'Norwegian', value: 'no' },
{ name: 'Polish', value: 'pl' },
{ name: 'Portuguese', value: 'pt' },
{ name: 'Portuguese (Brazil)', value: 'pt-BR' },
{ name: 'Portuguese (Portugal)', value: 'pt-PT' },
{ name: 'Romanian', value: 'ro' },
{ name: 'Russian', value: 'ru' },
{ name: 'Slovak', value: 'sk' },
{ name: 'Spanish', value: 'es' },
{ name: 'Spanish (Latin America)', value: 'es-419' },
{ name: 'Swedish', value: 'sv' },
{ name: 'Swedish (Sweden)', value: 'sv-SE' },
{ name: 'Thai', value: 'th' },
{ name: 'Thai (Thailand)', value: 'th-TH' },
{ name: 'Turkish', value: 'tr' },
{ name: 'Ukrainian', value: 'uk' },
{ name: 'Vietnamese', value: 'vi' }
];

View 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' },
];

View 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',
},
];

View 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',
},
];

View File

@@ -0,0 +1,4 @@
module.exports = [
{ name: 'Ink-whisper', value: 'ink-whisper' },
];

View File

@@ -0,0 +1,52 @@
module.exports = [
// Nova-3
{ name: 'Nova-3', value: 'nova-3' },
{ name: 'Nova-3 General', value: 'nova-3-general' },
{ name: 'Nova-3 Medical', value: 'nova-3-medical' },
// Nova-2
{ name: 'Nova-2', value: 'nova-2' },
{ name: 'Nova-2 General', value: 'nova-2-general' },
{ name: 'Nova-2 Meeting', value: 'nova-2-meeting' },
{ name: 'Nova-2 Phonecall', value: 'nova-2-phonecall' },
{ name: 'Nova-2 Finance', value: 'nova-2-finance' },
{ name: 'Nova-2 Conversational AI', value: 'nova-2-conversationalai' },
{ name: 'Nova-2 Voicemail', value: 'nova-2-voicemail' },
{ name: 'Nova-2 Video', value: 'nova-2-video' },
{ name: 'Nova-2 Medical', value: 'nova-2-medical' },
{ name: 'Nova-2 Drivethru', value: 'nova-2-drivethru' },
{ name: 'Nova-2 Automotive', value: 'nova-2-automotive' },
{ name: 'Nova-2 ATC', value: 'nova-2-atc' },
// Nova (legacy)
{ name: 'Nova', value: 'nova' },
{ name: 'Nova General', value: 'nova-general' },
{ name: 'Nova Phonecall', value: 'nova-phonecall' },
{ name: 'Nova Medical', value: 'nova-medical' },
// Enhanced (legacy)
{ name: 'Enhanced', value: 'enhanced' },
{ name: 'Enhanced General', value: 'enhanced-general' },
{ name: 'Enhanced Meeting', value: 'enhanced-meeting' },
{ name: 'Enhanced Phonecall', value: 'enhanced-phonecall' },
{ name: 'Enhanced Finance', value: 'enhanced-finance' },
// Base (legacy)
{ name: 'Base', value: 'base' },
{ name: 'Base General', value: 'base-general' },
{ name: 'Base Meeting', value: 'base-meeting' },
{ name: 'Base Phonecall', value: 'base-phonecall' },
{ name: 'Base Finance', value: 'base-finance' },
{ name: 'Base Conversational AI', value: 'base-conversationalai' },
{ name: 'Base Voicemail', value: 'base-voicemail' },
{ name: 'Base Video', value: 'base-video' },
// Whisper
{ name: 'Whisper Tiny', value: 'whisper-tiny' },
{ name: 'Whisper Base', value: 'whisper-base' },
{ name: 'Whisper Small', value: 'whisper-small' },
{ name: 'Whisper Medium', value: 'whisper-medium' },
{ name: 'Whisper Large', value: 'whisper-large' },
{ name: 'Whisper', value: 'whisper' },
];

View File

@@ -0,0 +1,6 @@
module.exports = [
{ name: 'Whisper', value: 'whisper-1' },
{ name: 'GPT 4o Mini Transcribe', value: 'gpt-4o-mini-transcribe' },
{ name: 'GLT 4o Transcribe', value: 'gpt-4o-transcribe' },
];

View 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',
},
];

View 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',
},
];

View File

@@ -0,0 +1,59 @@
module.exports = [
{ name: 'Afrikaans', value: 'af' },
{ name: 'Arabic', value: 'ar' },
{ name: 'Azerbaijani', value: 'az' },
{ name: 'Belarusian', value: 'be' },
{ name: 'Bulgarian', value: 'bg' },
{ name: 'Bosnian', value: 'bs' },
{ name: 'Catalan', value: 'ca' },
{ name: 'Czech', value: 'cs' },
{ name: 'Welsh', value: 'cy' },
{ name: 'Danish', value: 'da' },
{ name: 'German', value: 'de' },
{ name: 'Greek', value: 'el' },
{ name: 'English', value: 'en' },
{ name: 'Spanish', value: 'es' },
{ name: 'Estonian', value: 'et' },
{ name: 'Persian', value: 'fa' },
{ name: 'Finnish', value: 'fi' },
{ name: 'French', value: 'fr' },
{ name: 'Galician', value: 'gl' },
{ name: 'Hebrew', value: 'he' },
{ name: 'Hindi', value: 'hi' },
{ name: 'Croatian', value: 'hr' },
{ name: 'Hungarian', value: 'hu' },
{ name: 'Armenian', value: 'hy' },
{ name: 'Indonesian', value: 'id' },
{ name: 'Icelandic', value: 'is' },
{ name: 'Italian', value: 'it' },
{ name: 'Japanese', value: 'ja' },
{ name: 'Kazakh', value: 'kk' },
{ name: 'Kannada', value: 'kn' },
{ name: 'Korean', value: 'ko' },
{ name: 'Lithuanian', value: 'lt' },
{ name: 'Latvian', value: 'lv' },
{ name: 'Maori', value: 'mi' },
{ name: 'Macedonian', value: 'mk' },
{ name: 'Marathi', value: 'mr' },
{ name: 'Malay', value: 'ms' },
{ name: 'Nepali', value: 'ne' },
{ name: 'Dutch', value: 'nl' },
{ name: 'Norwegian', value: 'no' },
{ name: 'Polish', value: 'pl' },
{ name: 'Portuguese', value: 'pt' },
{ name: 'Romanian', value: 'ro' },
{ name: 'Russian', value: 'ru' },
{ name: 'Slovak', value: 'sk' },
{ name: 'Slovenian', value: 'sl' },
{ name: 'Serbian', value: 'sr' },
{ name: 'Swedish', value: 'sv' },
{ name: 'Swahili', value: 'sw' },
{ name: 'Tamil', value: 'ta' },
{ name: 'Thai', value: 'th' },
{ name: 'Tagalog', value: 'tl' },
{ name: 'Turkish', value: 'tr' },
{ name: 'Ukrainian', value: 'uk' },
{ name: 'Urdu', value: 'ur' },
{ name: 'Vietnamese', value: 'vi' },
{ name: 'Chinese', value: 'zh' },
];

View File

@@ -0,0 +1,6 @@
module.exports = [
{
name: 'English (United States)',
value: 'en-US',
},
];

View File

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

View File

@@ -0,0 +1,14 @@
module.exports = [
{ name: 'US English', value: 'en-US' },
{ name: 'British English', value: 'en-GB' },
{ name: 'LATAM Spanish', value: 'en-USes-419' },
{ name: 'Spanish', value: 'es' },
{ name: 'Catalan', value: 'ca-ES', version: 'v2' },
{ name: 'Brazilian Portuguese', value: 'pt-BR' },
{ name: 'French', value: 'fr', version: 'v1' },
{ name: 'Canadian French', value: 'fr-CA', version: 'v1' },
{ name: 'German', value: 'de', version: 'v1' },
{ name: 'Italian', value: 'it', version: 'v1' },
{ name: 'Turkish', value: 'tr', version: 'v1' },
{ name: 'Japanese', value: 'ja', version: 'v1' },
];

View File

@@ -0,0 +1,8 @@
module.exports = [
{ name: 'English', value: 'en' },
{ name: 'French', value: 'fr' },
{ name: 'German', value: 'de' },
{ name: 'Dutch', value: 'nl' },
{ name: 'Italian', value: 'it' },
{ name: 'Spanish', value: 'sp' },
];

View 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)' }],
},
];

View File

@@ -0,0 +1,301 @@
/* eslint-disable max-len */
module.exports = [
{
value: 'en',
name: 'English',
voices: [
{
value: '79f8b5fb-2cc8-479a-80df-29f7a7cf1a3e',
name: 'Nonfiction Man - This voice is smooth, confident, and resonant, perfect for narrating educational content',
},
{
value: 'e00d0e4c-a5c8-443f-a8a3-473eb9a62355',
name: 'Friendly Sidekick - This voice is friendly and supportive, designed for voicing characters in games and videos',
},
{
value: '3b554273-4299-48b9-9aaf-eefd438e3941',
name: 'Indian Lady - This voice is young, rich, and curious, perfect for a narrator or fictional character',
},
{
value: '71a7ad14-091c-4e8e-a314-022ece01c121',
name: 'British Reading Lady - This is a calm and elegant voice with a British accent, perfect for storytelling and narration',
},
{
value: '4d2fd738-3b3d-4368-957a-bb4805275bd9',
name: 'British Narration Lady - This is a neutral voice with a British accent, perfect for narrations ',
},
{
value: '15a9cd88-84b0-4a8b-95f2-5d583b54c72e',
name: 'Reading Lady - This voice is monotone and deliberate, perfect for a slower-paced and more serious reading voice',
},
{
value: 'd46abd1d-2d02-43e8-819f-51fb652c1c61',
name: 'Newsman - This voice is neutral and educational, perfect for a news anchor',
},
{
value: '2ee87190-8f84-4925-97da-e52547f9462c',
name: 'Child - This voice is young and full, perfect for a child',
},
{
value: 'cd17ff2d-5ea4-4695-be8f-42193949b946',
name: 'Meditation Lady - This voice is calm, soothing, and relaxing, perfect for meditation',
},
{
value: '5345cf08-6f37-424d-a5d9-8ae1101b9377',
name: 'Maria - This voice is laid back, natural, and conversational, like you\'re catching up with a good friend',
},
{
value: '41534e16-2966-4c6b-9670-111411def906',
name: '1920\'s Radioman - This voice is energetic and confident, great for an entertainer or radio host',
},
{
value: 'bf991597-6c13-47e4-8411-91ec2de5c466',
name: 'Newslady - This voice is authoritative and educational, perfect for a news anchor',
},
{
value: '00a77add-48d5-4ef6-8157-71e5437b282d',
name: 'Calm Lady - This voice is calm and nurturing, perfect for a narrator',
},
{
value: '156fb8d2-335b-4950-9cb3-a2d33befec77',
name: 'Helpful Woman - This voice is friendly and conversational, designed for customer support agents and casual conversations',
},
{
value: '36b42fcb-60c5-4bec-b077-cb1a00a92ec6',
name: 'Pilot over Intercom - This voice sounds like a British Pilot character speaking over an Intercom',
},
{
value: 'f146dcec-e481-45be-8ad2-96e1e40e7f32',
name: 'Reading Man - Male with calm narrational voice.',
},
{
value: '34575e71-908f-4ab6-ab54-b08c95d6597d',
name: 'New York Man - This voice is compelling and husky, with a New York accent, perfect for sales pitches and motivational content',
},
{
value: 'a0e99841-438c-4a64-b679-ae501e7d6091',
name: 'Barbershop Man - This voice is smooth and relaxing, perfect for a casual conversation',
},
{
value: '638efaaa-4d0c-442e-b701-3fae16aad012',
name: 'Indian Man - This voice is smooth with an Indian accent, perfect for a narrator',
},
{
value: '41f3c367-e0a8-4a85-89e0-c27bae9c9b6d',
name: 'Australian Customer Support Man - This voice is warm with an Australian accent, perfect for customer support agents',
},
{
value: '421b3369-f63f-4b03-8980-37a44df1d4e8',
name: 'Friendly Australian Man - This voice is rich and deep, with an Australian accent, perfect for casual conversations with a friend',
},
{
value: 'b043dea0-a007-4bbe-a708-769dc0d0c569',
name: 'Wise Man - This is a deep and deliberate voice, suited for educational content and conversations',
},
{
value: '69267136-1bdc-412f-ad78-0caad210fb40',
name: 'Friendly Reading Man - This voice is energetic and friendly, like having your friend read his favorite book to you',
},
{
value: 'a167e0f3-df7e-4d52-a9c3-f949145efdab',
name: 'Customer Support Man - This voice is clear and calm, perfect for a call center',
},
{
value: '4f8651b0-bbbd-46ac-8b37-5168c5923303',
name: 'Kentucky Woman - This voice is energetic and upbeat, with a slight Kentucky accent, perfect for speeches and rallies',
},
{
value: 'daf747c6-6bc2-4083-bd59-aa94dce23f5d',
name: 'Middle Eastern Woman - This voice is clear with a Middle Eastern Accent, perfect for a narrator',
},
{
value: '694f9389-aac1-45b6-b726-9d9369183238',
name: 'Sarah - This voice is natural and expressive with an American accent, perfect for a wide range of conversational use cases including customer support, sales, reception, and more.',
},
{
value: '794f9389-aac1-45b6-b726-9d9369183238',
name: 'Sarah Curious - This voice is similar to Sarah, but has improved emphasis for questions.',
},
{
value: '21b81c14-f85b-436d-aff5-43f2e788ecf8',
name: 'Laidback Woman - This voice is laid back and husky, with a slight Californian accent',
},
{
value: 'a3520a8f-226a-428d-9fcd-b0a4711a6829',
name: 'Reflective Woman - This voice is even, full, and reflective, perfect for a young narrator for an audiobook or movie',
},
{
value: '829ccd10-f8b3-43cd-b8a0-4aeaa81f3b30',
name: 'Customer Support Lady - This voice is polite and helpful, perfect for customer support agents',
},
{
value: '79a125e8-cd45-4c13-8a67-188112f4dd22',
name: 'British Lady - This voice is elegant with a slight British accent, perfect for storytelling and narrating',
},
{
value: 'c8605446-247c-4d39-acd4-8f4c28aa363c',
name: 'Wise Lady - This voice is wise and authoritative, perfect for a confident narrator',
},
{
value: '8985388c-1332-4ce7-8d55-789628aa3df4',
name: 'Australian Narrator Lady - This voice is even and neutral, with an Australian accent, designed for narrating content and stories',
},
{
value: 'ff1bb1a9-c582-4570-9670-5f46169d0fc8',
name: 'Indian Customer Support Lady - This voice is clear and polite, with an Indian accent, suitable for customer support agents',
},
{
value: '820a3788-2b37-4d21-847a-b65d8a68c99a',
name: 'Salesman - This voice is smooth and persuasive, perfect for sales pitches and phone conversations',
},
{
value: 'f114a467-c40a-4db8-964d-aaba89cd08fa',
name: 'Yogaman - This voice is calm, soothing, and stable, perfect for a yoga instructor',
},
{
value: 'c45bc5ec-dc68-4feb-8829-6e6b2748095d',
name: 'Movieman - This voice is deep, resonant, and assertive, perfect for a movie narrator',
},
{
value: '87748186-23bb-4158-a1eb-332911b0b708',
name: 'Wizardman - This voice is wise and mysterious, perfect for a Wizard character',
},
{
value: '043cfc81-d69f-4bee-ae1e-7862cb358650',
name: 'Australian Woman - This voice is deliberate and confident, with a slight Australian accent, perfect for inspiring characters in videos and stories',
},
{
value: '5619d38c-cf51-4d8e-9575-48f61a280413',
name: 'Announcer Man - This voice is deep and inviting, perfect for entertainment and broadcasting content',
},
{
value: '42b39f37-515f-4eee-8546-73e841679c1d',
name: 'Wise Guide Man - This voice is deep and deliberate, perfect for inspiring and guiding characters in games and videos',
},
{
value: '565510e8-6b45-45de-8758-13588fbaec73',
name: 'Midwestern Man - This voice is neutral and smooth, with a slight midwestern accent, perfect for narrations',
},
{
value: '726d5ae5-055f-4c3d-8355-d9677de68937',
name: 'Kentucky Man - This voice is laidback and smooth, with a Kentucky accent, perfect for a casual conversation',
},
{
value: '63ff761f-c1e8-414b-b969-d1833d1c870c',
name: 'Confident British Man - This voice is disciplined with a British accent, perfect for a commanding character or narrator',
},
{
value: '98a34ef2-2140-4c28-9c71-663dc4dd7022',
name: 'Southern Man - This voice is warm with a Southern accent, perfect for a narrator',
},
{
value: '95856005-0332-41b0-935f-352e296aa0df',
name: 'Classy British Man - This voice is light and smooth with a British accent, perfect for casual conversation',
},
{
value: 'ee7ea9f8-c0c1-498c-9279-764d6b56d189',
name: 'Polite Man - This voice is polite and conversational, with a slight accent, designed for customer support and casual conversations',
},
{
value: '40104aff-a015-4da1-9912-af950fbec99e',
name: 'Alabama Male - This voice has a strong Southern Accent, perfect for conversations and instructional videos',
},
{
value: '13524ffb-a918-499a-ae97-c98c7c4408c4',
name: 'Australian Male - This voice is smooth and disciplined, with an Australian Accent, suited for narrating educational content',
},
{
value: '1001d611-b1a8-46bd-a5ca-551b23505334',
name: 'Anime Girl - This voice is expressive and has a high pitch, suitable for anime or gaming characters',
},
{
value: 'e3827ec5-697a-4b7c-9704-1a23041bbc51',
name: 'Sweet Lady - This voice is sweet and passionate, perfect for a character in a game or book',
},
{
value: 'c2ac25f9-ecc4-4f56-9095-651354df60c0',
name: 'Commercial Lady - This voice is inviting, enthusiastic, and relatable, perfect for a commercial or advertisement',
},
{
value: '573e3144-a684-4e72-ac2b-9b2063a50b53',
name: 'Teacher Lady - This voice is neutral and clear, perfect for narrating educational content',
},
{
value: '8f091740-3df1-4795-8bd9-dc62d88e5131',
name: 'Princess - This voice is light, freindly and has a flourish, perfect for character work in videos and games',
},
{
value: '7360f116-6306-4e9a-b487-1235f35a0f21',
name: 'Commercial Man - This voice is upbeat and enthusiastic, perfect for commercials and advertisements',
},
{
value: '03496517-369a-4db1-8236-3d3ae459ddf7',
name: 'ASMR Lady - This voice is calming and soft, perfect for guided meditations and soothing content',
},
{
value: '248be419-c632-4f23-adf1-5324ed7dbf1d',
name: 'Professional Woman - This voice is neutral and calm, perfect for a call center',
},
{
value: 'bd9120b6-7761-47a6-a446-77ca49132781',
name: 'Tutorial Man - This voice is inviting and calming, perfect for tutorials',
},
{
value: '34bde396-9fde-4ebf-ad03-e3a1d1155205',
name: 'New York Woman - This voice commands authority, with a New York accent, perfect for a commanding narrator or character',
},
{
value: '11af83e2-23eb-452f-956e-7fee218ccb5c',
name: 'Midwestern Woman - This voice is neutral and deliberate, with a midwestern accent, suitable for news broadcasts and narration',
},
{
value: 'ed81fd13-2016-4a49-8fe3-c0d2761695fc',
name: 'Sportsman - This voice is energetic and enthusiastic, perfect for a sports broadcaster',
},
{
value: '996a8b96-4804-46f0-8e05-3fd4ef1a87cd',
name: 'Storyteller Lady - This voice is neutral and smooth, with a slight Canadian accent, perfect for narrations',
},
{
value: 'fb26447f-308b-471e-8b00-8e9f04284eb5',
name: 'Doctor Mischief - This is an expressive character voice, suited to whimsical characters for games and educational content',
},
{
value: '50d6beb4-80ea-4802-8387-6c948fe84208',
name: 'The Merchant - This voice is playful and quirky, designed for character work in games and videos',
},
{
value: 'e13cae5c-ec59-4f71-b0a6-266df3c9bb8e',
name: 'Madame Mischief - This voice is mischeivious and playful, suitable for voicing characters for kids content and games',
},
{
value: '5c42302c-194b-4d0c-ba1a-8cb485c84ab9',
name: 'Female Nurse - This voice is clear and firm, perfect for nurse characters and instructional videos',
},
{
value: 'f9836c6e-a0bd-460e-9d3c-f7299fa60f94',
name: 'Southern Woman - This voice is friendly and inviting, with a slight Southern Accent, perfect for conversations and phone calls',
},
{
value: 'a01c369f-6d2d-4185-bc20-b32c225eab70',
name: 'British Customer Support Lady - This voice is friendly and polite, with a British accent, perfect for phone conversations',
},
{
value: 'b7d50908-b17c-442d-ad8d-810c63997ed9',
name: 'California Girl - This voice is enthusiastic and friendly, perfect for a casual conversation between friends',
},
{
value: 'f785af04-229c-4a7c-b71b-f3194c7f08bb',
name: 'John - This voice is natural and empathetic with an American accent, perfect for use cases like demos and customer support calls.',
},
{
value: '729651dc-c6c3-4ee5-97fa-350da1f88600',
name: 'Pleasant Man - A pleasant male voice that\'s good for use cases like demos and customer support calls',
},
{
value: '91b4cf29-5166-44eb-8054-30d40ecc8081',
name: 'Anna - This voice is natural and expressive with an American accent, perfect for use cases like interviews and customer support calls.',
},
],
},
];

View File

@@ -0,0 +1,28 @@
const TtsDeepgramLanguagesVoiceRaw = require('./tts-model-deepgram');
const languagesVoices = [];
TtsDeepgramLanguagesVoiceRaw.forEach((data) => {
const lang = languagesVoices.find((l) => {
return l.value === data.locale;
});
if (!lang) {
languagesVoices.push({
value: data.locale,
name: data.localeName,
voices: TtsDeepgramLanguagesVoiceRaw
.filter((d) => {
return d.locale === data.locale;
})
.map((d) => {
return {
value: d.value,
name: `${d.name}`,
};
}),
});
}
});
module.exports = languagesVoices;

View 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: [] },
];

View 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)' },
],
},
];

View 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)' },
],
},
];

View File

@@ -0,0 +1,118 @@
module.exports = [
{
value: 'en',
name: 'English',
voices: [
{ name: 'Alex', value: 'Alex' },
{ name: 'Ashley', value: 'Ashley' },
{ name: 'Craig', value: 'Craig' },
{ name: 'Deborah', value: 'Deborah' },
{ name: 'Dennis', value: 'Dennis' },
{ name: 'Edward', value: 'Edward' },
{ name: 'Elizabeth', value: 'Elizabeth' },
{ name: 'Hades', value: 'Hades' },
{ name: 'Julia', value: 'Julia' },
{ name: 'Pixie', value: 'Pixie' },
{ name: 'Mark', value: 'Mark' },
{ name: 'Olivia', value: 'Olivia' },
{ name: 'Priya', value: 'Priya' },
{ name: 'Ronald', value: 'Ronald' },
{ name: 'Sarah', value: 'Sarah' },
{ name: 'Shaun', value: 'Shaun' },
{ name: 'Theodore', value: 'Theodore' },
{ name: 'Timothy', value: 'Timothy' },
{ name: 'Wendy', value: 'Wendy' },
{ name: 'Dominus', value: 'Dominus' },
],
},
{
value: 'zh',
name: 'Chinese',
voices: [
{ name: 'Yichen', value: 'Yichen' },
{ name: 'Xiaoyin', value: 'Xiaoyin' },
{ name: 'Xinyi', value: 'Xinyi' },
{ name: 'Jing', value: 'Jing' },
],
},
{
value: 'nl',
name: 'Dutch',
voices: [
{ name: 'Erik', value: 'Erik' },
{ name: 'Katrien', value: 'Katrien' },
{ name: 'Lennart', value: 'Lennart' },
{ name: 'Lore', value: 'Lore' },
],
},
{
value: 'fr',
name: 'French',
voices: [
{ name: 'Alain', value: 'Alain' },
{ name: 'Hélène', value: 'Hélène' },
{ name: 'Mathieu', value: 'Mathieu' },
{ name: 'Étienne', value: 'Étienne' },
],
},
{
value: 'de',
name: 'German',
voices: [
{ name: 'Johanna', value: 'Johanna' },
{ name: 'Josef', value: 'Josef' },
],
},
{
value: 'it',
name: 'Italian',
voices: [
{ name: 'Gianni', value: 'Gianni' },
{ name: 'Orietta', value: 'Orietta' },
],
},
{
value: 'ja',
name: 'Japanese',
voices: [
{ name: 'Asuka', value: 'Asuka' },
{ name: 'Satoshi', value: 'Satoshi' },
],
},
{
value: 'ko',
name: 'Korean',
voices: [
{ name: 'Hyunwoo', value: 'Hyunwoo' },
{ name: 'Minji', value: 'Minji' },
{ name: 'Seojun', value: 'Seojun' },
{ name: 'Yoona', value: 'Yoona' },
],
},
{
value: 'pl',
name: 'Polish',
voices: [
{ name: 'Szymon', value: 'Szymon' },
{ name: 'Wojciech', value: 'Wojciech' },
],
},
{
value: 'pt',
name: 'Portuguese',
voices: [
{ name: 'Heitor', value: 'Heitor' },
{ name: 'Maitê', value: 'Maitê' },
],
},
{
value: 'es',
name: 'Spanish',
voices: [
{ name: 'Diego', value: 'Diego' },
{ name: 'Lupita', value: 'Lupita' },
{ name: 'Miguel', value: 'Miguel' },
{ name: 'Rafael', value: 'Rafael' },
],
},
];

View File

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

File diff suppressed because it is too large Load Diff

View 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;

View File

@@ -0,0 +1,35 @@
module.exports = [
{
name: 'Sonic',
value: 'sonic',
languages: ['en', 'fr', 'de', 'es', 'pt', 'zh', 'ja', 'hi', 'it', 'ko', 'nl', 'pl', 'ru', 'sv', 'tr']
},
{
name: 'Sonic 2',
value: 'sonic-2',
languages: ['en', 'fr', 'de', 'es', 'pt', 'zh', 'ja', 'hi', 'it', 'ko', 'nl', 'pl', 'ru', 'sv', 'tr']
},
{
name: 'Sonic Turbo',
value: 'sonic-turbo',
languages: ['en', 'fr', 'de', 'es', 'pt', 'zh', 'ja', 'hi', 'it', 'ko', 'nl', 'pl', 'ru', 'sv', 'tr']
},
{ name: 'Sonic Preview', value: 'sonic-preview', languages: ['en'] },
{
name: 'Sonic 2024-12-12',
value: 'sonic-2024-12-12',
languages: ['en', 'fr', 'de', 'es', 'pt', 'zh', 'ja', 'hi', 'it', 'ko', 'nl', 'pl', 'ru', 'sv', 'tr']
},
{
name: 'Sonic 2024-10-19',
value: 'sonic-2024-10-19',
languages: ['en', 'fr', 'de', 'es', 'pt', 'zh', 'ja', 'hi', 'it', 'ko', 'nl', 'pl', 'ru', 'sv', 'tr']
},
{ name: 'Sonic English', value: 'sonic-english', languages: ['en'] },
{
name: 'Sonic Multilingual',
value: 'sonic-multilingual',
languages: ['en', 'fr', 'de', 'es', 'pt', 'zh', 'ja', 'hi', 'it', 'ko', 'nl', 'pl', 'ru', 'sv', 'tr']
},
];

View File

@@ -0,0 +1,320 @@
module.exports = [
{
locale: 'en-ph',
localeName: 'English (PH)',
name: 'Amalthea English (PH) Female Aura-2',
value: 'aura-2-amalthea-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Andromeda English (US) Female Aura-2',
value: 'aura-2-andromeda-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Apollo English (US) Male Aura-2',
value: 'aura-2-apollo-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Arcas English (US) Male Aura-2',
value: 'aura-2-arcas-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Aries English (US) Male Aura-2',
value: 'aura-2-aries-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Asteria English (US) Female Aura-2',
value: 'aura-2-asteria-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Athena English (US) Female Aura-2',
value: 'aura-2-athena-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Atlas English (US) Male Aura-2',
value: 'aura-2-atlas-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Aurora English (US) Female Aura-2',
value: 'aura-2-aurora-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Callista English (US) Female Aura-2',
value: 'aura-2-callista-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Cora English (US) Female Aura-2',
value: 'aura-2-cora-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Cordelia English (US) Female Aura-2',
value: 'aura-2-cordelia-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Delia English (US) Female Aura-2',
value: 'aura-2-delia-en'
},
{
locale: 'en-gb',
localeName: 'English (GB)',
name: 'Draco English (GB) Male Aura-2',
value: 'aura-2-draco-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Electra English (US) Female Aura-2',
value: 'aura-2-electra-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Harmonia English (US) Female Aura-2',
value: 'aura-2-harmonia-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Helena English (US) Female Aura-2',
value: 'aura-2-helena-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Hera English (US) Female Aura-2',
value: 'aura-2-hera-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Hermes English (US) Male Aura-2',
value: 'aura-2-hermes-en'
},
{
locale: 'en-au',
localeName: 'English (AU)',
name: 'Hyperion English (AU) Male Aura-2',
value: 'aura-2-hyperion-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Iris English (US) Female Aura-2',
value: 'aura-2-iris-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Janus English (US) Female Aura-2',
value: 'aura-2-janus-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Juno English (US) Female Aura-2',
value: 'aura-2-juno-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Jupiter English (US) Male Aura-2',
value: 'aura-2-jupiter-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Luna English (US) Female Aura-2',
value: 'aura-2-luna-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Mars English (US) Male Aura-2',
value: 'aura-2-mars-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Minerva English (US) Female Aura-2',
value: 'aura-2-minerva-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Neptune English (US) Male Aura-2',
value: 'aura-2-neptune-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Odysseus English (US) Male Aura-2',
value: 'aura-2-odysseus-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Ophelia English (US) Female Aura-2',
value: 'aura-2-ophelia-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Orion English (US) Male Aura-2',
value: 'aura-2-orion-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Orpheus English (US) Male Aura-2',
value: 'aura-2-orpheus-en'
},
{
locale: 'en-gb',
localeName: 'English (GB)',
name: 'Pandora English (GB) Female Aura-2',
value: 'aura-2-pandora-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Phoebe English (US) Female Aura-2',
value: 'aura-2-phoebe-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Pluto English (US) Male Aura-2',
value: 'aura-2-pluto-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Saturn English (US) Male Aura-2',
value: 'aura-2-saturn-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Selene English (US) Female Aura-2',
value: 'aura-2-selene-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Thalia English (US) Female Aura-2',
value: 'aura-2-thalia-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Theia English (US) Female Aura-2',
value: 'aura-2-theia-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Vesta English (US) Female Aura-2',
value: 'aura-2-vesta-en'
},
{
locale: 'en-us',
localeName: 'English (US)',
name: 'Zeus English (US) Male Aura-2',
value: 'aura-2-zeus-en'
},
{
locale: 'en-US',
localeName: 'English (US)',
name: 'Asteria English (US) Female Aura-1',
value: 'aura-asteria-en'
},
{
locale: 'en-US',
localeName: 'English (US)',
name: 'Luna English (US) Female Aura-1',
value: 'aura-luna-en'
},
{
locale: 'en-US',
localeName: 'English (US)',
name: 'Stella English (US) Female Aura-1',
value: 'aura-stella-en'
},
{
locale: 'en-GB',
localeName: 'English (UK)',
name: 'Stella English (UK) Female Aura-1',
value: 'aura-athena-en'
},
{
locale: 'en-US',
localeName: 'English (US)',
name: 'Hera English (US) Female Aura-1',
value: 'aura-hera-en'
},
{
locale: 'en-US',
localeName: 'English (US)',
name: 'Orion English (US) Male Aura-1',
value: 'aura-orion-en'
},
{
locale: 'en-US',
localeName: 'English (US)',
name: 'Arcas English (US) Male Aura-1',
value: 'aura-arcas-en'
},
{
locale: 'en-US',
localeName: 'English (US)',
name: 'Perseus English (US) Male Aura-1',
value: 'aura-perseus-en'
},
{
locale: 'en-IE',
localeName: 'English (Ireland)',
name: 'Angus English (Ireland) Male Aura-1',
value: 'aura-angus-en'
},
{
locale: 'en-US',
localeName: 'English (US)',
name: 'Orpheus English (US) Male Aura-1',
value: 'aura-orpheus-en'
},
{
locale: 'en-gb',
localeName: 'English (US)',
name: 'Helios English (GB) Male Aura-1',
value: 'aura-helios-en'
},
{
locale: 'en-US',
localeName: 'English (US)',
name: 'Zeus English (US) Male Aura-1',
value: 'aura-zeus-en'
},
];

View File

@@ -0,0 +1,12 @@
module.exports = [
{ name: 'Turbo v2', value: 'eleven_turbo_v2' },
{ name: 'Turbo v2.5', value: 'eleven_turbo_v2_5' },
{ name: 'Flash v2', value: 'eleven_flash_v2' },
{ name: 'Flash v2.5', value: 'eleven_flash_v2_5' },
{ name: 'Multilingual v1', value: 'eleven_multilingual_v1' },
{ name: 'Multilingual v2', value: 'eleven_multilingual_v2' },
{ name: 'Multilingual STS v2', value: 'eleven_multilingual_sts_v2' },
{ name: 'English v1', value: 'eleven_monolingual_v1' },
{ name: 'English v2', value: 'eleven_english_sts_v2' },
];

View File

@@ -0,0 +1,5 @@
module.exports = [
{ name: 'Llama Inworld TTS', value: 'inworld-tts-1' },
{ name: 'Llama Inworld TTS Max', value: 'inworld-tts-1-max' },
];

View File

@@ -0,0 +1,6 @@
module.exports = [
{ name: 'TTS-1', value: 'tts-1' },
{ name: 'TTS-1-HD', value: 'tts-1-hd' },
{ name: 'GPT-4o-Mini-TTS', value: 'gpt-4o-mini-tts' },
];

View File

@@ -0,0 +1,8 @@
module.exports = [
{ name: 'Play3.0', value: 'Play3.0' },
{ name: 'PlayHT2.0-turbo', value: 'PlayHT2.0-turbo' },
{ name: 'PlayHT2.0', value: 'PlayHT2.0' },
{ name: 'PlayHT1.0', value: 'PlayHT1.0' },
{ name: 'Dialog 1.0', value: 'PlayDialog'}
];

View File

@@ -0,0 +1,7 @@
module.exports = [
{ name: 'Arcana', value: 'arcana' },
{ name: 'Mist', value: 'mist' },
{ name: 'Mistv2', value: 'mistv2' },
{ name: 'V1', value: 'v1' },
];

View File

@@ -0,0 +1,6 @@
module.exports = [
{ name: 'TTS-1', value: 'tts-1' },
{ name: 'TTS-1-HD', value: 'tts-1-hd' },
{ name: 'GPT-4o mini TTS', value: 'gpt-4o-mini-tts' },
];

View 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' },
],
},
];

View File

@@ -0,0 +1,66 @@
module.exports = (() => {
const voices = [
{ value: 'Magpie-Multilingual.EN-US.Female.Female-1', name: 'Female Magpie' },
{ value: 'Magpie-Multilingual.EN-US.Female.Calm', name: 'Female Calm' },
{ value: 'Magpie-Multilingual.EN-US.Female.Fearful', name: 'Female Fearful' },
{ value: 'Magpie-Multilingual.EN-US.Female.Happy', name: 'Female Happy' },
{ value: 'Magpie-Multilingual.EN-US.Female.Angry', name: 'Female Angry' },
{ value: 'Magpie-Multilingual.EN-US.Female.Neutral', name: 'Female Neutral' },
{ value: 'Magpie-Multilingual.EN-US.Male.Calm', name: 'Male Calm' },
{ value: 'Magpie-Multilingual.EN-US.Male.Fearful', name: 'Male Fearful' },
{ value: 'Magpie-Multilingual.EN-US.Male.Happy', name: 'Male Happy' },
{ value: 'Magpie-Multilingual.EN-US.Male.Neutral', name: 'Male Neutral' },
{ value: 'Magpie-Multilingual.EN-US.Male.Angry', name: 'Male Angry' },
{ value: 'Magpie-Multilingual.EN-US.Male.Disgusted', name: 'Male Disgusted' },
{ value: 'Magpie-Multilingual.EN-US.Male.Male-1', name: 'Male Magpie' },
{ value: 'Magpie-Multilingual.FR-FR.Male.Male-1', name: 'Male Magpie' },
{ value: 'Magpie-Multilingual.FR-FR.Female.Female-1', name: 'Female Magpie' },
{ value: 'Magpie-Multilingual.FR-FR.Female.Angry', name: 'Female Angry' },
{ value: 'Magpie-Multilingual.FR-FR.Female.Calm', name: 'Female Calm' },
{ value: 'Magpie-Multilingual.FR-FR.Female.Disgust', name: 'Female Disgust' },
{ value: 'Magpie-Multilingual.FR-FR.Female.Sad', name: 'Female Sad' },
{ value: 'Magpie-Multilingual.FR-FR.Female.Happy', name: 'Female Happy' },
{ value: 'Magpie-Multilingual.FR-FR.Female.Fearful', name: 'Female Fearful' },
{ value: 'Magpie-Multilingual.FR-FR.Female.Neutral', name: 'Female Neutral' },
{ value: 'Magpie-Multilingual.FR-FR.Male.Neutral', name: 'Male Neutral' },
{ value: 'Magpie-Multilingual.FR-FR.Male.Angry', name: 'Male Angry' },
{ value: 'Magpie-Multilingual.FR-FR.Male.Calm', name: 'Male Calm' },
{ value: 'Magpie-Multilingual.FR-FR.Male.Sad', name: 'Male Sad' },
{ value: 'Magpie-Multilingual.ES-US.Male.Male-1', name: 'Male Magpie' },
{ value: 'Magpie-Multilingual.ES-US.Female.Female-1', name: 'Female Magpie' },
{ value: 'Magpie-Multilingual.ES-US.Female.Neutral', name: 'Female Neutral' },
{ value: 'Magpie-Multilingual.ES-US.Male.Neutral', name: 'Male Neutral' },
{ value: 'Magpie-Multilingual.ES-US.Male.Angry', name: 'Male Angry' },
{ value: 'Magpie-Multilingual.ES-US.Female.Angry', name: 'Female Angry' },
{ value: 'Magpie-Multilingual.ES-US.Female.Happy', name: 'Female Happy' },
{ value: 'Magpie-Multilingual.ES-US.Male.Happy', name: 'Male Happy' },
{ value: 'Magpie-Multilingual.ES-US.Female.Calm', name: 'Female Calm' },
{ value: 'Magpie-Multilingual.ES-US.Male.Calm', name: 'Male Calm' },
{ value: 'Magpie-Multilingual.ES-US.Female.Pleasant_Surprise', name: 'Female Pleasant Surprise' },
{ value: 'Magpie-Multilingual.ES-US.Male.Pleasant_Surprise', name: 'Male Pleasant Surprise' },
{ value: 'Magpie-Multilingual.ES-US.Female.Sad', name: 'Female Sad' },
{ value: 'Magpie-Multilingual.ES-US.Male.Sad', name: 'Male Sad' },
{ value: 'Magpie-Multilingual.ES-US.Male.Disgust', name: 'Male Disgust' }
];
return [
{
value: 'en-US',
name: 'English',
voices: [
{ value: 'English-US.Female-1', name: 'Female' },
{ value: 'English-US.Male-1', name: 'Male' },
...voices.filter((voice) => voice.value.includes('EN-US'))]
},
{
value: 'fr-FR',
name: 'French',
voices: voices.filter((voice) => voice.value.includes('FR-FR'))
},
{
value: 'es-US',
name: 'Spanish',
voices: voices.filter((voice) => voice.value.includes('ES-US'))
}
];
})();

View File

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

View File

@@ -0,0 +1,438 @@
module.exports = [
{
value: 'en-gb',
name: 'En-gb',
voices: [
{
name: 'Seth (Legacy) (professional) - Resemble Voice',
value: 'a52c4efc',
},
{
name: 'Seth (professional) - Resemble Voice',
value: 'd3e61caf',
},
],
},
{
value: 'en-GB',
name: 'En-GB',
voices: [
{
name: 'Beatrice Pendergast (professional) - Resemble Voice',
value: '00b1fd4e',
},
{
name: 'Ed Smart (professional) - Resemble Voice',
value: '0c755526',
},
{
name: 'Paula J (professional) - Resemble Voice',
value: '33e64cd2',
},
],
},
{
value: 'en-us',
name: 'En-us',
voices: [
{
name: 'David (professional) - Resemble Voice',
value: '5bb13f03',
},
],
},
{
value: 'en-US',
name: 'En-US',
voices: [
{
name: 'Adam Lofbomm (professional) - Resemble Voice',
value: '4e228dba',
},
{
name: 'Alex (professional) - Resemble Voice',
value: '41b99669',
},
{
name: 'Amelia (professional) - Resemble Voice',
value: 'ecbe5d97',
},
{
name: 'Andrew (rapid) - Resemble Marketplace',
value: 'd2f26a3e',
},
{
name: 'Annika (professional) - Resemble Voice',
value: 'b27f3cc0',
},
{
name: 'Arthur (professional) - Resemble Voice',
value: '9de11312',
},
{
name: 'Ash (professional) - Resemble Voice',
value: 'ee322483',
},
{
name: 'Aurora (professional) - Resemble Voice',
value: 'a72d9fca',
},
{
name: 'Austin (professional) - Resemble Voice',
value: '82a67e58',
},
{
name: 'Beth (Legacy) (professional) - Resemble Voice',
value: '25c7823f',
},
{
name: 'Beth (professional) - Resemble Voice',
value: 'fa66d263',
},
{
name: 'Blade (professional) - Resemble Voice',
value: '8bedd793',
},
{
name: 'Brandy Sky (professional) - Resemble Voice',
value: '79e2f1dc',
},
{
name: 'Brenley (professional) - Resemble Voice',
value: 'e6ec3ca4',
},
{
name: 'Britney (professional) - Resemble Voice',
value: 'e57e23ff',
},
{
name: 'Broadcast Joe (professional) - Resemble Voice',
value: '21e49584',
},
{
name: 'Carl Bishop (Angry) (professional) - Resemble Voice',
value: 'f06cd770',
},
{
name: 'Carl Bishop (Conversational) (professional) - Resemble Voice',
value: '7f40ff35',
},
{
name: 'Carl Bishop (Happy) (professional) - Resemble Voice',
value: '99751e42',
},
{
name: 'Carl Bishop (professional) - Resemble Voice',
value: '01bcc102',
},
{
name: 'Carl Bishop (Scared) (Legacy) (professional) - Resemble Voice',
value: '1dcf0222',
},
{
name: 'Carl Bishop (Scared) (professional) - Resemble Voice',
value: 'eacbc44f',
},
{
name: 'Charles (Legacy) (professional) - Resemble Voice',
value: '4c6d3da5',
},
{
name: 'Charles (professional) - Resemble Voice',
value: 'd79a5198',
},
{
name: 'Charlotte (professional) - Resemble Voice',
value: '96b91cf9',
},
{
name: 'Chris Whiting (professional) - Resemble Voice',
value: '95b7560a',
},
{
name: 'Cliff (professional) - Resemble Voice',
value: 'fcf8490c',
},
{
name: 'Connor (professional) - Resemble Voice',
value: 'a6131acf',
},
{
name: 'Deanna (professional) - Resemble Voice',
value: '0842fdf9',
},
{
name: 'Ember (professional) - Resemble Voice',
value: '55592656',
},
{
name: 'Gene Amore (professional) - Resemble Voice',
value: 'f2ea7aa0',
},
{
name: 'Harry Robinson (professional) - Resemble Voice',
value: '3c36d67d',
},
{
name: 'Helena (professional) - Resemble Voice',
value: 'ac948df2',
},
{
name: 'Hem (professional) - Resemble Voice',
value: 'b6edbe5f',
},
{
name: 'John (professional) - Resemble Voice',
value: 'ac48daeb',
},
{
name: 'Josh (professional) - Resemble Voice',
value: '987c99e9',
},
{
name: 'Julie Hoverson (professional) - Resemble Voice',
value: 'b119524c',
},
{
name: 'Justin (Legacy) (professional) - Resemble Voice',
value: 'b2d1bb75',
},
{
name: 'Justin (Meditative) (Legacy) (professional) - Resemble Voice',
value: '93ce0920',
},
{
name: 'Justin (Meditative) (professional) - Resemble Voice',
value: '2570000e',
},
{
name: 'Justin (professional) - Resemble Voice',
value: '9d513c17',
},
{
name: 'Karl Nordman (professional) - Resemble Voice',
value: 'da67f17e',
},
{
name: 'Kate (professional) - Resemble Voice',
value: '28b4cc5a',
},
{
name: 'Katya (professional) - Resemble Voice',
value: 'c9ee13b4',
},
{
name: 'Ken (professional) - Resemble Voice',
value: '3dbfbf3d',
},
{
name: 'Kessi (professional) - Resemble Voice',
value: '2211cb8c',
},
{
name: 'Little Ari (professional) - Resemble Voice',
value: '805adead',
},
{
name: 'Little Brittle (professional) - Resemble Voice',
value: '8a73f115',
},
{
name: 'Liz (professional) - Resemble Voice',
value: '4884d94a',
},
{
name: 'Lothar (professional) - Resemble Voice',
value: '78671217',
},
{
name: 'Luna (professional) - Resemble Voice',
value: 'ae8223ca',
},
{
name: 'Matt Weller (professional) - Resemble Voice',
value: 'f4da4639',
},
{
name: 'Maureen (Angry) (professional) - Resemble Voice',
value: '482babfc',
},
{
name: 'Maureen (Caring) (professional) - Resemble Voice',
value: 'b15e550f',
},
{
name: 'Maureen (Happy) (professional) - Resemble Voice',
value: '91947e5c',
},
{
name: 'Maureen (professional) - Resemble Voice',
value: '7d94218f',
},
{
name: 'Maureen (Sad) (professional) - Resemble Voice',
value: 'bca7481c',
},
{
name: 'Maureen (Scared) (professional) - Resemble Voice',
value: '251c9439',
},
{
name: 'Mauren (Announcer) (professional) - Resemble Voice',
value: 'e984fb89',
},
{
name: 'Melody (Legacy) (professional) - Resemble Voice',
value: '15be93bd',
},
{
name: 'Melody (professional) - Resemble Voice',
value: '1c49e774',
},
{
name: 'Mike (professional) - Resemble Voice',
value: '3a02dc40',
},
{
name: 'Niki (professional) - Resemble Voice',
value: 'db37643c',
},
{
name: 'Olga (professional) - Resemble Voice',
value: '07c1d6b5',
},
{
name: 'Olivia (Legacy) (professional) - Resemble Voice',
value: '405b58e3',
},
{
name: 'Olivia (professional) - Resemble Voice',
value: 'ef49f972',
},
{
name: 'Orion (professional) - Resemble Voice',
value: 'aa8053cc',
},
{
name: 'Pete (professional) - Resemble Voice',
value: '1864fd63',
},
{
name: 'Primrose (Legacy) (professional) - Resemble Voice',
value: '7c8e47ca',
},
{
name: 'Primrose (professional) - Resemble Voice',
value: '33eecc17',
},
{
name: 'Primrose (Whispering) (Legacy) (professional) - Resemble Voice',
value: 'a56c5c6f',
},
{
name: 'Primrose (Whispering) (professional) - Resemble Voice',
value: '28fcdf76',
},
{
name: 'Primrose (Winded) (Legacy) (professional) - Resemble Voice',
value: '6f9a77a4',
},
{
name: 'Primrose (Winded) (professional) - Resemble Voice',
value: '0097f246',
},
{
name: 'Professor Shaposhnikov (professional) - Resemble Voice',
value: '3f5fb9f1',
},
{
name: 'Radio Nikole (professional) - Resemble Voice',
value: '19eae884',
},
{
name: 'Richard Garifo (professional) - Resemble Voice',
value: '85ba84f2',
},
{
name: 'Rico (professional) - Resemble Voice',
value: '14ca34b3',
},
{
name: 'Robert (professional) - Resemble Voice',
value: '3e907bcc',
},
{
name: 'Rupert (rapid) - Resemble Voice',
value: '28f1626c',
},
{
name: 'Sam (professional) - Resemble Voice',
value: '0f2f9a7e',
},
{
name: 'Samantha (Legacy) (professional) - Resemble Voice',
value: '266bfae9',
},
{
name: 'Samantha (professional) - Resemble Voice',
value: 'e28236ee',
},
{
name: 'Siobhan (professional) - Resemble Voice',
value: 'af72c1ac',
},
{
name: 'Steve (Scared) (professional) - Resemble Voice',
value: 'aaa56e79',
},
{
name: 'Tanja (professional) - Resemble Voice',
value: 'adb84c77',
},
{
name: 'Tanja (Telephonic) (professional) - Resemble Voice',
value: '4f5a470b',
},
{
name: 'Tanja (Warm Word Weaver) (professional) - Resemble Voice',
value: 'abbbc383',
},
{
name: 'Tarkos (professional) - Resemble Voice',
value: '779842bf',
},
{
name: 'Tyler (professional) - Resemble Voice',
value: 'ff225977',
},
{
name: 'Vicky (professional) - Resemble Voice',
value: 'f453b918',
},
{
name: 'Vivian (Legacy) (professional) - Resemble Voice',
value: 'bed1044d',
},
{
name: 'Vivian (professional) - Resemble Voice',
value: '1ff0045f',
},
{
name: 'William (Whispering) (Legacy) (professional) - Resemble Voice',
value: '79eb7953',
},
{
name: 'William (Whispering) (professional) - Resemble Voice',
value: 'e2180df0',
},
{
name: 'Willow (Whispering) (professional) - Resemble Voice',
value: 'f2906c4a',
},
{
name: 'Willow II (Whispering) (professional) - Resemble Voice',
value: 'c815cd7a',
},
],
},
];

View File

@@ -0,0 +1,46 @@
module.exports = [
{
value: 'es-ES',
name: 'Castilian Spanish',
voices: [
{
value: 'david_es_es',
name: 'David-Male',
},
],
},
{
value: 'es-PE',
name: 'Peruvian Spanish',
voices: [
{
value: 'miguel_es_pe',
name: 'Miguel-Male',
},
{
value: 'luz_es_pe',
name: 'Luz-Female',
},
],
},
{
value: 'pt-BR',
name: 'Brazilian Portuguese',
voices: [
{
value: 'bel_pt_br',
name: 'Bel-Female',
},
],
},
{
value: 'ca-ES',
name: 'Catalan',
voices: [
{
value: 'anna_ca',
name: 'Anna-Female',
},
],
},
];

View 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)' },
],
},
];

View 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' },
],
},
];

Some files were not shown because too many files have changed in this diff Show More