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
logs logs
*.log *.log
run-tests.sh
# Runtime data # Runtime data
pids pids

View File

@@ -11,7 +11,7 @@ const sql = `
function makeStrategy(logger, retrieveKey) { function makeStrategy(logger, retrieveKey) {
return new Strategy( return new Strategy(
async function(token, done) { 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) => { jwt.verify(token, process.env.JWT_SECRET, async(err, decoded) => {
if (err) { if (err) {
if (err.name === 'TokenExpiredError') { if (err.name === 'TokenExpiredError') {

View File

@@ -14,7 +14,8 @@ const {
testMicrosoftTts, testMicrosoftTts,
testWellSaidTts, testWellSaidTts,
testNuanceStt, testNuanceStt,
testNuanceTts testNuanceTts,
testDeepgramStt
} = require('../../utils/speech-utils'); } = require('../../utils/speech-utils');
const obscureKey = (key) => { const obscureKey = (key) => {
@@ -88,6 +89,11 @@ const encryptCredential = (obj) => {
const nuanceData = JSON.stringify({client_id, secret}); const nuanceData = JSON.stringify({client_id, secret});
return encrypt(nuanceData); 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: default:
assert(false, `invalid or missing vendor: ${vendor}`); assert(false, `invalid or missing vendor: ${vendor}`);
} }
@@ -175,6 +181,10 @@ router.get('/', async(req, res) => {
obj.client_id = o.client_id; obj.client_id = o.client_id;
obj.secret = obscureKey(o.secret); 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; return obj;
})); }));
} catch (err) { } catch (err) {
@@ -225,6 +235,10 @@ router.get('/:sid', async(req, res) => {
obj.client_id = o.client_id; obj.client_id = o.client_id;
obj.secret = obscureKey(o.secret); 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); res.status(200).json(obj);
} catch (err) { } catch (err) {
sysError(logger, res, 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); res.status(200).json(results);
} catch (err) { } catch (err) {
sysError(logger, res, 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 sttGoogle = require('@google-cloud/speech').v1p1beta1;
const Polly = require('aws-sdk/clients/polly'); const Polly = require('aws-sdk/clients/polly');
const AWS = require('aws-sdk'); const AWS = require('aws-sdk');
const { Deepgram } = require('@deepgram/sdk');
const bent = require('bent'); const bent = require('bent');
const fs = require('fs'); 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 testAwsTts = (logger, credentials) => {
const polly = new Polly(credentials); const polly = new Polly(credentials);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@@ -138,5 +166,6 @@ module.exports = {
testMicrosoftStt, testMicrosoftStt,
testWellSaidStt, testWellSaidStt,
testNuanceTts, testNuanceTts,
testNuanceStt testNuanceStt,
testDeepgramStt
}; };

87
package-lock.json generated
View File

@@ -9,10 +9,11 @@
"version": "v0.7.7", "version": "v0.7.7",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@deepgram/sdk": "^1.10.2",
"@google-cloud/speech": "^5.1.0", "@google-cloud/speech": "^5.1.0",
"@google-cloud/text-to-speech": "^4.0.3", "@google-cloud/text-to-speech": "^4.0.3",
"@jambonz/db-helpers": "^0.7.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", "@jambonz/time-series": "^0.2.5",
"argon2-ffi": "^2.0.0", "argon2-ffi": "^2.0.0",
"aws-sdk": "^2.1152.0", "aws-sdk": "^2.1152.0",
@@ -478,6 +479,16 @@
"node": ">=6.9.0" "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": { "node_modules/@eslint/eslintrc": {
"version": "0.4.3", "version": "0.4.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz",
@@ -676,9 +687,9 @@
} }
}, },
"node_modules/@jambonz/realtimedb-helpers": { "node_modules/@jambonz/realtimedb-helpers": {
"version": "0.5.7", "version": "0.5.9",
"resolved": "https://registry.npmjs.org/@jambonz/realtimedb-helpers/-/realtimedb-helpers-0.5.7.tgz", "resolved": "https://registry.npmjs.org/@jambonz/realtimedb-helpers/-/realtimedb-helpers-0.5.9.tgz",
"integrity": "sha512-TOTnFWSa4ronCdQTWfB8c5VI6DXcBEyDA4vbZnzkVAzSP90NpRPOPrvo2tEZxcGSlVIjBZew7rWgWyqkSwUT/Q==", "integrity": "sha512-1DdEL+Zy3vcgJNXeGaiAdIe5k+3NdRdtTikJMiACrKUF1GVpnEjv/NKapr5u9FdOODblJ2bgFjktLpmSsVK/9Q==",
"dependencies": { "dependencies": {
"@google-cloud/text-to-speech": "^4.0.3", "@google-cloud/text-to-speech": "^4.0.3",
"@grpc/grpc-js": "^1.7.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", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" "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": { "node_modules/busboy": {
"version": "1.6.0", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
@@ -4497,9 +4520,9 @@
} }
}, },
"node_modules/node-gyp-build": { "node_modules/node-gyp-build": {
"version": "4.2.3", "version": "4.5.0",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz",
"integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==", "integrity": "sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==",
"bin": { "bin": {
"node-gyp-build": "bin.js", "node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js", "node-gyp-build-optional": "optional.js",
@@ -6404,6 +6427,18 @@
"resolved": "https://registry.npmjs.org/username-sync/-/username-sync-1.0.3.tgz", "resolved": "https://registry.npmjs.org/username-sync/-/username-sync-1.0.3.tgz",
"integrity": "sha512-m/7/FSqjJNAzF2La448c/aEom0gJy7HY7Y509h6l0ePvEkFictAGptwWaj1msWJ38JbfEDOUoE8kqFee9EHKdA==" "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": { "node_modules/util": {
"version": "0.12.5", "version": "0.12.5",
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
@@ -7046,6 +7081,16 @@
"to-fast-properties": "^2.0.0" "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": { "@eslint/eslintrc": {
"version": "0.4.3", "version": "0.4.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz",
@@ -7203,9 +7248,9 @@
} }
}, },
"@jambonz/realtimedb-helpers": { "@jambonz/realtimedb-helpers": {
"version": "0.5.7", "version": "0.5.9",
"resolved": "https://registry.npmjs.org/@jambonz/realtimedb-helpers/-/realtimedb-helpers-0.5.7.tgz", "resolved": "https://registry.npmjs.org/@jambonz/realtimedb-helpers/-/realtimedb-helpers-0.5.9.tgz",
"integrity": "sha512-TOTnFWSa4ronCdQTWfB8c5VI6DXcBEyDA4vbZnzkVAzSP90NpRPOPrvo2tEZxcGSlVIjBZew7rWgWyqkSwUT/Q==", "integrity": "sha512-1DdEL+Zy3vcgJNXeGaiAdIe5k+3NdRdtTikJMiACrKUF1GVpnEjv/NKapr5u9FdOODblJ2bgFjktLpmSsVK/9Q==",
"requires": { "requires": {
"@google-cloud/text-to-speech": "^4.0.3", "@google-cloud/text-to-speech": "^4.0.3",
"@grpc/grpc-js": "^1.7.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", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" "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": { "busboy": {
"version": "1.6.0", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
@@ -10136,9 +10189,9 @@
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==" "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA=="
}, },
"node-gyp-build": { "node-gyp-build": {
"version": "4.2.3", "version": "4.5.0",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz",
"integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==" "integrity": "sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg=="
}, },
"node-object-hash": { "node-object-hash": {
"version": "2.3.10", "version": "2.3.10",
@@ -11600,6 +11653,14 @@
"resolved": "https://registry.npmjs.org/username-sync/-/username-sync-1.0.3.tgz", "resolved": "https://registry.npmjs.org/username-sync/-/username-sync-1.0.3.tgz",
"integrity": "sha512-m/7/FSqjJNAzF2La448c/aEom0gJy7HY7Y509h6l0ePvEkFictAGptwWaj1msWJ38JbfEDOUoE8kqFee9EHKdA==" "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": { "util": {
"version": "0.12.5", "version": "0.12.5",
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", "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" "url": "https://github.com/jambonz/jambonz-api-server.git"
}, },
"dependencies": { "dependencies": {
"@deepgram/sdk": "^1.10.2",
"@google-cloud/speech": "^5.1.0", "@google-cloud/speech": "^5.1.0",
"@google-cloud/text-to-speech": "^4.0.3", "@google-cloud/text-to-speech": "^4.0.3",
"@jambonz/db-helpers": "^0.7.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", "@jambonz/time-series": "^0.2.5",
"argon2-ffi": "^2.0.0", "argon2-ffi": "^2.0.0",
"aws-sdk": "^2.1152.0", "aws-sdk": "^2.1152.0",

View File

@@ -135,6 +135,7 @@ test('speech credentials tests', async(t) => {
json: true, json: true,
}); });
console.log(JSON.stringify(result)); 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 */ /* add a credential for wellsaid */
@@ -159,6 +160,7 @@ test('speech credentials tests', async(t) => {
json: true, json: true,
}); });
console.log(JSON.stringify(result)); console.log(JSON.stringify(result));
t.ok(result.statusCode === 200 && result.body.tts.status === 'ok', 'successfully tested speech credential for wellsaid');
/* delete the credential */ /* delete the credential */
result = await request.delete(`/Accounts/${account_sid}/SpeechCredentials/${ms_sid}`, { 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'); 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, '/Accounts', account_sid);
await deleteObjectBySid(request, '/ServiceProviders', service_provider_sid); await deleteObjectBySid(request, '/ServiceProviders', service_provider_sid);
//t.end(); //t.end();