From f1d7dcc6d20b65940d6bd39dfc42854567eea3e1 Mon Sep 17 00:00:00 2001 From: Dave Horton Date: Wed, 17 Nov 2021 20:50:26 -0500 Subject: [PATCH] initial changes for microsoft speech support (#11) * initial changes for microsoft speech support * remove very wordy log message --- db/jambones.sqs | 68 ++++++++++++++-------------- lib/routes/api/speech-credentials.js | 56 ++++++++++++++++++++++- lib/utils/speech-utils.js | 27 ++++++++++- package.json | 1 + test/speech-credentials.js | 32 +++++++++++++ 5 files changed, 148 insertions(+), 36 deletions(-) diff --git a/db/jambones.sqs b/db/jambones.sqs index a6087ac..cb1fac9 100644 --- a/db/jambones.sqs +++ b/db/jambones.sqs @@ -87,7 +87,7 @@ - + @@ -148,7 +148,7 @@ - + @@ -225,7 +225,7 @@ - + @@ -279,7 +279,7 @@ - + @@ -426,7 +426,7 @@ - + @@ -492,7 +492,7 @@ - + @@ -670,7 +670,7 @@ - + @@ -752,7 +752,7 @@ - + @@ -880,7 +880,7 @@ - + @@ -901,6 +901,8 @@ + + @@ -960,7 +962,7 @@ - + @@ -1010,7 +1012,7 @@ - + @@ -1110,7 +1112,7 @@ - + @@ -1179,7 +1181,7 @@ - + @@ -1216,7 +1218,7 @@ - + @@ -1325,7 +1327,7 @@ - + @@ -1395,7 +1397,7 @@ - + @@ -1555,7 +1557,7 @@ - + @@ -1594,7 +1596,7 @@ - + @@ -1686,7 +1688,7 @@ - + @@ -1737,7 +1739,7 @@ - + @@ -1833,7 +1835,7 @@ - + @@ -1933,7 +1935,7 @@ - + @@ -2102,7 +2104,7 @@ - + @@ -2210,7 +2212,7 @@ - + @@ -2280,7 +2282,7 @@ - + @@ -2325,7 +2327,7 @@ - + @@ -2392,7 +2394,7 @@ - + @@ -2400,7 +2402,7 @@ - + @@ -2411,17 +2413,17 @@ - + - - - - + + + + diff --git a/lib/routes/api/speech-credentials.js b/lib/routes/api/speech-credentials.js index 075a3c8..528c653 100644 --- a/lib/routes/api/speech-credentials.js +++ b/lib/routes/api/speech-credentials.js @@ -8,12 +8,24 @@ const { testGoogleTts, testGoogleStt, testAwsTts, - testAwsStt + testAwsStt, + testMicrosoftStt, + testMicrosoftTts } = require('../../utils/speech-utils'); router.post('/', async(req, res) => { const logger = req.app.locals.logger; - const {use_for_stt, use_for_tts, vendor, service_key, access_key_id, secret_access_key, aws_region} = req.body; + const { + use_for_stt, + use_for_tts, + vendor, + service_key, + access_key_id, + secret_access_key, + aws_region, + api_key, + region + } = req.body; const account_sid = req.user.account_sid || req.body.account_sid; let service_provider_sid; if (!account_sid) { @@ -47,6 +59,13 @@ router.post('/', async(req, res) => { }); encrypted_credential = encrypt(data); } + else if (vendor === 'microsoft') { + const data = JSON.stringify({ + region, + api_key + }); + encrypted_credential = encrypt(data); + } else throw new DbErrorBadRequest(`invalid speech vendor ${vendor}`); const uuid = await SpeechCredential.make({ account_sid, @@ -85,6 +104,11 @@ router.get('/', async(req, res) => { obj.access_key_id = o.access_key_id; obj.secret_access_key = o.secret_access_key; } + else if ('microsoft' === obj.vendor) { + const o = decrypt(credential); + obj.api_key = o.api_key; + obj.region = o.region; + } return obj; })); } catch (err) { @@ -110,6 +134,11 @@ router.get('/:sid', async(req, res) => { obj.access_key_id = o.access_key_id; obj.secret_access_key = o.secret_access_key; } + else if ('microsoft' === obj.vendor) { + const o = JSON.parse(decrypt(credential)); + obj.api_key = o.api_key; + obj.region = o.region; + } res.status(200).json(obj); } catch (err) { sysError(logger, res, err); @@ -239,6 +268,29 @@ router.get('/:sid/test', async(req, res) => { } } } + else if (cred.vendor === 'microsoft') { + const {api_key, region} = credential; + if (cred.use_for_tts) { + try { + await testMicrosoftTts(logger, {api_key, region}); + 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 testMicrosoftStt(logger, {api_key, region}); + 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); } catch (err) { sysError(logger, res, err); diff --git a/lib/utils/speech-utils.js b/lib/utils/speech-utils.js index def6b66..5a34714 100644 --- a/lib/utils/speech-utils.js +++ b/lib/utils/speech-utils.js @@ -2,6 +2,7 @@ const ttsGoogle = require('@google-cloud/text-to-speech'); const sttGoogle = require('@google-cloud/speech').v1p1beta1; const Polly = require('aws-sdk/clients/polly'); const AWS = require('aws-sdk'); +const bent = require('bent'); const fs = require('fs'); const testGoogleTts = async(logger, credentials) => { @@ -52,9 +53,33 @@ const testAwsStt = (logger, credentials) => { }); }; +const testMicrosoftTts = async(logger, credentials) => { + const {api_key, region} = credentials; + + if (!api_key) throw new Error('testMicrosoftTts: credentials are missing api_key'); + if (!region) throw new Error('testMicrosoftTts: credentials are missing region'); + try { + const getJSON = bent('json', { + 'Ocp-Apim-Subscription-Key': api_key + }); + const response = await getJSON(`https://${region}.tts.speech.microsoft.com/cognitiveservices/voices/list`); + return response; + } catch (err) { + logger.info({err}, `testMicrosoftTts - failed to list voices for region ${region}`); + throw err; + } +}; + +const testMicrosoftStt = async(logger, credentials) => { + //TODO + return true; +}; + module.exports = { testGoogleTts, testGoogleStt, testAwsTts, - testAwsStt + testAwsStt, + testMicrosoftTts, + testMicrosoftStt }; diff --git a/package.json b/package.json index f7c9af9..a165898 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "dependencies": { "@google-cloud/speech": "^4.2.0", "@google-cloud/text-to-speech": "^3.1.3", + "microsoft-cognitiveservices-speech-sdk": "^1.19.0", "@jambonz/db-helpers": "^0.6.12", "@jambonz/realtimedb-helpers": "^0.4.3", "@jambonz/time-series": "^0.1.5", diff --git a/test/speech-credentials.js b/test/speech-credentials.js index 5c7d918..82791ac 100644 --- a/test/speech-credentials.js +++ b/test/speech-credentials.js @@ -7,6 +7,7 @@ const request = require('request-promise-native').defaults({ baseUrl: 'http://127.0.0.1:3000/v1' }); const {createServiceProvider, createAccount, deleteObjectBySid} = require('./utils'); +const { noopLogger } = require('@jambonz/realtimedb-helpers/lib/utils'); process.on('unhandledRejection', (reason, p) => { console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); @@ -109,6 +110,37 @@ test('speech credentials tests', async(t) => { }); t.ok(result.statusCode === 204, 'successfully deleted speech credential'); + /* add a credential for microsoft */ + if (process.env.MICROSOFT_API_KEY && process.env.MICROSOFT_REGION) { + result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, { + resolveWithFullResponse: true, + auth: authUser, + json: true, + body: { + vendor: 'microsoft', + use_for_tts: true, + api_key: process.env.MICROSOFT_API_KEY, + region: process.env.MICROSOFT_REGION + } + }); + t.ok(result.statusCode === 201, 'successfully added speech credential'); + const ms_sid = result.body.sid; + + /* test the speech credential */ + result = await request.get(`/Accounts/${account_sid}/SpeechCredentials/${ms_sid}/test`, { + resolveWithFullResponse: true, + auth: authUser, + json: true, + }); + console.log(JSON.stringify(result)); + + /* delete the credential */ + result = await request.delete(`/Accounts/${account_sid}/SpeechCredentials/${ms_sid}`, { + auth: authUser, + resolveWithFullResponse: true, + }); + t.ok(result.statusCode === 204, 'successfully deleted speech credential'); + } await deleteObjectBySid(request, '/Accounts', account_sid); await deleteObjectBySid(request, '/ServiceProviders', service_provider_sid);