@jambonz/speech-utils 0.0.51 → 0.1.0
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/index.js +2 -2
- package/lib/get-aws-sts-token.js +24 -15
- package/lib/get-tts-voices.js +22 -11
- package/lib/synth-audio.js +25 -11
- package/package.json +1 -1
- package/test/synth.js +27 -0
package/index.js
CHANGED
|
@@ -13,10 +13,10 @@ module.exports = (opts, logger) => {
|
|
|
13
13
|
getTtsSize: require('./lib/get-tts-size').bind(null, client, logger),
|
|
14
14
|
purgeTtsCache: require('./lib/purge-tts-cache').bind(null, client, logger),
|
|
15
15
|
addFileToCache: require('./lib/add-file-to-cache').bind(null, client, logger),
|
|
16
|
-
synthAudio: require('./lib/synth-audio').bind(null, client, logger),
|
|
16
|
+
synthAudio: require('./lib/synth-audio').bind(null, client, createHash, retrieveHash, logger),
|
|
17
17
|
getNuanceAccessToken: require('./lib/get-nuance-access-token').bind(null, client, logger),
|
|
18
18
|
getIbmAccessToken: require('./lib/get-ibm-access-token').bind(null, client, logger),
|
|
19
19
|
getAwsAuthToken: require('./lib/get-aws-sts-token').bind(null, logger, createHash, retrieveHash),
|
|
20
|
-
getTtsVoices: require('./lib/get-tts-voices').bind(null, client, logger),
|
|
20
|
+
getTtsVoices: require('./lib/get-tts-voices').bind(null, client, createHash, retrieveHash, logger),
|
|
21
21
|
};
|
|
22
22
|
};
|
package/lib/get-aws-sts-token.js
CHANGED
|
@@ -1,32 +1,41 @@
|
|
|
1
|
-
const { STSClient, GetSessionTokenCommand } = require('@aws-sdk/client-sts');
|
|
1
|
+
const { STSClient, GetSessionTokenCommand, AssumeRoleCommand } = require('@aws-sdk/client-sts');
|
|
2
2
|
const {makeAwsKey, noopLogger} = require('./utils');
|
|
3
3
|
const debug = require('debug')('jambonz:speech-utils');
|
|
4
4
|
const EXPIRY = 3600;
|
|
5
5
|
|
|
6
6
|
async function getAwsAuthToken(
|
|
7
|
-
logger,
|
|
8
|
-
|
|
9
|
-
awsAccessKeyId, awsSecretAccessKey, awsRegion) {
|
|
7
|
+
logger, createHash, retrieveHash,
|
|
8
|
+
awsAccessKeyId, awsSecretAccessKey, awsRegion, roleArn = null) {
|
|
10
9
|
logger = logger || noopLogger;
|
|
11
10
|
try {
|
|
12
|
-
const key = makeAwsKey(awsAccessKeyId);
|
|
11
|
+
const key = makeAwsKey(roleArn || awsAccessKeyId);
|
|
13
12
|
const obj = await retrieveHash(key);
|
|
14
13
|
if (obj) return {...obj, servedFromCache: true};
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
region: awsRegion
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
15
|
+
let data;
|
|
16
|
+
if (roleArn) {
|
|
17
|
+
const stsClient = new STSClient({ region: awsRegion});
|
|
18
|
+
const roleToAssume = { RoleArn: roleArn, RoleSessionName: 'Jambonz_Speech', DurationSeconds: EXPIRY};
|
|
19
|
+
const command = new AssumeRoleCommand(roleToAssume);
|
|
20
|
+
|
|
21
|
+
data = await stsClient.send(command);
|
|
22
|
+
} else {
|
|
23
|
+
/* access token not found in cache, so generate it using STS */
|
|
24
|
+
const stsClient = new STSClient({
|
|
25
|
+
region: awsRegion,
|
|
26
|
+
credentials: {
|
|
27
|
+
accessKeyId: awsAccessKeyId,
|
|
28
|
+
secretAccessKey: awsSecretAccessKey,
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
const command = new GetSessionTokenCommand({DurationSeconds: EXPIRY});
|
|
32
|
+
data = await stsClient.send(command);
|
|
33
|
+
}
|
|
26
34
|
|
|
27
35
|
const credentials = {
|
|
28
36
|
accessKeyId: data.Credentials.AccessKeyId,
|
|
29
37
|
secretAccessKey: data.Credentials.SecretAccessKey,
|
|
38
|
+
sessionToken: data.Credentials.SessionToken,
|
|
30
39
|
securityToken: data.Credentials.SessionToken
|
|
31
40
|
};
|
|
32
41
|
|
package/lib/get-tts-voices.js
CHANGED
|
@@ -6,6 +6,7 @@ const TextToSpeechV1 = require('ibm-watson/text-to-speech/v1');
|
|
|
6
6
|
const { IamAuthenticator } = require('ibm-watson/auth');
|
|
7
7
|
const ttsGoogle = require('@google-cloud/text-to-speech');
|
|
8
8
|
const { PollyClient, DescribeVoicesCommand } = require('@aws-sdk/client-polly');
|
|
9
|
+
const getAwsAuthToken = require('./get-aws-sts-token');
|
|
9
10
|
|
|
10
11
|
const getIbmVoices = async(client, logger, credentials) => {
|
|
11
12
|
const {tts_region, tts_api_key} = credentials;
|
|
@@ -87,16 +88,26 @@ const getGoogleVoices = async(_client, logger, credentials) => {
|
|
|
87
88
|
return await client.listVoices();
|
|
88
89
|
};
|
|
89
90
|
|
|
90
|
-
const getAwsVoices = async(_client, logger, credentials) => {
|
|
91
|
+
const getAwsVoices = async(_client, createHash, retrieveHash, logger, credentials) => {
|
|
91
92
|
try {
|
|
92
|
-
const {region, accessKeyId, secretAccessKey} = credentials;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
93
|
+
const {region, accessKeyId, secretAccessKey, roleArn} = credentials;
|
|
94
|
+
let client = null;
|
|
95
|
+
if (accessKeyId && secretAccessKey) {
|
|
96
|
+
client = new PollyClient({
|
|
97
|
+
region,
|
|
98
|
+
credentials: {
|
|
99
|
+
accessKeyId,
|
|
100
|
+
secretAccessKey
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
} else if (roleArn) {
|
|
104
|
+
client = new PollyClient({
|
|
105
|
+
region,
|
|
106
|
+
credentials: await getAwsAuthToken(logger, createHash, retrieveHash, null, null, region, roleArn),
|
|
107
|
+
});
|
|
108
|
+
} else {
|
|
109
|
+
client = new PollyClient({region});
|
|
110
|
+
}
|
|
100
111
|
const command = new DescribeVoicesCommand({});
|
|
101
112
|
const response = await client.send(command);
|
|
102
113
|
return response;
|
|
@@ -122,7 +133,7 @@ const getAwsVoices = async(_client, logger, credentials) => {
|
|
|
122
133
|
* @returns object containing filepath to an mp3 file in the /tmp folder containing
|
|
123
134
|
* the synthesized audio, and a variable indicating whether it was served from cache
|
|
124
135
|
*/
|
|
125
|
-
async function getTtsVoices(client, logger, {vendor, credentials}) {
|
|
136
|
+
async function getTtsVoices(client, createHash, retrieveHash, logger, {vendor, credentials}) {
|
|
126
137
|
logger = logger || noopLogger;
|
|
127
138
|
|
|
128
139
|
assert.ok(['nuance', 'ibm', 'google', 'aws', 'polly'].includes(vendor),
|
|
@@ -137,7 +148,7 @@ async function getTtsVoices(client, logger, {vendor, credentials}) {
|
|
|
137
148
|
return getGoogleVoices(client, logger, credentials);
|
|
138
149
|
case 'aws':
|
|
139
150
|
case 'polly':
|
|
140
|
-
return getAwsVoices(client, logger, credentials);
|
|
151
|
+
return getAwsVoices(client, createHash, retrieveHash, logger, credentials);
|
|
141
152
|
default:
|
|
142
153
|
break;
|
|
143
154
|
}
|
package/lib/synth-audio.js
CHANGED
|
@@ -39,6 +39,7 @@ const debug = require('debug')('jambonz:realtimedb-helpers');
|
|
|
39
39
|
const EXPIRES = (process.env.JAMBONES_TTS_CACHE_DURATION_MINS || 4 * 60) * 60; // cache tts for 4 hours
|
|
40
40
|
const TMP_FOLDER = '/tmp';
|
|
41
41
|
const OpenAI = require('openai');
|
|
42
|
+
const getAwsAuthToken = require('./get-aws-sts-token');
|
|
42
43
|
|
|
43
44
|
|
|
44
45
|
const trimTrailingSilence = (buffer) => {
|
|
@@ -75,7 +76,7 @@ const trimTrailingSilence = (buffer) => {
|
|
|
75
76
|
* @returns object containing filepath to an mp3 file in the /tmp folder containing
|
|
76
77
|
* the synthesized audio, and a variable indicating whether it was served from cache
|
|
77
78
|
*/
|
|
78
|
-
async function synthAudio(client, logger, stats, { account_sid,
|
|
79
|
+
async function synthAudio(client, createHash, retrieveHash, logger, stats, { account_sid,
|
|
79
80
|
vendor, language, voice, gender, text, engine, salt, model, credentials, deploymentId,
|
|
80
81
|
disableTtsCache, renderForCaching, disableTtsStreaming, options
|
|
81
82
|
}) {
|
|
@@ -187,7 +188,8 @@ async function synthAudio(client, logger, stats, { account_sid,
|
|
|
187
188
|
case 'aws':
|
|
188
189
|
case 'polly':
|
|
189
190
|
vendorLabel = 'aws';
|
|
190
|
-
audioBuffer = await synthPolly(
|
|
191
|
+
audioBuffer = await synthPolly(createHash, retrieveHash, logger,
|
|
192
|
+
{credentials, stats, language, voice, text, engine});
|
|
191
193
|
break;
|
|
192
194
|
case 'azure':
|
|
193
195
|
case 'microsoft':
|
|
@@ -263,16 +265,28 @@ async function synthAudio(client, logger, stats, { account_sid,
|
|
|
263
265
|
});
|
|
264
266
|
}
|
|
265
267
|
|
|
266
|
-
const synthPolly = async(
|
|
268
|
+
const synthPolly = async(createHash, retrieveHash, logger,
|
|
269
|
+
{credentials, stats, language, voice, engine, text}) => {
|
|
267
270
|
try {
|
|
268
|
-
const {region, accessKeyId, secretAccessKey} = credentials;
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
271
|
+
const {region, accessKeyId, secretAccessKey, roleArn} = credentials;
|
|
272
|
+
let polly;
|
|
273
|
+
if (accessKeyId && secretAccessKey) {
|
|
274
|
+
polly = new PollyClient({
|
|
275
|
+
region,
|
|
276
|
+
credentials: {
|
|
277
|
+
accessKeyId,
|
|
278
|
+
secretAccessKey
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
} else if (roleArn) {
|
|
282
|
+
polly = new PollyClient({
|
|
283
|
+
region,
|
|
284
|
+
credentials: await getAwsAuthToken(logger, createHash, retrieveHash, null, null, region, roleArn),
|
|
285
|
+
});
|
|
286
|
+
} else {
|
|
287
|
+
// AWS RoleArn assigned to Instance profile
|
|
288
|
+
polly = new PollyClient({region});
|
|
289
|
+
}
|
|
276
290
|
const opts = {
|
|
277
291
|
Engine: engine,
|
|
278
292
|
OutputFormat: 'mp3',
|
package/package.json
CHANGED
package/test/synth.js
CHANGED
|
@@ -162,6 +162,33 @@ test('AWS speech synth tests', async(t) => {
|
|
|
162
162
|
client.quit();
|
|
163
163
|
});
|
|
164
164
|
|
|
165
|
+
test('AWS speech synth tests by RoleArn', async(t) => {
|
|
166
|
+
const fn = require('..');
|
|
167
|
+
const {synthAudio, client} = fn(opts, logger);
|
|
168
|
+
|
|
169
|
+
if (!process.env.AWS_ROLE_ARN || !process.env.AWS_REGION) {
|
|
170
|
+
t.pass('skipping AWS speech synth tests by RoleArn since AWS_ROLE_ARN or AWS_REGION not provided');
|
|
171
|
+
return t.end();
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
let opts = await synthAudio(stats, {
|
|
175
|
+
vendor: 'aws',
|
|
176
|
+
credentials: {
|
|
177
|
+
roleArn: process.env.AWS_ROLE_ARN,
|
|
178
|
+
region: process.env.AWS_REGION,
|
|
179
|
+
},
|
|
180
|
+
language: 'en-US',
|
|
181
|
+
voice: 'Joey',
|
|
182
|
+
text: 'This is a test. This is only a test',
|
|
183
|
+
});
|
|
184
|
+
t.ok(!opts.servedFromCache, `successfully synthesized aws by roleArn audio to ${opts.filePath}`);
|
|
185
|
+
} catch (err) {
|
|
186
|
+
console.error(err);
|
|
187
|
+
t.end(err);
|
|
188
|
+
}
|
|
189
|
+
client.quit();
|
|
190
|
+
});
|
|
191
|
+
|
|
165
192
|
test('Azure speech synth tests', async(t) => {
|
|
166
193
|
const fn = require('..');
|
|
167
194
|
const {synthAudio, client} = fn(opts, logger);
|