@jambonz/speech-utils 1.0.3 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/utils.js CHANGED
@@ -1,20 +1,7 @@
1
1
  const crypto = require('crypto');
2
- const {SynthesizerClient} = require('../stubs/nuance/synthesizer_grpc_pb');
3
2
  const {RivaSpeechSynthesisClient} = require('../stubs/riva/proto/riva_tts_grpc_pb');
4
- const {Pool} = require('undici');
5
- const pool = new Pool('https://auth.crt.nuance.com');
6
- const NUANCE_AUTH_ENDPOINT = 'tts.api.nuance.com:443';
7
3
  const grpc = require('@grpc/grpc-js');
8
- const formurlencoded = require('form-urlencoded');
9
- const { TMP_FOLDER, HTTP_TIMEOUT } = require('./config');
10
-
11
- const debug = require('debug')('jambonz:realtimedb-helpers');
12
- /**
13
- * Future TODO: cache recently used connections to providers
14
- * to avoid connection overhead during a call.
15
- * Will need to periodically age them out to avoid memory leaks.
16
- */
17
- //const nuanceClientMap = new Map();
4
+ const { TMP_FOLDER } = require('./config');
18
5
 
19
6
  function makeSynthKey({
20
7
  account_sid = '',
@@ -45,90 +32,12 @@ const noopLogger = {
45
32
  error: () => {}
46
33
  };
47
34
 
48
- const toBase64 = (str) => Buffer.from(str || '', 'utf8').toString('base64');
49
-
50
- function makeBasicAuthHeader(username, password) {
51
- if (!username || !password) return {};
52
- const creds = `${encodeURIComponent(username)}:${password || ''}`;
53
- const header = `Basic ${toBase64(creds)}`;
54
- return {Authorization: header};
55
- }
56
-
57
35
  function makeAwsKey(awsAccessKeyId) {
58
36
  const hash = crypto.createHash('sha1');
59
37
  hash.update(awsAccessKeyId);
60
38
  return `aws:${hash.digest('hex')}`;
61
39
  }
62
40
 
63
- function makePlayhtKey(apiKey) {
64
- const hash = crypto.createHash('sha1');
65
- hash.update(apiKey);
66
- return `playht:${hash.digest('hex')}`;
67
- }
68
- function makeVerbioKey(client_id) {
69
- const hash = crypto.createHash('sha1');
70
- hash.update(client_id);
71
- return `verbio:${hash.digest('hex')}`;
72
- }
73
-
74
- function makeNuanceKey(clientId, secret, scope) {
75
- const hash = crypto.createHash('sha1');
76
- hash.update(`${clientId}:${secret}:${scope}`);
77
- return `nuance:${hash.digest('hex')}`;
78
- }
79
-
80
- const getNuanceAccessToken = async(clientId, secret, scope = 'asr tts') => {
81
- const payload = {
82
- grant_type: 'client_credentials',
83
- scope
84
- };
85
- const auth = makeBasicAuthHeader(clientId, secret);
86
- const {statusCode, headers, body} = await pool.request({
87
- path: '/oauth2/token',
88
- method: 'POST',
89
- headers: {
90
- ...auth,
91
- 'Content-Type': 'application/x-www-form-urlencoded'
92
- },
93
- body: formurlencoded(payload),
94
- timeout: HTTP_TIMEOUT,
95
- followRedirects: false
96
- });
97
-
98
- if (200 !== statusCode) {
99
- debug({statusCode, headers, body: body.text()}, 'error fetching access token from Nuance');
100
- const err = new Error();
101
- err.statusCode = statusCode;
102
- throw err;
103
- }
104
- const json = await body.json();
105
- return json.access_token;
106
- };
107
-
108
- const createKryptonClient = async(uri) => {
109
- const client = new SynthesizerClient(uri, grpc.credentials.createInsecure());
110
- return client;
111
- };
112
-
113
- const createNuanceClient = async(access_token) => {
114
-
115
- //if (nuanceClientMap.has(access_token)) return nuanceClientMap.get(access_token);
116
-
117
- const generateMetadata = (params, callback) => {
118
- var metadata = new grpc.Metadata();
119
- metadata.add('authorization', `Bearer ${access_token}`);
120
- callback(null, metadata);
121
- };
122
-
123
- const sslCreds = grpc.credentials.createSsl();
124
- const authCreds = grpc.credentials.createFromMetadataGenerator(generateMetadata);
125
- const combined_creds = grpc.credentials.combineChannelCredentials(sslCreds, authCreds);
126
- const client = new SynthesizerClient(NUANCE_AUTH_ENDPOINT, combined_creds);
127
-
128
- //if (process.env.NUANCE_CACHE_TTS_CONNECTIONS) nuanceClientMap.set(access_token, client);
129
- return client;
130
- };
131
-
132
41
  const createRivaClient = async(rivaUri) => {
133
42
  const client = new RivaSpeechSynthesisClient(rivaUri, grpc.credentials.createInsecure());
134
43
  return client;
@@ -136,17 +45,8 @@ const createRivaClient = async(rivaUri) => {
136
45
 
137
46
  module.exports = {
138
47
  makeSynthKey,
139
- makeNuanceKey,
140
-
141
- makePlayhtKey,
142
48
  makeAwsKey,
143
- makeVerbioKey,
144
- getNuanceAccessToken,
145
- createNuanceClient,
146
- createKryptonClient,
147
49
  createRivaClient,
148
- makeBasicAuthHeader,
149
- NUANCE_AUTH_ENDPOINT,
150
50
  noopLogger,
151
51
  makeFilePath
152
52
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jambonz/speech-utils",
3
- "version": "1.0.3",
3
+ "version": "1.0.6",
4
4
  "description": "TTS-related speech utilities for jambonz",
5
5
  "main": "index.js",
6
6
  "author": "Dave Horton",
@@ -13,7 +13,6 @@
13
13
  "coverage": "nyc --reporter html --report-dir ./coverage npm run test",
14
14
  "jslint": "eslint index.js lib",
15
15
  "jslint:fix": "npm run jslint --fix",
16
- "build": "./build_stubs.sh",
17
16
  "prepare": "husky"
18
17
  },
19
18
  "repository": {
@@ -35,7 +34,6 @@
35
34
  "@jambonz/realtimedb-helpers": "^0.8.7",
36
35
  "bent": "^7.3.12",
37
36
  "debug": "^4.3.4",
38
- "form-urlencoded": "^6.1.4",
39
37
  "google-protobuf": "^3.21.2",
40
38
  "microsoft-cognitiveservices-speech-sdk": "1.38.0",
41
39
  "openai": "^4.98.0",
package/test/index.js CHANGED
@@ -3,5 +3,4 @@ require('./synth');
3
3
  require('./list-voices');
4
4
  require('./aws');
5
5
 
6
- require('./nuance');
7
6
  require('./docker_stop');
@@ -12,99 +12,6 @@ const stats = {
12
12
  histogram: () => {}
13
13
  };
14
14
 
15
- test('Verbio - get Access key and voices', async(t) => {
16
- const fn = require('..');
17
- const {client, getTtsVoices, getVerbioAccessToken} = fn(opts, logger);
18
- if (!process.env.VERBIO_CLIENT_ID || !process.env.VERBIO_CLIENT_SECRET) {
19
- t.pass('skipping Verbio test since no Verbio Keys provided');
20
- t.end();
21
- client.quit();
22
- return;
23
- }
24
-
25
- try {
26
- const credentials = {
27
- client_id: process.env.VERBIO_CLIENT_ID,
28
- client_secret: process.env.VERBIO_CLIENT_SECRET
29
- };
30
- let obj = await getVerbioAccessToken(credentials);
31
- t.ok(obj.access_token , 'successfully received access token not from cache');
32
- const voices = await getTtsVoices({vendor: 'verbio', credentials});
33
- t.ok(voices && voices.length != 0, 'successfully received verbio voices');
34
- } catch (err) {
35
- console.error(err);
36
- t.end(err);
37
- }
38
- client.quit();
39
- });
40
-
41
- test('Nuance hosted tests', async(t) => {
42
- const fn = require('..');
43
- const {client, getTtsVoices} = fn(opts, logger);
44
-
45
- if (!process.env.NUANCE_CLIENT_ID || !process.env.NUANCE_SECRET ) {
46
- t.pass('skipping Nuance hosted test since no Nuance client_id and secret provided');
47
- t.end();
48
- client.quit();
49
- return;
50
- }
51
- try {
52
- const opts = {
53
- vendor: 'nuance',
54
- credentials: {
55
- client_id: process.env.NUANCE_CLIENT_ID,
56
- secret: process.env.NUANCE_SECRET
57
- }
58
- };
59
- let voices = await getTtsVoices(opts);
60
- t.ok(voices.length > 0 && voices[0].language,
61
- `GetVoices: successfully retrieved ${voices.length} voices from Nuance`);
62
-
63
- await client.flushall();
64
-
65
- t.end();
66
-
67
- }
68
- catch (err) {
69
- console.error(err);
70
- t.end(err);
71
- }
72
- client.quit();
73
- });
74
-
75
- test('Nuance on-prem tests', async(t) => {
76
- const fn = require('..');
77
- const {client, getTtsVoices} = fn(opts, logger);
78
-
79
- if (!process.env.NUANCE_TTS_URI ) {
80
- t.pass('skipping Nuance on-prem test since no Nuance uri provided');
81
- t.end();
82
- client.quit();
83
- return;
84
- }
85
- try {
86
- const opts = {
87
- vendor: 'nuance',
88
- credentials: {
89
- nuance_tts_uri: process.env.NUANCE_TTS_URI
90
- }
91
- };
92
- let voices = await getTtsVoices(opts);
93
- t.ok(voices.length > 0 && voices[0].language,
94
- `GetVoices: successfully retrieved ${voices.length} voices from Nuance`);
95
-
96
- await client.flushall();
97
-
98
- t.end();
99
-
100
- }
101
- catch (err) {
102
- console.error(err);
103
- t.end(err);
104
- }
105
- client.quit();
106
- });
107
-
108
15
  test('Google tests', async(t) => {
109
16
  const fn = require('..');
110
17
  const {client, getTtsVoices} = fn(opts, logger);
package/test/synth.js CHANGED
@@ -759,82 +759,6 @@ test('Azure custom voice speech synth tests', async(t) => {
759
759
  client.quit();
760
760
  });
761
761
 
762
- test('Nuance hosted speech synth tests', async(t) => {
763
- const fn = require('..');
764
- const {synthAudio, client} = fn(opts, logger);
765
-
766
- if (!process.env.NUANCE_CLIENT_ID || !process.env.NUANCE_SECRET) {
767
- t.pass('skipping Nuance speech synth tests since NUANCE_CLIENT_ID or NUANCE_SECRET not provided');
768
- return t.end();
769
- }
770
- try {
771
- let opts = await synthAudio(stats, {
772
- vendor: 'nuance',
773
- credentials: {
774
- client_id: process.env.NUANCE_CLIENT_ID,
775
- secret: process.env.NUANCE_SECRET,
776
- },
777
- language: 'en-US',
778
- voice: 'Evan',
779
- text: 'This is a test. This is only a test',
780
- });
781
- t.ok(!opts.servedFromCache, `successfully synthesized nuance audio to ${opts.filePath}`);
782
-
783
- opts = await synthAudio(stats, {
784
- vendor: 'nuance',
785
- credentials: {
786
- client_id: process.env.NUANCE_CLIENT_ID,
787
- secret: process.env.NUANCE_SECRET,
788
- },
789
- language: 'en-US',
790
- voice: 'Evan',
791
- text: 'This is a test. This is only a test',
792
- });
793
- t.ok(opts.servedFromCache, `successfully retrieved nuance audio from cache ${opts.filePath}`);
794
- } catch (err) {
795
- console.error(err);
796
- t.end(err);
797
- }
798
- client.quit();
799
- });
800
-
801
- test('Nuance on-prem speech synth tests', async(t) => {
802
- const fn = require('..');
803
- const {synthAudio, client} = fn(opts, logger);
804
-
805
- if (!process.env.NUANCE_TTS_URI) {
806
- t.pass('skipping Nuance on prem speech synth tests since NUANCE_TTS_URI not provided');
807
- return t.end();
808
- }
809
- try {
810
- let opts = await synthAudio(stats, {
811
- vendor: 'nuance',
812
- credentials: {
813
- nuance_tts_uri: process.env.NUANCE_TTS_URI
814
- },
815
- language: 'en-US',
816
- voice: 'Evan',
817
- text: 'This is a test of on-prem. This is only a test',
818
- });
819
- t.ok(!opts.servedFromCache, `successfully synthesized nuance audio to ${opts.filePath}`);
820
-
821
- opts = await synthAudio(stats, {
822
- vendor: 'nuance',
823
- credentials: {
824
- nuance_tts_uri: process.env.NUANCE_TTS_URI
825
- },
826
- language: 'en-US',
827
- voice: 'Evan',
828
- text: 'This is a test of on-prem. This is only a test',
829
- });
830
- t.ok(opts.servedFromCache, `successfully retrieved nuance audio from cache ${opts.filePath}`);
831
- } catch (err) {
832
- console.error(err);
833
- t.end(err);
834
- }
835
- client.quit();
836
- });
837
-
838
762
  test('Nvidia speech synth tests', async(t) => {
839
763
  const fn = require('..');
840
764
  const {synthAudio, client} = fn(opts, logger);
@@ -975,55 +899,6 @@ test('Elevenlabs speech synth tests', async(t) => {
975
899
  client.quit();
976
900
  });
977
901
 
978
- const testPlayHT = async(t, voice_engine) => {
979
- const fn = require('..');
980
- const {synthAudio, client} = fn(opts, logger);
981
-
982
- if (!process.env.PLAYHT_API_KEY || !process.env.PLAYHT_USER_ID) {
983
- t.pass('skipping PlayHT speech synth tests since PLAYHT_API_KEY or PLAYHT_USER_ID is/are not provided');
984
- return t.end();
985
- }
986
- const text = 'Hi there and welcome to jambones! ' + Date.now();
987
- try {
988
- const opts = await synthAudio(stats, {
989
- vendor: 'playht',
990
- credentials: {
991
- api_key: process.env.PLAYHT_API_KEY,
992
- user_id: process.env.PLAYHT_USER_ID,
993
- voice_engine,
994
- options: JSON.stringify({
995
- quality: 'medium',
996
- speed: 1,
997
- seed: 1,
998
- temperature: 1,
999
- emotion: 'female_happy',
1000
- voice_guidance: 3,
1001
- style_guidance: 20,
1002
- text_guidance: 1,
1003
- })
1004
- },
1005
- language: 'english',
1006
- voice: 's3://voice-cloning-zero-shot/d9ff78ba-d016-47f6-b0ef-dd630f59414e/female-cs/manifest.json',
1007
- text,
1008
- renderForCaching: true
1009
- });
1010
- t.ok(!opts.servedFromCache, `successfully playht eleven audio to ${opts.filePath}`);
1011
-
1012
- } catch (err) {
1013
- console.error(JSON.stringify(err));
1014
- t.end(err);
1015
- }
1016
- client.quit();
1017
- };
1018
-
1019
- test('PlayHT speech synth tests', async(t) => {
1020
- await testPlayHT(t, 'PlayHT2.0-turbo');
1021
- });
1022
-
1023
- test('PlayHT3.0 speech synth tests', async(t) => {
1024
- await testPlayHT(t, 'Play3.0');
1025
- });
1026
-
1027
902
  test('Cartesia speech synth tests', async(t) => {
1028
903
  const fn = require('..');
1029
904
  const {synthAudio, client} = fn(opts, logger);
@@ -1215,39 +1090,6 @@ test('whisper speech synth tests', async(t) => {
1215
1090
  client.quit();
1216
1091
  });
1217
1092
 
1218
- test('Verbio speech synth tests', async(t) => {
1219
- const fn = require('..');
1220
- const {synthAudio, client} = fn(opts, logger);
1221
-
1222
- if (!process.env.VERBIO_CLIENT_ID || !process.env.VERBIO_CLIENT_SECRET) {
1223
- t.pass('skipping Verbio Synthesize test since no Verbio Keys provided');
1224
- t.end();
1225
- client.quit();
1226
- return;
1227
- }
1228
-
1229
- const text = 'Hi there and welcome to jambones!';
1230
- try {
1231
- let opts = await synthAudio(stats, {
1232
- vendor: 'verbio',
1233
- credentials: {
1234
- client_id: process.env.VERBIO_CLIENT_ID,
1235
- client_secret: process.env.VERBIO_CLIENT_SECRET
1236
- },
1237
- language: 'en-US',
1238
- voice: 'tommy_en-us',
1239
- text,
1240
- renderForCaching: true
1241
- });
1242
- t.ok(!opts.servedFromCache, `successfully synthesized whisper audio to ${opts.filePath}`);
1243
-
1244
- } catch (err) {
1245
- console.error(JSON.stringify(err));
1246
- t.end(err);
1247
- }
1248
- client.quit();
1249
- })
1250
-
1251
1093
  test('Deepgram speech synth tests', async(t) => {
1252
1094
  const fn = require('..');
1253
1095
  const {synthAudio, client} = fn(opts, logger);
package/build_stubs.sh DELETED
@@ -1,11 +0,0 @@
1
- #!/bin/sh
2
-
3
- mkdir -p stubs/nuance
4
-
5
- for FILE in ./protos/nuance/*; do
6
- grpc_tools_node_protoc \
7
- --js_out=import_style=commonjs,binary:./stubs/nuance \
8
- --grpc_out=grpc_js:./stubs/nuance \
9
- --proto_path=./protos/nuance \
10
- $FILE
11
- done
@@ -1,49 +0,0 @@
1
- const formurlencoded = require('form-urlencoded');
2
- const {Pool} = require('undici');
3
- const pool = new Pool('https://auth.crt.nuance.com');
4
- const {makeNuanceKey, makeBasicAuthHeader, noopLogger} = require('./utils');
5
- const { HTTP_TIMEOUT } = require('./config');
6
- const debug = require('debug')('jambonz:realtimedb-helpers');
7
-
8
- async function getNuanceAccessToken(client, logger, clientId, secret, scope) {
9
- logger = logger || noopLogger;
10
- try {
11
- const key = makeNuanceKey(clientId, secret, scope);
12
- const access_token = await client.get(key);
13
- if (access_token) return {access_token, servedFromCache: true};
14
-
15
- /* access token not found in cache, so fetch it from Nuance */
16
- const payload = {
17
- grant_type: 'client_credentials',
18
- scope
19
- };
20
- const auth = makeBasicAuthHeader(clientId, secret);
21
- const {statusCode, headers, body} = await pool.request({
22
- path: '/oauth2/token',
23
- method: 'POST',
24
- headers: {
25
- ...auth,
26
- 'Content-Type': 'application/x-www-form-urlencoded'
27
- },
28
- body: formurlencoded(payload),
29
- timeout: HTTP_TIMEOUT,
30
- followRedirects: false
31
- });
32
-
33
- if (200 !== statusCode) {
34
- logger.debug({statusCode, headers, body: body.text()}, 'error fetching access token from Nuance');
35
- const err = new Error();
36
- err.statusCode = statusCode;
37
- throw err;
38
- }
39
- const json = await body.json();
40
- await client.set(key, json.access_token, 'EX', json.expires_in - 30);
41
- return {...json, servedFromCache: false};
42
- } catch (err) {
43
- debug(err, `getNuanceAccessToken: Error retrieving Nuance access token for client_id ${clientId}`);
44
- logger.error(err, `getNuanceAccessToken: Error retrieving Nuance access token for client_id ${clientId}`);
45
- throw err;
46
- }
47
- }
48
-
49
- module.exports = getNuanceAccessToken;
@@ -1,51 +0,0 @@
1
- const {Pool} = require('undici');
2
- const { noopLogger, makeVerbioKey } = require('./utils');
3
- const { HTTP_TIMEOUT } = require('./config');
4
- const pool = new Pool('https://auth.speechcenter.verbio.com:444');
5
- const debug = require('debug')('jambonz:realtimedb-helpers');
6
-
7
- async function getVerbioAccessToken(client, logger, credentials) {
8
- logger = logger || noopLogger;
9
- const { client_id, client_secret } = credentials;
10
- try {
11
- const key = makeVerbioKey(client_id);
12
- const access_token = await client.get(key);
13
- if (access_token) {
14
- return {access_token, servedFromCache: true};
15
- }
16
-
17
- const payload = {
18
- client_id,
19
- client_secret
20
- };
21
-
22
- const {statusCode, headers, body} = await pool.request({
23
- path: '/api/v1/token',
24
- method: 'POST',
25
- headers: {
26
- 'Content-Type': 'application/json',
27
- 'User-Agent': 'jambonz'
28
- },
29
- body: JSON.stringify(payload),
30
- timeout: HTTP_TIMEOUT,
31
- followRedirects: false
32
- });
33
-
34
- if (200 !== statusCode) {
35
- logger.debug({statusCode, headers, body: await body.text()}, 'error fetching access token from Verbio');
36
- const err = new Error();
37
- err.statusCode = statusCode;
38
- throw err;
39
- }
40
- const json = await body.json();
41
- const expiry = Math.floor(json.expiration_time - Date.now() / 1000 - 30);
42
- await client.set(key, json.access_token, 'EX', expiry);
43
- return {...json, servedFromCache: false};
44
- } catch (err) {
45
- debug(err, `getVerbioAccessToken: Error retrieving Verbio access token for client_id ${client_id}`);
46
- logger.error(err, `getVerbioAccessToken: Error retrieving Verbio access token for client_id ${client_id}`);
47
- throw err;
48
- }
49
- }
50
-
51
- module.exports = getVerbioAccessToken;