diff --git a/lib/routes/api/speech-credentials.js b/lib/routes/api/speech-credentials.js index f8ede015..5303cea1 100644 --- a/lib/routes/api/speech-credentials.js +++ b/lib/routes/api/speech-credentials.js @@ -11,7 +11,8 @@ const {decryptCredential, testWhisper, testDeepgramTTS, testRimelabs, testVerbioTts, testVerbioStt, - testSpeechmaticsStt} = require('../../utils/speech-utils'); + testSpeechmaticsStt, + testCartesia} = require('../../utils/speech-utils'); const {DbErrorUnprocessableRequest, DbErrorForbidden, DbErrorBadRequest} = require('../../utils/errors'); const { testGoogleTts, @@ -251,6 +252,12 @@ const encryptCredential = (obj) => { const playhtData = JSON.stringify({api_key, user_id, voice_engine, options}); return encrypt(playhtData); + case 'cartesia': + assert(api_key, 'invalid cartesia speech credential: api_key is required'); + assert(model_id, 'invalid cartesia speech credential: model_id is required'); + const cartesiaData = JSON.stringify({api_key, model_id, options}); + return encrypt(cartesiaData); + case 'rimelabs': assert(api_key, 'invalid rimelabs speech credential: api_key is required'); assert(model_id, 'invalid rimelabs speech credential: model_id is required'); @@ -804,6 +811,22 @@ router.get('/:sid/test', async(req, res) => { SpeechCredential.ttsTestResult(sid, false); } } + } else if (cred.vendor === 'cartesia') { + if (cred.use_for_tts) { + try { + await testCartesia(logger, synthAudio, credential); + results.tts.status = 'ok'; + SpeechCredential.ttsTestResult(sid, true); + } catch (err) { + let reason = err.message; + // if error is from bent, let get the body + try { + reason = await err.text(); + } catch {} + results.tts = {status: 'fail', reason}; + SpeechCredential.ttsTestResult(sid, false); + } + } } else if (cred.vendor === 'rimelabs') { if (cred.use_for_tts) { try { diff --git a/lib/utils/speech-data/tts-cartesia.js b/lib/utils/speech-data/tts-cartesia.js new file mode 100644 index 00000000..dc4500f8 --- /dev/null +++ b/lib/utils/speech-data/tts-cartesia.js @@ -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.', + }, + ], + }, +]; diff --git a/lib/utils/speech-data/tts-model-cartesia.js b/lib/utils/speech-data/tts-model-cartesia.js new file mode 100644 index 00000000..7df967f5 --- /dev/null +++ b/lib/utils/speech-data/tts-model-cartesia.js @@ -0,0 +1,25 @@ +module.exports = [ + { + name: 'Sonic', + value: 'sonic', + languages: ['en', 'fr', 'de', 'es', 'pt', 'zh', 'ja', 'hi', 'it', 'ko', 'nl', 'pl', 'ru', 'sv', 'tr'] + }, + { name: 'Sonic Preview', value: 'sonic-preview', languages: ['en'] }, + { + name: 'Sonic 2024-12-12', + value: 'sonic-2024-12-12', + languages: ['en', 'fr', 'de', 'es', 'pt', 'zh', 'ja', 'hi', 'it', 'ko', 'nl', 'pl', 'ru', 'sv', 'tr'] + }, + { + name: 'Sonic 2024-10-19', + value: 'sonic-2024-10-19', + languages: ['en', 'fr', 'de', 'es', 'pt', 'zh', 'ja', 'hi', 'it', 'ko', 'nl', 'pl', 'ru', 'sv', 'tr'] + }, + { name: 'Sonic English', value: 'sonic-english', languages: ['en'] }, + { + name: 'Sonic Multilingual', + value: 'sonic-multilingual', + languages: ['en', 'fr', 'de', 'es', 'pt', 'zh', 'ja', 'hi', 'it', 'ko', 'nl', 'pl', 'ru', 'sv', 'tr'] + }, +]; + diff --git a/lib/utils/speech-utils.js b/lib/utils/speech-utils.js index 1ef160d3..d51741b5 100644 --- a/lib/utils/speech-utils.js +++ b/lib/utils/speech-utils.js @@ -27,6 +27,7 @@ const TtsModelWhisper = require('./speech-data/tts-model-whisper'); const TtsModelPlayHT = require('./speech-data/tts-model-playht'); const ttsLanguagesPlayHt = require('./speech-data/tts-languages-playht'); const TtsModelRimelabs = require('./speech-data/tts-model-rimelabs'); +const TtsModelCartesia = require('./speech-data/tts-model-cartesia'); const SttGoogleLanguagesVoices = require('./speech-data/stt-google'); const SttAwsLanguagesVoices = require('./speech-data/stt-aws'); @@ -40,6 +41,8 @@ const SttSonioxLanguagesVoices = require('./speech-data/stt-soniox'); const SttSpeechmaticsLanguagesVoices = require('./speech-data/stt-speechmatics'); const SttAssemblyaiLanguagesVoices = require('./speech-data/stt-assemblyai'); const SttVerbioLanguagesVoices = require('./speech-data/stt-verbio'); +const ttsCartesia = require('./speech-data/tts-cartesia'); +const ttsModelCartesia = require('./speech-data/tts-model-cartesia'); const testSonioxStt = async(logger, credentials) => { @@ -606,6 +609,11 @@ function decryptCredential(obj, credential, logger, isObscureKey = true) { obj.user_id = o.user_id; obj.voice_engine = o.voice_engine; obj.options = o.options; + } else if ('cartesia' === obj.vendor) { + const o = JSON.parse(decrypt(credential)); + obj.api_key = isObscureKey ? obscureKey(o.api_key) : o.api_key; + obj.model_id = o.model_id; + obj.options = o.options; } else if ('rimelabs' === obj.vendor) { const o = JSON.parse(decrypt(credential)); obj.api_key = isObscureKey ? obscureKey(o.api_key) : o.api_key; @@ -688,6 +696,8 @@ async function getLanguagesAndVoicesForVendor(logger, vendor, credential, getTts return await getLanguagesVoicesForVerbio(credential, getTtsVoices, logger); case 'speechmatics': return await getLanguagesVoicesForSpeechmatics(credential, getTtsVoices, logger); + case 'cartesia': + return await getLanguagesVoicesForCartesia(credential, getTtsVoices, logger); default: logger.info(`invalid vendor ${vendor}, return empty result`); throw new Error(`Invalid vendor ${vendor}`); @@ -1143,6 +1153,95 @@ function parseVerbioLanguagesVoices(data) { }, []); } +const fetchCartesiaVoices = async(credential) => { + if (credential) { + const get = bent('https://api.cartesia.ai', 'GET', 'json', { + 'X-API-Key' : credential.api_key, + 'Cartesia-Version': '2024-06-10', + 'Accept': 'application/json' + }); + + const voices = await get('/voices'); + return voices; + } +}; + +const testCartesia = async(logger, synthAudio, credentials) => { + try { + await synthAudio( + { + increment: () => {}, + histogram: () => {} + }, + { + vendor: 'cartesia', + credentials, + language: 'en', + voice: '694f9389-aac1-45b6-b726-9d9369183238', + text: 'Hi there and welcome to jambones!', + renderForCaching: true + } + ); + // Test if Cartesia can fetch voices + await fetchCartesiaVoices(credentials); + } catch (err) { + logger.info({err}, 'synth cartesia returned error'); + throw err; + } +}; + +async function getLanguagesVoicesForCartesia(credential) { + if (credential) { + const {model_id} = credential; + const {languages} = ttsModelCartesia.find((m) => m.value === model_id); + const voices = await fetchCartesiaVoices(credential); + + const buildVoice = (d) => ( + { + value: `${d.id}`, + name: `${d.name} - ${d.description}` + }); + const languageMap = { + en: 'English', + fr: 'French', + de: 'German', + es: 'Spanish', + pt: 'Portuguese', + zh: 'Chinese', + ja: 'Japanese', + hi: 'Hindi', + it: 'Italian', + ko: 'Korean', + nl: 'Dutch', + pl: 'Polish', + ru: 'Russian', + sv: 'Swedish', + tr: 'Turkish', + }; + const ttsVoices = voices.reduce((acc, voice) => { + if (!languages.includes(voice.language)) { + return acc; + } + + const languageCode = voice.language; + const existingLanguage = acc.find((lang) => lang.value === languageCode); + if (existingLanguage) { + existingLanguage.voices.push(buildVoice(voice)); + } else { + acc.push({ + value: languageCode, + name: languageMap[languageCode], + voices: [buildVoice(voice)] + }); + } + return acc; + }, []); + + return tranform(ttsVoices, undefined, TtsModelCartesia); + } + return tranform(ttsCartesia, undefined, TtsModelCartesia); +} + module.exports = { testGoogleTts, testGoogleStt, @@ -1169,5 +1268,6 @@ module.exports = { testVerbioTts, testVerbioStt, getLanguagesAndVoicesForVendor, - testSpeechmaticsStt + testSpeechmaticsStt, + testCartesia }; diff --git a/package-lock.json b/package-lock.json index e697d85a..12635a59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "@jambonz/lamejs": "^1.2.2", "@jambonz/mw-registrar": "^0.2.7", "@jambonz/realtimedb-helpers": "^0.8.10", - "@jambonz/speech-utils": "^0.1.22", + "@jambonz/speech-utils": "^0.2.1", "@jambonz/time-series": "^0.2.8", "@jambonz/verb-specifications": "^0.0.72", "@soniox/soniox-node": "^1.2.2", @@ -1779,6 +1779,72 @@ "node": ">=6.9.0" } }, + "node_modules/@cartesia/cartesia-js": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@cartesia/cartesia-js/-/cartesia-js-2.1.1.tgz", + "integrity": "sha512-Kwcrxtay6G3hwFGyq7cwB00d+diABhi2/5Mb4vytmN+QqBgTAVYgwFweGnLDL9Ci9zFttgLE1rW8u+EA8U5P9A==", + "dependencies": { + "emittery": "^0.13.1", + "form-data": "^4.0.0", + "form-data-encoder": "^4.0.2", + "formdata-node": "^6.0.3", + "human-id": "^4.1.1", + "node-fetch": "2.7.0", + "qs": "6.11.2", + "readable-stream": "^4.5.2", + "url-join": "4.0.1", + "ws": "^8.15.13" + } + }, + "node_modules/@cartesia/cartesia-js/node_modules/form-data-encoder": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-4.0.2.tgz", + "integrity": "sha512-KQVhvhK8ZkWzxKxOr56CPulAhH3dobtuQ4+hNQ+HekH/Wp5gSOafqRAeTphQUJAIk0GBvHZgJ2ZGRWd5kphMuw==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@cartesia/cartesia-js/node_modules/formdata-node": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-6.0.3.tgz", + "integrity": "sha512-8e1++BCiTzUno9v5IZ2J6bv4RU+3UKDmqWUQD0MIMVCd9AdhWkO1gw57oo1mNEX1dMq2EGI+FbWz4B92pscSQg==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@cartesia/cartesia-js/node_modules/qs": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@cartesia/cartesia-js/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/@deepgram/sdk": { "version": "1.21.0", "resolved": "https://registry.npmjs.org/@deepgram/sdk/-/sdk-1.21.0.tgz", @@ -2226,13 +2292,14 @@ } }, "node_modules/@jambonz/speech-utils": { - "version": "0.1.22", - "resolved": "https://registry.npmjs.org/@jambonz/speech-utils/-/speech-utils-0.1.22.tgz", - "integrity": "sha512-1/ARN6tiY/Roc86e1pw1Te/PDXTWjZb+hgpNdkM3i+GqpmtsuTWnDMxS9sBbsIgYEggJM67fn4Z1j65nERW0ag==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@jambonz/speech-utils/-/speech-utils-0.2.1.tgz", + "integrity": "sha512-LIZ3HCW+vra626L18NiudY4v7zXVW4EvqUeeVLFM9S8fi1nGP2lHbDur/DFiN/VsJXYdNIdO4PAsILxeZXoOLg==", "license": "MIT", "dependencies": { "@aws-sdk/client-polly": "^3.496.0", "@aws-sdk/client-sts": "^3.496.0", + "@cartesia/cartesia-js": "^2.1.0", "@google-cloud/text-to-speech": "^5.5.0", "@grpc/grpc-js": "^1.9.14", "@jambonz/realtimedb-helpers": "^0.8.7", @@ -4810,6 +4877,18 @@ "integrity": "sha512-bx7+5Saea/qu14kmPTDHQxkp2UnziG3iajUQu3BxFvCOnpAJdDbMV4rSl+EqFDkkpNNVUFlR1kDfpL59xfy1HA==", "dev": true }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -6188,6 +6267,15 @@ "node": ">= 14" } }, + "node_modules/human-id": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/human-id/-/human-id-4.1.1.tgz", + "integrity": "sha512-3gKm/gCSUipeLsRYZbbdA1BD83lBoWUkZ7G9VFrhWPAU76KwYo5KR8V28bpoPm/ygy0x5/GCbpRQdY7VLYCoIg==", + "license": "MIT", + "bin": { + "human-id": "dist/cli.js" + } + }, "node_modules/humanize-ms": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", diff --git a/package.json b/package.json index cf8e520a..f76a4bd5 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "@jambonz/lamejs": "^1.2.2", "@jambonz/mw-registrar": "^0.2.7", "@jambonz/realtimedb-helpers": "^0.8.10", - "@jambonz/speech-utils": "^0.1.22", + "@jambonz/speech-utils": "^0.2.1", "@jambonz/time-series": "^0.2.8", "@jambonz/verb-specifications": "^0.0.72", "@soniox/soniox-node": "^1.2.2",