add support for deepgram STT

This commit is contained in:
Dave Horton
2022-11-10 14:34:04 -05:00
parent 1b67d5f89d
commit ee5e25bb8d
7 changed files with 171 additions and 17 deletions

1
.gitignore vendored
View File

@@ -1,6 +1,7 @@
# Logs
logs
*.log
run-tests.sh
# Runtime data
pids

View File

@@ -11,7 +11,7 @@ const sql = `
function makeStrategy(logger, retrieveKey) {
return new Strategy(
async function(token, done) {
logger.debug(`validating with token ${token}`);
//logger.debug(`validating with token ${token}`);
jwt.verify(token, process.env.JWT_SECRET, async(err, decoded) => {
if (err) {
if (err.name === 'TokenExpiredError') {

View File

@@ -14,7 +14,8 @@ const {
testMicrosoftTts,
testWellSaidTts,
testNuanceStt,
testNuanceTts
testNuanceTts,
testDeepgramStt
} = require('../../utils/speech-utils');
const obscureKey = (key) => {
@@ -88,6 +89,11 @@ const encryptCredential = (obj) => {
const nuanceData = JSON.stringify({client_id, secret});
return encrypt(nuanceData);
case 'deepgram':
assert(api_key, 'invalid deepgram speech credential: api_key is required');
const deepgramData = JSON.stringify({api_key});
return encrypt(deepgramData);
default:
assert(false, `invalid or missing vendor: ${vendor}`);
}
@@ -175,6 +181,10 @@ router.get('/', async(req, res) => {
obj.client_id = o.client_id;
obj.secret = obscureKey(o.secret);
}
else if ('deepgram' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.api_key = obscureKey(o.api_key);
}
return obj;
}));
} catch (err) {
@@ -225,6 +235,10 @@ router.get('/:sid', async(req, res) => {
obj.client_id = o.client_id;
obj.secret = obscureKey(o.secret);
}
else if ('deepgram' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.api_key = obscureKey(o.api_key);
}
res.status(200).json(obj);
} catch (err) {
sysError(logger, res, err);
@@ -474,6 +488,19 @@ router.get('/:sid/test', async(req, res) => {
}
}
}
else if (cred.vendor === 'deepgram') {
const {api_key} = credential;
if (cred.use_for_stt) {
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);
}
}
}
res.status(200).json(results);
} catch (err) {
sysError(logger, res, err);

View File

@@ -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 { Deepgram } = require('@deepgram/sdk');
const bent = require('bent');
const fs = require('fs');
@@ -42,6 +43,33 @@ const testGoogleStt = async(logger, credentials) => {
}
};
const testDeepgramStt = async(logger, credentials) => {
const {api_key} = credentials;
const deepgram = new Deepgram(api_key);
const mimetype = 'audio/wav';
const source = {
buffer: fs.readFileSync(`${__dirname}/../../data/test_audio.wav`),
mimetype: mimetype
};
return new Promise((resolve, reject) => {
// Send the audio to Deepgram and get the response
deepgram.transcription
.preRecorded(source, {punctuate: true})
.then((response) => {
//logger.debug({response}, 'got transcript');
if (response?.results?.channels[0]?.alternatives?.length > 0) resolve(response);
else reject(new Error('no transcript returned'));
return;
})
.catch((err) => {
logger.info({err}, 'failed to get deepgram transcript');
reject(err);
});
});
};
const testAwsTts = (logger, credentials) => {
const polly = new Polly(credentials);
return new Promise((resolve, reject) => {
@@ -138,5 +166,6 @@ module.exports = {
testMicrosoftStt,
testWellSaidStt,
testNuanceTts,
testNuanceStt
testNuanceStt,
testDeepgramStt
};

87
package-lock.json generated
View File

@@ -9,10 +9,11 @@
"version": "v0.7.7",
"license": "MIT",
"dependencies": {
"@deepgram/sdk": "^1.10.2",
"@google-cloud/speech": "^5.1.0",
"@google-cloud/text-to-speech": "^4.0.3",
"@jambonz/db-helpers": "^0.7.3",
"@jambonz/realtimedb-helpers": "^0.5.7",
"@jambonz/realtimedb-helpers": "^0.5.9",
"@jambonz/time-series": "^0.2.5",
"argon2-ffi": "^2.0.0",
"aws-sdk": "^2.1152.0",
@@ -478,6 +479,16 @@
"node": ">=6.9.0"
}
},
"node_modules/@deepgram/sdk": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/@deepgram/sdk/-/sdk-1.10.2.tgz",
"integrity": "sha512-7f/Uya1Tu0NBcxYSTbbmDnvAQ+YvzuzipVNa1uwfcMyiQZgBVZv+E7ToJhhC7KRr/tmQjniW29RsPqhOMBN99Q==",
"dependencies": {
"bufferutil": "^4.0.6",
"utf-8-validate": "^5.0.9",
"ws": "^7.5.5"
}
},
"node_modules/@eslint/eslintrc": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz",
@@ -676,9 +687,9 @@
}
},
"node_modules/@jambonz/realtimedb-helpers": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/@jambonz/realtimedb-helpers/-/realtimedb-helpers-0.5.7.tgz",
"integrity": "sha512-TOTnFWSa4ronCdQTWfB8c5VI6DXcBEyDA4vbZnzkVAzSP90NpRPOPrvo2tEZxcGSlVIjBZew7rWgWyqkSwUT/Q==",
"version": "0.5.9",
"resolved": "https://registry.npmjs.org/@jambonz/realtimedb-helpers/-/realtimedb-helpers-0.5.9.tgz",
"integrity": "sha512-1DdEL+Zy3vcgJNXeGaiAdIe5k+3NdRdtTikJMiACrKUF1GVpnEjv/NKapr5u9FdOODblJ2bgFjktLpmSsVK/9Q==",
"dependencies": {
"@google-cloud/text-to-speech": "^4.0.3",
"@grpc/grpc-js": "^1.7.3",
@@ -1320,6 +1331,18 @@
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk="
},
"node_modules/bufferutil": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz",
"integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==",
"hasInstallScript": true,
"dependencies": {
"node-gyp-build": "^4.3.0"
},
"engines": {
"node": ">=6.14.2"
}
},
"node_modules/busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
@@ -4497,9 +4520,9 @@
}
},
"node_modules/node-gyp-build": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz",
"integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==",
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz",
"integrity": "sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==",
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
@@ -6404,6 +6427,18 @@
"resolved": "https://registry.npmjs.org/username-sync/-/username-sync-1.0.3.tgz",
"integrity": "sha512-m/7/FSqjJNAzF2La448c/aEom0gJy7HY7Y509h6l0ePvEkFictAGptwWaj1msWJ38JbfEDOUoE8kqFee9EHKdA=="
},
"node_modules/utf-8-validate": {
"version": "5.0.10",
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
"integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
"hasInstallScript": true,
"dependencies": {
"node-gyp-build": "^4.3.0"
},
"engines": {
"node": ">=6.14.2"
}
},
"node_modules/util": {
"version": "0.12.5",
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
@@ -7046,6 +7081,16 @@
"to-fast-properties": "^2.0.0"
}
},
"@deepgram/sdk": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/@deepgram/sdk/-/sdk-1.10.2.tgz",
"integrity": "sha512-7f/Uya1Tu0NBcxYSTbbmDnvAQ+YvzuzipVNa1uwfcMyiQZgBVZv+E7ToJhhC7KRr/tmQjniW29RsPqhOMBN99Q==",
"requires": {
"bufferutil": "^4.0.6",
"utf-8-validate": "^5.0.9",
"ws": "^7.5.5"
}
},
"@eslint/eslintrc": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz",
@@ -7203,9 +7248,9 @@
}
},
"@jambonz/realtimedb-helpers": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/@jambonz/realtimedb-helpers/-/realtimedb-helpers-0.5.7.tgz",
"integrity": "sha512-TOTnFWSa4ronCdQTWfB8c5VI6DXcBEyDA4vbZnzkVAzSP90NpRPOPrvo2tEZxcGSlVIjBZew7rWgWyqkSwUT/Q==",
"version": "0.5.9",
"resolved": "https://registry.npmjs.org/@jambonz/realtimedb-helpers/-/realtimedb-helpers-0.5.9.tgz",
"integrity": "sha512-1DdEL+Zy3vcgJNXeGaiAdIe5k+3NdRdtTikJMiACrKUF1GVpnEjv/NKapr5u9FdOODblJ2bgFjktLpmSsVK/9Q==",
"requires": {
"@google-cloud/text-to-speech": "^4.0.3",
"@grpc/grpc-js": "^1.7.3",
@@ -7728,6 +7773,14 @@
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk="
},
"bufferutil": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz",
"integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==",
"requires": {
"node-gyp-build": "^4.3.0"
}
},
"busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
@@ -10136,9 +10189,9 @@
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA=="
},
"node-gyp-build": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz",
"integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg=="
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz",
"integrity": "sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg=="
},
"node-object-hash": {
"version": "2.3.10",
@@ -11600,6 +11653,14 @@
"resolved": "https://registry.npmjs.org/username-sync/-/username-sync-1.0.3.tgz",
"integrity": "sha512-m/7/FSqjJNAzF2La448c/aEom0gJy7HY7Y509h6l0ePvEkFictAGptwWaj1msWJ38JbfEDOUoE8kqFee9EHKdA=="
},
"utf-8-validate": {
"version": "5.0.10",
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
"integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
"requires": {
"node-gyp-build": "^4.3.0"
}
},
"util": {
"version": "0.12.5",
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",

View File

@@ -18,10 +18,11 @@
"url": "https://github.com/jambonz/jambonz-api-server.git"
},
"dependencies": {
"@deepgram/sdk": "^1.10.2",
"@google-cloud/speech": "^5.1.0",
"@google-cloud/text-to-speech": "^4.0.3",
"@jambonz/db-helpers": "^0.7.3",
"@jambonz/realtimedb-helpers": "^0.5.7",
"@jambonz/realtimedb-helpers": "^0.5.9",
"@jambonz/time-series": "^0.2.5",
"argon2-ffi": "^2.0.0",
"aws-sdk": "^2.1152.0",

View File

@@ -135,6 +135,7 @@ test('speech credentials tests', async(t) => {
json: true,
});
console.log(JSON.stringify(result));
t.ok(result.statusCode === 200 && result.body.tts.status === 'ok', 'successfully tested speech credential for deepgram');
}
/* add a credential for wellsaid */
@@ -159,6 +160,7 @@ test('speech credentials tests', async(t) => {
json: true,
});
console.log(JSON.stringify(result));
t.ok(result.statusCode === 200 && result.body.tts.status === 'ok', 'successfully tested speech credential for wellsaid');
/* delete the credential */
result = await request.delete(`/Accounts/${account_sid}/SpeechCredentials/${ms_sid}`, {
@@ -168,6 +170,39 @@ test('speech credentials tests', async(t) => {
t.ok(result.statusCode === 204, 'successfully deleted speech credential');
}
/* add a credential for deepgram */
if (process.env.DEEPGRAM_API_KEY) {
result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
body: {
vendor: 'deepgram',
use_for_stt: true,
api_key: process.env.DEEPGRAM_API_KEY
}
});
t.ok(result.statusCode === 201, 'successfully added speech credential for deepgram');
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));
t.ok(result.statusCode === 200 && result.body.stt.status === 'ok', 'successfully tested speech credential for deepgram');
/* 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);
//t.end();