From fd9dc77a58fb667fa24d8dded5dcd6cddf28ff90 Mon Sep 17 00:00:00 2001 From: Hoan Luu Huu <110280845+xquanluu@users.noreply.github.com> Date: Wed, 13 Aug 2025 19:18:08 +0700 Subject: [PATCH] support resemble TTS (#488) * support resemble TTS * wip * wip * update speech utils version * update resemble voice list --- lib/routes/api/speech-credentials.js | 33 +- lib/utils/speech-data/tts-resemble.js | 438 ++++++++++++++++++++++++++ lib/utils/speech-utils.js | 105 +++++- package-lock.json | 217 ++++++++++--- package.json | 2 +- test/speech-credentials.js | 22 ++ 6 files changed, 766 insertions(+), 51 deletions(-) create mode 100644 lib/utils/speech-data/tts-resemble.js diff --git a/lib/routes/api/speech-credentials.js b/lib/routes/api/speech-credentials.js index 695e966..d847bd9 100644 --- a/lib/routes/api/speech-credentials.js +++ b/lib/routes/api/speech-credentials.js @@ -15,7 +15,8 @@ const {decryptCredential, testWhisper, testDeepgramTTS, testCartesia, testVoxistStt, testOpenAiStt, - testInworld} = require('../../utils/speech-utils'); + testInworld, + testResembleTTS} = require('../../utils/speech-utils'); const {DbErrorUnprocessableRequest, DbErrorForbidden, DbErrorBadRequest} = require('../../utils/errors'); const { testGoogleTts, @@ -132,6 +133,8 @@ const encryptCredential = (obj) => { 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, @@ -226,6 +229,15 @@ const encryptCredential = (obj) => { 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}); @@ -523,7 +535,9 @@ router.put('/:sid', async(req, res) => { playht_tts_uri, engine_version, service_version, - speechmatics_stt_uri + speechmatics_stt_uri, + resemble_tts_use_tls, + resemble_tts_uri } = req.body; const newCred = { @@ -556,7 +570,9 @@ router.put('/:sid', async(req, res) => { playht_tts_uri, engine_version, service_version, - speechmatics_stt_uri + 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); @@ -756,6 +772,17 @@ router.get('/:sid/test', async(req, res) => { SpeechCredential.sttTestResult(sid, false); } } + } 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_tts) { diff --git a/lib/utils/speech-data/tts-resemble.js b/lib/utils/speech-data/tts-resemble.js new file mode 100644 index 0000000..e5d4705 --- /dev/null +++ b/lib/utils/speech-data/tts-resemble.js @@ -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', + }, + ], + }, +]; diff --git a/lib/utils/speech-utils.js b/lib/utils/speech-utils.js index 2d4197b..73575a6 100644 --- a/lib/utils/speech-utils.js +++ b/lib/utils/speech-utils.js @@ -21,6 +21,7 @@ const TtsPlayHtLanguagesVoices = require('./speech-data/tts-playht'); const TtsVerbioLanguagesVoices = require('./speech-data/tts-verbio'); const TtsInworldLanguagesVoices = require('./speech-data/tts-inworld'); const ttsCartesia = require('./speech-data/tts-cartesia'); +const TtsResembleLanguagesVoices = require('./speech-data/tts-resemble'); const TtsModelDeepgram = require('./speech-data/tts-model-deepgram'); const TtsLanguagesDeepgram = require('./speech-data/tts-deepgram'); @@ -424,6 +425,24 @@ const testWhisper = async(logger, synthAudio, credentials) => { } }; +const testResembleTTS = async(logger, synthAudio, credentials) => { + try { + await synthAudio({increment: () => {}, histogram: () => {}}, + { + vendor: 'resemble', + credentials, + language: 'en-US', + voice: '3f5fb9f1', + text: 'Hi there and welcome to jambones!', + renderForCaching: true + } + ); + } catch (err) { + logger.info({err}, 'synth resemble returned error'); + throw err; + } +}; + const testDeepgramTTS = async(logger, synthAudio, credentials) => { try { await synthAudio({increment: () => {}, histogram: () => {}}, @@ -729,6 +748,11 @@ function decryptCredential(obj, credential, logger, isObscureKey = true) { const o = JSON.parse(decrypt(credential)); obj.api_key = isObscureKey ? obscureKey(o.api_key) : o.api_key; obj.service_version = o.service_version; + } else if ('resemble' === obj.vendor) { + const o = JSON.parse(decrypt(credential)); + obj.api_key = isObscureKey ? obscureKey(o.api_key) : o.api_key; + obj.resemble_tts_uri = o.resemble_tts_uri; + obj.resemble_tts_use_tls = o.resemble_tts_use_tls; } else if ('voxist' === obj.vendor) { const o = JSON.parse(decrypt(credential)); obj.api_key = isObscureKey ? obscureKey(o.api_key) : o.api_key; @@ -799,6 +823,8 @@ async function getLanguagesAndVoicesForVendor(logger, vendor, credential, getTts return await getLanguagesVoicesForRimelabs(credential, getTtsVoices, logger); case 'inworld': return await getLanguagesVoicesForInworld(credential, getTtsVoices, logger); + case 'resemble': + return await getLanguagesAndVoicesForResemble(credential, getTtsVoices, logger); case 'assemblyai': return await getLanguagesVoicesForAssemblyAI(credential, getTtsVoices, logger); case 'voxist': @@ -1240,6 +1266,82 @@ async function getLanguagesVoicesForVerbio(credentials, getTtsVoices, logger) { } } +async function getLanguagesAndVoicesForResemble(credential, getTtsVoices, logger) { + if (credential) { + try { + const {api_key} = credential; + let allVoices = []; + let page = 1; + let hasMorePages = true; + // Fetch all pages of voices + while (hasMorePages) { + const response = await fetch(`https://app.resemble.ai/api/v2/voices?page=${page}&page_size=100`, { + headers: { + 'Authorization': `Token token=${api_key}`, + 'Accept': 'application/json' + } + }); + + if (!response.ok) { + throw new Error('failed to list voices'); + } + + const data = await response.json(); + + if (!data.success) { + throw new Error('API returned unsuccessful response'); + } + + allVoices = allVoices.concat(data.items); + + // Check if there are more pages + hasMorePages = page < data.num_pages; + page++; + } + + // Filter only finished voices that support text_to_speech + const availableVoices = allVoices.filter((voice) => + voice.status === 'finished' && + voice.component_status?.text_to_speech?.status === 'ready' + ); + + // Group voices by language + const ttsVoices = availableVoices.reduce((acc, voice) => { + const languageCode = voice.default_language || 'en-US'; + const existingLanguage = acc.find((lang) => lang.value === languageCode); + + const voiceEntry = { + name: `${voice.name} (${voice.voice_type}) - ${voice.source}`, + value: voice.uuid + }; + + if (existingLanguage) { + existingLanguage.voices.push(voiceEntry); + } else { + + acc.push({ + value: languageCode, + name: capitalizeFirst(languageCode), + voices: [voiceEntry] + }); + } + + return acc; + }, []); + // Sort languages and voices + ttsVoices.sort((a, b) => a.name.localeCompare(b.name)); + ttsVoices.forEach((lang) => { + lang.voices.sort((a, b) => a.name.localeCompare(b.name)); + }); + return tranform(ttsVoices); + } catch (err) { + logger.info('Error while fetching Resemble languages, voices, return predefined values', err); + } + } + + return tranform(TtsResembleLanguagesVoices); +} + function tranform(tts, stt, models, sttModels) { return { ...(tts && {tts}), @@ -1528,5 +1630,6 @@ module.exports = { testSpeechmaticsStt, testCartesia, testVoxistStt, - testOpenAiStt + testOpenAiStt, + testResembleTTS }; diff --git a/package-lock.json b/package-lock.json index 2baf66d..7dc4694 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "@jambonz/lamejs": "^1.2.2", "@jambonz/mw-registrar": "^0.2.7", "@jambonz/realtimedb-helpers": "^0.8.15", - "@jambonz/speech-utils": "^0.2.13", + "@jambonz/speech-utils": "^0.2.17", "@jambonz/time-series": "^0.2.8", "@jambonz/verb-specifications": "^0.0.111", "@soniox/soniox-node": "^1.2.2", @@ -4086,9 +4086,9 @@ } }, "node_modules/@jambonz/speech-utils": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/@jambonz/speech-utils/-/speech-utils-0.2.13.tgz", - "integrity": "sha512-8ISTWTfz3fWtPmzPDsZG8zgnf6pTjLA1WasMAF/d/ktGswqVsbhoPcDh5ZyZ7BsEqOMLMIv2Hn0ESmrBuMn5kw==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/@jambonz/speech-utils/-/speech-utils-0.2.17.tgz", + "integrity": "sha512-YcBq8bOvo5QfQJQXP4e2N0sCPqkoW0jNM4eHhspJotJc7V9DB3T5VVA6SGUsDXDZUwyT0JPI3Tx+xof02eWQ2Q==", "license": "MIT", "dependencies": { "23": "^0.0.0", @@ -4102,7 +4102,7 @@ "debug": "^4.3.4", "form-urlencoded": "^6.1.4", "google-protobuf": "^3.21.2", - "ibm-watson": "^8.0.0", + "ibm-watson": "^11.0.0", "microsoft-cognitiveservices-speech-sdk": "1.38.0", "openai": "^4.98.0", "undici": "^7.5.0" @@ -4111,7 +4111,24 @@ "node_modules/@jambonz/speech-utils/node_modules/@types/node": { "version": "13.13.52", "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.52.tgz", - "integrity": "sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ==" + "integrity": "sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ==", + "license": "MIT" + }, + "node_modules/@jambonz/speech-utils/node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } }, "node_modules/@jambonz/speech-utils/node_modules/https-proxy-agent": { "version": "4.0.0", @@ -4135,10 +4152,46 @@ "node": ">= 6.0.0" } }, + "node_modules/@jambonz/speech-utils/node_modules/ibm-cloud-sdk-core": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/ibm-cloud-sdk-core/-/ibm-cloud-sdk-core-5.4.2.tgz", + "integrity": "sha512-5VFkKYU/vSIWFJTVt392XEdPmiEwUJqhxjn1MRO3lfELyU2FB+yYi8brbmXUgq+D1acHR1fpS7tIJ6IlnrR9Cg==", + "license": "Apache-2.0", + "dependencies": { + "@types/debug": "^4.1.12", + "@types/node": "^18.19.80", + "@types/tough-cookie": "^4.0.0", + "axios": "^1.11.0", + "camelcase": "^6.3.0", + "debug": "^4.3.4", + "dotenv": "^16.4.5", + "extend": "3.0.2", + "file-type": "16.5.4", + "form-data": "^4.0.4", + "isstream": "0.1.2", + "jsonwebtoken": "^9.0.2", + "mime-types": "2.1.35", + "retry-axios": "^2.6.0", + "tough-cookie": "^4.1.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jambonz/speech-utils/node_modules/ibm-cloud-sdk-core/node_modules/@types/node": { + "version": "18.19.122", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.122.tgz", + "integrity": "sha512-yzegtT82dwTNEe/9y+CM8cgb42WrUfMMCg2QqSddzO1J6uPmBD7qKCZ7dOHZP2Yrpm/kb0eqdNMn2MUyEiqBmA==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, "node_modules/@jambonz/speech-utils/node_modules/ibm-watson": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/ibm-watson/-/ibm-watson-8.0.0.tgz", - "integrity": "sha512-0vhB1bMZbF6H6AR18LDziaO5+7l8tkTeSeum2/z1gYEy25EatNWh3tHWiFgra923/HST1ZFJWcxloeZTohP9oA==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/ibm-watson/-/ibm-watson-11.0.0.tgz", + "integrity": "sha512-4b0v217rdOhMGNYE0vDYrgGt66DuwkCADxVolllrqeB/WNSmF1YBKC5NQAEhGw8b/tNwDBA3MUAOmhay+P0c4g==", + "license": "Apache-2.0", "dependencies": { "@types/async": "^3.2.5", "@types/extend": "^3.0.1", @@ -4148,12 +4201,12 @@ "async": "^3.2.0", "camelcase": "^6.2.0", "extend": "~3.0.2", - "ibm-cloud-sdk-core": "^4.0.3", + "ibm-cloud-sdk-core": "^5.4.0", "isstream": "~0.1.2", "websocket": "^1.0.33" }, "engines": { - "node": ">=16.0.0" + "node": ">=20.0.0" } }, "node_modules/@jambonz/speech-utils/node_modules/microsoft-cognitiveservices-speech-sdk": { @@ -5805,16 +5858,32 @@ } }, "node_modules/axios": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", - "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", + "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -6077,6 +6146,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -6605,6 +6687,20 @@ "ignored": "bin/ignored" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/duplexify": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", @@ -6734,12 +6830,10 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -6773,10 +6867,10 @@ } }, "node_modules/es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", - "dev": true, + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0" }, @@ -6785,14 +6879,15 @@ } }, "node_modules/es-set-tostringtag": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", - "dev": true, + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.4", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -7630,15 +7725,21 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -7656,6 +7757,19 @@ "node": ">=8.0.0" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-symbol-description": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", @@ -7778,11 +7892,12 @@ "integrity": "sha512-3MSOYFO5U9mPGikIYCzK0SaThypfGgS6bHqrUGXG3DPHCrb+txNqeEcns1W0lkGfk0rCyNXm7xB9rMxnCiZOoA==" }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7859,6 +7974,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -7867,9 +7983,10 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -7881,7 +7998,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "dependencies": { "has-symbols": "^1.0.3" }, @@ -9089,6 +9205,15 @@ "semver": "bin/semver.js" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", diff --git a/package.json b/package.json index 055ce00..93daa9d 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "@jambonz/lamejs": "^1.2.2", "@jambonz/mw-registrar": "^0.2.7", "@jambonz/realtimedb-helpers": "^0.8.15", - "@jambonz/speech-utils": "^0.2.13", + "@jambonz/speech-utils": "^0.2.17", "@jambonz/time-series": "^0.2.8", "@jambonz/verb-specifications": "^0.0.111", "@soniox/soniox-node": "^1.2.2", diff --git a/test/speech-credentials.js b/test/speech-credentials.js index 5d8731d..d47a9b2 100644 --- a/test/speech-credentials.js +++ b/test/speech-credentials.js @@ -902,6 +902,28 @@ test('speech credentials tests', async(t) => { }); t.ok(result.statusCode === 204, 'successfully deleted speech credential'); + /* add a credential for resemble */ + result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, { + resolveWithFullResponse: true, + auth: authUser, + json: true, + body: { + vendor: 'resemble', + use_for_tts: true, + use_for_stt: false, + api_key: 'api_key', + } + }); + t.ok(result.statusCode === 201, 'successfully added speech credential for Resemble'); + const resembleSid = result.body.sid; + + /* delete the credential */ + result = await request.delete(`/Accounts/${account_sid}/SpeechCredentials/${resembleSid}`, { + auth: authUser, + resolveWithFullResponse: true, + }); + t.ok(result.statusCode === 204, 'successfully deleted speech credential for Resemble'); + /* add a credential for deepgram river */ result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, { resolveWithFullResponse: true,