@jambonz/speech-utils 1.0.8 → 1.0.9

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.
@@ -80,7 +80,8 @@ 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', 'murf', 'xai'].includes(vendor) ||
83
+ 'whisper', 'deepgram', 'deepgramflux', 'rimelabs', 'cartesia', 'inworld', 'resemble', 'murf', 'xai']
84
+ .includes(vendor) ||
84
85
  vendor.startsWith('custom'),
85
86
  `synthAudio supported vendors are google, aws, microsoft, nvidia and wellsaid ..etc, not ${vendor}`);
86
87
  if ('google' === vendor) {
@@ -124,6 +125,11 @@ async function synthAudio(client, createHash, retrieveHash, logger, stats, { acc
124
125
  if (!credentials.deepgram_tts_uri) {
125
126
  assert.ok(credentials.api_key, 'synthAudio requires api_key when deepgram is used');
126
127
  }
128
+ } else if ('deepgramflux' === vendor) {
129
+ // Deepgram Flux TTS (/v2/speak); the flux model rides on `voice`/`model`
130
+ if (!credentials.deepgram_tts_uri) {
131
+ assert.ok(credentials.api_key, 'synthAudio requires api_key when deepgramflux is used');
132
+ }
127
133
  } else if ('xai' === vendor) {
128
134
  assert.ok(credentials.api_key, 'synthAudio requires api_key when xai is used');
129
135
  } else if ('cartesia' === vendor) {
@@ -230,6 +236,10 @@ async function synthAudio(client, createHash, retrieveHash, logger, stats, { acc
230
236
  audioData = await synthDeepgram(logger, {credentials, stats, model, key, text,
231
237
  renderForCaching, disableTtsStreaming, disableTtsCache});
232
238
  break;
239
+ case 'deepgramflux':
240
+ audioData = await synthDeepgramFlux(logger, {credentials, stats, model: model || voice, key, text,
241
+ renderForCaching, disableTtsStreaming, disableTtsCache});
242
+ break;
233
243
  case 'xai':
234
244
  audioData = await synthXai(logger, {
235
245
  credentials, options, stats, language, voice, key, text, renderForCaching, disableTtsStreaming,
@@ -1140,6 +1150,50 @@ const synthDeepgram = async(logger, {credentials, stats, model, key, text, rende
1140
1150
  }
1141
1151
  };
1142
1152
 
1153
+ // Deepgram Flux TTS — the conversation-native model served from /v2/speak.
1154
+ // Streaming rides the mediajam deepgramflux dialect via a say: filePath; the
1155
+ // batch/cache path POSTs to /v2/speak (mp3 is batch-only for Flux).
1156
+ const synthDeepgramFlux = async(logger, {credentials, stats, model, key, text, renderForCaching,
1157
+ disableTtsStreaming, disableTtsCache}) => {
1158
+ const {api_key, deepgram_tts_uri} = credentials;
1159
+ if (!JAMBONES_DISABLE_TTS_STREAMING && !renderForCaching && !disableTtsStreaming) {
1160
+ let params = '{';
1161
+ params += `api_key=${api_key}`;
1162
+ params += `,playback_id=${key}`;
1163
+ params += ',vendor=deepgramflux';
1164
+ params += `,voice=${model}`;
1165
+ params += `,write_cache_file=${disableTtsCache ? 0 : 1}`;
1166
+ if (deepgram_tts_uri) params += `,endpoint=${deepgram_tts_uri}`;
1167
+ params += '}';
1168
+
1169
+ return {
1170
+ filePath: `say:${params}${text.replace(/\n/g, ' ')}`,
1171
+ servedFromCache: false,
1172
+ rtt: 0
1173
+ };
1174
+ }
1175
+ try {
1176
+ const post = bent(deepgram_tts_uri || 'https://api.deepgram.com', 'POST', 'buffer', {
1177
+ // on-premise deepgram does not require to have api_key
1178
+ ...(api_key && {'Authorization': `Token ${api_key}`}),
1179
+ 'Accept': 'audio/mpeg',
1180
+ 'Content-Type': 'application/json'
1181
+ });
1182
+ const audioContent = await post(`/v2/speak?model=${model}`, {
1183
+ text
1184
+ });
1185
+ return {
1186
+ audioContent,
1187
+ extension: 'mp3',
1188
+ sampleRate: 8000
1189
+ };
1190
+ } catch (err) {
1191
+ logger.info({err}, 'synth Deepgram Flux returned error');
1192
+ stats.increment('tts.count', ['vendor:deepgramflux', 'accepted:no']);
1193
+ throw err;
1194
+ }
1195
+ };
1196
+
1143
1197
  const synthXai = async(logger, {
1144
1198
  credentials, options, stats, language, voice, key, text, renderForCaching, disableTtsStreaming, disableTtsCache
1145
1199
  }) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jambonz/speech-utils",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "TTS-related speech utilities for jambonz",
5
5
  "main": "index.js",
6
6
  "author": "Dave Horton",
package/test/synth.js CHANGED
@@ -1118,6 +1118,34 @@ test('Deepgram speech synth tests', async(t) => {
1118
1118
  client.quit();
1119
1119
  })
1120
1120
 
1121
+ test('Deepgram Flux speech synth tests', async(t) => {
1122
+ const fn = require('..');
1123
+ const {synthAudio, client} = fn(opts, logger);
1124
+
1125
+ if (!process.env.DEEPGRAM_API_KEY) {
1126
+ t.pass('skipping Deepgram Flux speech synth tests since DEEPGRAM_API_KEY');
1127
+ return t.end();
1128
+ }
1129
+ const text = 'Hi there and welcome to jambones!';
1130
+ try {
1131
+ const opts = await synthAudio(stats, {
1132
+ vendor: 'deepgramflux',
1133
+ credentials: {
1134
+ api_key: process.env.DEEPGRAM_API_KEY
1135
+ },
1136
+ model: process.env.DEEPGRAM_FLUX_MODEL || 'flux-alexis-en',
1137
+ text,
1138
+ renderForCaching: true
1139
+ });
1140
+ t.ok(!opts.servedFromCache, `successfully synthesized deepgramflux audio to ${opts.filePath}`);
1141
+
1142
+ } catch (err) {
1143
+ console.error(JSON.stringify(err));
1144
+ t.end(err);
1145
+ }
1146
+ client.quit();
1147
+ });
1148
+
1121
1149
  test('xai speech synth tests', async(t) => {
1122
1150
  const fn = require('..');
1123
1151
  const {synthAudio, client} = fn(opts, logger);