@jambonz/speech-utils 1.0.6 → 1.0.7
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/synth-audio.js +75 -1
- package/package.json +1 -1
package/lib/synth-audio.js
CHANGED
|
@@ -80,7 +80,7 @@ async function synthAudio(client, createHash, retrieveHash, logger, stats, { acc
|
|
|
80
80
|
logger = logger || noopLogger;
|
|
81
81
|
|
|
82
82
|
assert.ok(['google', 'aws', 'polly', 'microsoft', 'wellsaid', 'nvidia', 'elevenlabs',
|
|
83
|
-
'whisper', 'deepgram', 'rimelabs', 'cartesia', 'inworld', 'resemble'].includes(vendor) ||
|
|
83
|
+
'whisper', 'deepgram', 'rimelabs', 'cartesia', 'inworld', 'resemble', 'murf'].includes(vendor) ||
|
|
84
84
|
vendor.startsWith('custom'),
|
|
85
85
|
`synthAudio supported vendors are google, aws, microsoft, nvidia and wellsaid ..etc, not ${vendor}`);
|
|
86
86
|
if ('google' === vendor) {
|
|
@@ -127,6 +127,9 @@ async function synthAudio(client, createHash, retrieveHash, logger, stats, { acc
|
|
|
127
127
|
} else if ('cartesia' === vendor) {
|
|
128
128
|
assert.ok(credentials.api_key, 'synthAudio requires api_key when cartesia is used');
|
|
129
129
|
assert.ok(credentials.model_id, 'synthAudio requires model_id when cartesia is used');
|
|
130
|
+
} else if ('murf' === vendor) {
|
|
131
|
+
assert.ok(voice, 'synthAudio requires voice when murf is used');
|
|
132
|
+
assert.ok(credentials.api_key, 'synthAudio requires api_key when murf is used');
|
|
130
133
|
} else if (vendor === 'resemble') {
|
|
131
134
|
assert.ok(voice, 'synthAudio requires voice when resemble is used');
|
|
132
135
|
assert.ok(credentials.api_key, 'synthAudio requires api_key when resemble is used');
|
|
@@ -211,6 +214,11 @@ async function synthAudio(client, createHash, retrieveHash, logger, stats, { acc
|
|
|
211
214
|
credentials, options, stats, language, voice, key, text, renderForCaching, disableTtsStreaming,
|
|
212
215
|
disableTtsCache});
|
|
213
216
|
break;
|
|
217
|
+
case 'murf':
|
|
218
|
+
audioData = await synthMurf(logger, {
|
|
219
|
+
credentials, options, stats, language, voice, key, text, renderForCaching, disableTtsStreaming,
|
|
220
|
+
disableTtsCache});
|
|
221
|
+
break;
|
|
214
222
|
case 'whisper':
|
|
215
223
|
audioData = await synthWhisper(logger, {
|
|
216
224
|
credentials, stats, voice, key, text, instructions, renderForCaching, disableTtsStreaming,
|
|
@@ -969,6 +977,72 @@ const synthRimelabs = async(logger, {
|
|
|
969
977
|
throw err;
|
|
970
978
|
}
|
|
971
979
|
};
|
|
980
|
+
|
|
981
|
+
const synthMurf = async(logger, {
|
|
982
|
+
credentials, options, stats, language, voice, key, text, renderForCaching, disableTtsStreaming, disableTtsCache
|
|
983
|
+
}) => {
|
|
984
|
+
const {api_key, model_id, api_uri, options: credOpts} = credentials;
|
|
985
|
+
const opts = !!options && Object.keys(options).length !== 0 ? options : JSON.parse(credOpts || '{}');
|
|
986
|
+
|
|
987
|
+
/* default to using the streaming interface, unless disabled by env var OR we want just a cache file */
|
|
988
|
+
if (!JAMBONES_DISABLE_TTS_STREAMING && !renderForCaching && !disableTtsStreaming) {
|
|
989
|
+
/* param keys here must match mod_murf_tts's text_param handler */
|
|
990
|
+
let params = '{';
|
|
991
|
+
params += `api_key=${api_key}`;
|
|
992
|
+
params += `,playback_id=${key}`;
|
|
993
|
+
params += ',vendor=murf';
|
|
994
|
+
params += `,voice=${voice}`;
|
|
995
|
+
if (model_id) params += `,model_id=${model_id}`;
|
|
996
|
+
if (language) params += `,language=${language}`;
|
|
997
|
+
if (api_uri) params += `,api_uri=${api_uri}`;
|
|
998
|
+
if (opts.style) params += `,style=${opts.style}`;
|
|
999
|
+
if (opts.rate !== undefined && opts.rate !== null) params += `,rate=${opts.rate}`;
|
|
1000
|
+
if (opts.pitch !== undefined && opts.pitch !== null) params += `,pitch=${opts.pitch}`;
|
|
1001
|
+
if (opts.variation !== undefined && opts.variation !== null) params += `,variation=${opts.variation}`;
|
|
1002
|
+
params += `,write_cache_file=${disableTtsCache ? 0 : 1}`;
|
|
1003
|
+
params += '}';
|
|
1004
|
+
|
|
1005
|
+
return {
|
|
1006
|
+
filePath: `say:${params}${text.replace(/\n/g, ' ').replace(/\r/g, ' ')}`,
|
|
1007
|
+
servedFromCache: false,
|
|
1008
|
+
rtt: 0
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
try {
|
|
1013
|
+
const sampleRate = 8000;
|
|
1014
|
+
/* no Accept header: murf returns 406 if it doesn't match; the response
|
|
1015
|
+
container is selected by the `format` field in the body instead */
|
|
1016
|
+
const post = bent(api_uri || 'https://global.api.murf.ai', 'POST', 'buffer', {
|
|
1017
|
+
'api-key': api_key,
|
|
1018
|
+
'Content-Type': 'application/json'
|
|
1019
|
+
});
|
|
1020
|
+
/* murf REST schema is documented loosely; field names follow the SDK params
|
|
1021
|
+
(voice_id/model/format/sample_rate) plus the websocket voice fields. */
|
|
1022
|
+
const audioContent = await post('/v1/speech/stream', {
|
|
1023
|
+
text,
|
|
1024
|
+
voice_id: voice,
|
|
1025
|
+
...(model_id && {model: model_id}),
|
|
1026
|
+
...(language && {locale: language}),
|
|
1027
|
+
...(opts.style && {style: opts.style}),
|
|
1028
|
+
...(opts.rate !== undefined && opts.rate !== null && {rate: opts.rate}),
|
|
1029
|
+
...(opts.pitch !== undefined && opts.pitch !== null && {pitch: opts.pitch}),
|
|
1030
|
+
...(opts.variation !== undefined && opts.variation !== null && {variation: opts.variation}),
|
|
1031
|
+
format: 'WAV',
|
|
1032
|
+
sample_rate: sampleRate,
|
|
1033
|
+
channel_type: 'MONO'
|
|
1034
|
+
});
|
|
1035
|
+
return {
|
|
1036
|
+
audioContent,
|
|
1037
|
+
extension: 'wav',
|
|
1038
|
+
sampleRate
|
|
1039
|
+
};
|
|
1040
|
+
} catch (err) {
|
|
1041
|
+
logger.info({err}, 'synth murf returned error');
|
|
1042
|
+
stats.increment('tts.count', ['vendor:murf', 'accepted:no']);
|
|
1043
|
+
throw err;
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
972
1046
|
const synthWhisper = async(logger, {credentials, stats, voice, key, text, instructions,
|
|
973
1047
|
renderForCaching, disableTtsStreaming, disableTtsCache}) => {
|
|
974
1048
|
const {api_key, model_id, baseURL, timeout, speed} = credentials;
|