Compare commits

...
4 Commits
Author SHA1 Message Date
Dave Horton a47ef6d7c4 0.1.22 2024-11-04 07:38:23 -05:00
Dave Horton 84089fa528 Merge pull request #98 from jambonz/fix/freshdesk_411
Fix custom tts vendor cached file can not be played
2024-11-04 07:37:49 -05:00
Quan HL 72be44eea2 adding testcase 2024-11-04 15:53:11 +07:00
Quan HL 05d6c4b32d fixed custom vendor cache audio stores file extension 2024-11-04 15:43:44 +07:00
4 changed files with 44 additions and 7 deletions
+25 -4
View File
@@ -170,6 +170,8 @@ async function synthAudio(client, createHash, retrieveHash, logger, stats, { acc
renderForCaching renderForCaching
}); });
let filePath; let filePath;
// used only for custom vendor
let fileExtension;
filePath = makeFilePath({vendor, voice, key, salt, renderForCaching}); filePath = makeFilePath({vendor, voice, key, salt, renderForCaching});
debug(`synth key is ${key}`); debug(`synth key is ${key}`);
let cached; let cached;
@@ -201,7 +203,17 @@ async function synthAudio(client, createHash, retrieveHash, logger, stats, { acc
debug('result WAS found in cache'); debug('result WAS found in cache');
servedFromCache = true; servedFromCache = true;
stats.increment('tts.cache.requests', ['found:yes']); stats.increment('tts.cache.requests', ['found:yes']);
audioBuffer = Buffer.from(cached, 'base64'); if (vendor.startsWith('custom')) {
// custom vendors support multiple mime types such as: mp3, wav, r8, r16 ...etc,
// mime type/file extension is available when http response has header Content-type.
// In cache, file extension is store together with audiBuffer in a json.
// Normal cache audio will be base64 string
const payload = JSON.parse(cached);
filePath = filePath.replace(/\.[^\.]*$/g, payload.fileExtension);
audioBuffer = Buffer.from(payload.audioBuffer, 'base64');
} else {
audioBuffer = Buffer.from(cached, 'base64');
}
client.expire(key, EXPIRES).catch((err) => logger.info(err, 'Error setting expires')); client.expire(key, EXPIRES).catch((err) => logger.info(err, 'Error setting expires'));
} }
if (!cached) { if (!cached) {
@@ -268,7 +280,7 @@ async function synthAudio(client, createHash, retrieveHash, logger, stats, { acc
renderForCaching, disableTtsStreaming}); renderForCaching, disableTtsStreaming});
break; break;
case vendor.startsWith('custom') ? vendor : 'cant_match_value': case vendor.startsWith('custom') ? vendor : 'cant_match_value':
({ audioBuffer, filePath } = await synthCustomVendor(logger, ({ audioBuffer, filePath, fileExtension } = await synthCustomVendor(logger,
{credentials, stats, language, voice, text, filePath})); {credentials, stats, language, voice, text, filePath}));
break; break;
default: default:
@@ -282,7 +294,14 @@ async function synthAudio(client, createHash, retrieveHash, logger, stats, { acc
debug(`tts rtt time for ${text.length} chars on ${vendorLabel}: ${rtt}`); debug(`tts rtt time for ${text.length} chars on ${vendorLabel}: ${rtt}`);
logger.info(`tts rtt time for ${text.length} chars on ${vendorLabel}: ${rtt}`); logger.info(`tts rtt time for ${text.length} chars on ${vendorLabel}: ${rtt}`);
client.setex(key, EXPIRES, audioBuffer.toString('base64')) const base64Audio = audioBuffer.toString('base64');
const cacheContent = vendor.startsWith('custom') ?
JSON.stringify({
audioBuffer: base64Audio,
fileExtension
}) : base64Audio;
client.setex(key, EXPIRES, cacheContent)
.catch((err) => logger.error(err, `error calling setex on key ${key}`)); .catch((err) => logger.error(err, `error calling setex on key ${key}`));
} }
@@ -729,9 +748,11 @@ const synthCustomVendor = async(logger, {credentials, stats, language, voice, te
const regex = /\.[^\.]*$/g; const regex = /\.[^\.]*$/g;
const mime = response.headers['content-type']; const mime = response.headers['content-type'];
const buffer = await response.arrayBuffer(); const buffer = await response.arrayBuffer();
const fileExtension = getFileExtFromMime(mime);
return { return {
audioBuffer: buffer, audioBuffer: buffer,
filePath: filePath.replace(regex, getFileExtFromMime(mime)) filePath: filePath.replace(regex, fileExtension),
fileExtension
}; };
} catch (err) { } catch (err) {
logger.info({err}, `Vendor ${vendor} returned error`); logger.info({err}, `Vendor ${vendor} returned error`);
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "@jambonz/speech-utils", "name": "@jambonz/speech-utils",
"version": "0.1.21", "version": "0.1.22",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@jambonz/speech-utils", "name": "@jambonz/speech-utils",
"version": "0.1.21", "version": "0.1.22",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@aws-sdk/client-polly": "^3.496.0", "@aws-sdk/client-polly": "^3.496.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@jambonz/speech-utils", "name": "@jambonz/speech-utils",
"version": "0.1.21", "version": "0.1.22",
"description": "TTS-related speech utilities for jambonz", "description": "TTS-related speech utilities for jambonz",
"main": "index.js", "main": "index.js",
"author": "Dave Horton", "author": "Dave Horton",
+16
View File
@@ -554,6 +554,7 @@ test('Custom Vendor speech synth tests', async(t) => {
text: 'This is a test. This is only a test', text: 'This is a test. This is only a test',
}); });
t.ok(!opts.servedFromCache, `successfully synthesized custom vendor audio to ${opts.filePath}`); t.ok(!opts.servedFromCache, `successfully synthesized custom vendor audio to ${opts.filePath}`);
t.ok(opts.filePath.endsWith('wav'), 'audio is cached as wav file');
let obj = await getJSON(`http://127.0.0.1:3100/lastRequest/somethingnew`); let obj = await getJSON(`http://127.0.0.1:3100/lastRequest/somethingnew`);
t.ok(obj.headers.Authorization == 'Bearer some_jwt_token', 'Custom Vendor Authentication Header is correct'); t.ok(obj.headers.Authorization == 'Bearer some_jwt_token', 'Custom Vendor Authentication Header is correct');
t.ok(obj.body.language == 'en-US', 'Custom Vendor Language is correct'); t.ok(obj.body.language == 'en-US', 'Custom Vendor Language is correct');
@@ -561,6 +562,21 @@ test('Custom Vendor speech synth tests', async(t) => {
t.ok(obj.body.type == 'text', 'Custom Vendor type is correct'); t.ok(obj.body.type == 'text', 'Custom Vendor type is correct');
t.ok(obj.body.text == 'This is a test. This is only a test', 'Custom Vendor text is correct'); t.ok(obj.body.text == 'This is a test. This is only a test', 'Custom Vendor text is correct');
// Checking if cache is stored with wav format
opts = await synthAudio(stats, {
vendor: 'custom:somethingnew',
credentials: {
use_for_tts: 1,
custom_tts_url: "http://127.0.0.1:3100/somethingnew",
auth_token: 'some_jwt_token'
},
language: 'en-US',
voice: 'English-US.Female-1',
text: 'This is a test. This is only a test',
});
t.ok(opts.servedFromCache, `successfully get custom vendor cached audio to ${opts.filePath}`);
t.ok(opts.filePath.endsWith('wav'), 'audio is cached as wav file');
opts = await synthAudio(stats, { opts = await synthAudio(stats, {
vendor: 'custom:somethingnew2', vendor: 'custom:somethingnew2',
credentials: { credentials: {