speechflow 1.6.5 → 1.6.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/CHANGELOG.md +19 -0
- package/README.md +23 -0
- package/etc/stx.conf +5 -0
- package/package.json +4 -4
- package/speechflow-cli/dst/speechflow-main-cli.js +2 -2
- package/speechflow-cli/dst/speechflow-main-cli.js.map +1 -1
- package/speechflow-cli/dst/speechflow-main-graph.js +4 -3
- package/speechflow-cli/dst/speechflow-main-graph.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.js +12 -11
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-expander.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-expander.js +12 -11
- package/speechflow-cli/dst/speechflow-node-a2a-expander.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js +2 -8
- package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-filler.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-filler.js +18 -16
- package/speechflow-cli/dst/speechflow-node-a2a-filler.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gain.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gain.js +8 -8
- package/speechflow-cli/dst/speechflow-node-a2a-gain.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gender.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gender.js +38 -34
- package/speechflow-cli/dst/speechflow-node-a2a-gender.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-meter.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-meter.js +11 -11
- package/speechflow-cli/dst/speechflow-node-a2a-meter.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-mute.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-mute.js +44 -10
- package/speechflow-cli/dst/speechflow-node-a2a-mute.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-pitch.d.ts +13 -0
- package/speechflow-cli/dst/speechflow-node-a2a-pitch.js +213 -0
- package/speechflow-cli/dst/speechflow-node-a2a-pitch.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js +12 -11
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-speex.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-speex.js +13 -12
- package/speechflow-cli/dst/speechflow-node-a2a-speex.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-vad.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-vad.js +24 -23
- package/speechflow-cli/dst/speechflow-node-a2a-vad.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-wav.js +35 -7
- package/speechflow-cli/dst/speechflow-node-a2a-wav.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-amazon.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-amazon.js +16 -16
- package/speechflow-cli/dst/speechflow-node-a2t-amazon.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js +16 -16
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-openai.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-openai.js +15 -15
- package/speechflow-cli/dst/speechflow-node-a2t-openai.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-amazon.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-amazon.js +9 -9
- package/speechflow-cli/dst/speechflow-node-t2a-amazon.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js +13 -12
- package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js +4 -4
- package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-amazon.js +3 -3
- package/speechflow-cli/dst/speechflow-node-t2t-amazon.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-deepl.js +2 -2
- package/speechflow-cli/dst/speechflow-node-t2t-deepl.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-format.js +36 -2
- package/speechflow-cli/dst/speechflow-node-t2t-format.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-google.js +2 -2
- package/speechflow-cli/dst/speechflow-node-t2t-google.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-modify.js +5 -5
- package/speechflow-cli/dst/speechflow-node-t2t-modify.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-ollama.js +2 -2
- package/speechflow-cli/dst/speechflow-node-t2t-ollama.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-openai.js +2 -2
- package/speechflow-cli/dst/speechflow-node-t2t-openai.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-sentence.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-sentence.js +13 -13
- package/speechflow-cli/dst/speechflow-node-t2t-sentence.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js +2 -2
- package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-transformers.js +2 -2
- package/speechflow-cli/dst/speechflow-node-t2t-transformers.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-x2x-filter.js +2 -2
- package/speechflow-cli/dst/speechflow-node-x2x-filter.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-x2x-trace.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-x2x-trace.js +42 -8
- package/speechflow-cli/dst/speechflow-node-x2x-trace.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-device.js +3 -2
- package/speechflow-cli/dst/speechflow-node-xio-device.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-file.js +19 -18
- package/speechflow-cli/dst/speechflow-node-xio-file.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-mqtt.js +13 -13
- package/speechflow-cli/dst/speechflow-node-xio-mqtt.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-websocket.js +8 -8
- package/speechflow-cli/dst/speechflow-node-xio-websocket.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node.js +7 -7
- package/speechflow-cli/dst/speechflow-node.js.map +1 -1
- package/speechflow-cli/dst/speechflow-util-audio.js +2 -2
- package/speechflow-cli/dst/speechflow-util-audio.js.map +1 -1
- package/speechflow-cli/dst/speechflow-util-stream.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-util-stream.js +22 -2
- package/speechflow-cli/dst/speechflow-util-stream.js.map +1 -1
- package/speechflow-cli/etc/oxlint.jsonc +2 -1
- package/speechflow-cli/etc/tsconfig.json +1 -0
- package/speechflow-cli/package.json +21 -21
- package/speechflow-cli/src/speechflow-main-cli.ts +2 -2
- package/speechflow-cli/src/speechflow-main-graph.ts +4 -3
- package/speechflow-cli/src/speechflow-node-a2a-compressor.ts +13 -12
- package/speechflow-cli/src/speechflow-node-a2a-expander.ts +13 -12
- package/speechflow-cli/src/speechflow-node-a2a-ffmpeg.ts +2 -8
- package/speechflow-cli/src/speechflow-node-a2a-filler.ts +19 -17
- package/speechflow-cli/src/speechflow-node-a2a-gain.ts +8 -8
- package/speechflow-cli/src/speechflow-node-a2a-gender.ts +42 -36
- package/speechflow-cli/src/speechflow-node-a2a-meter.ts +11 -11
- package/speechflow-cli/src/speechflow-node-a2a-mute.ts +11 -10
- package/speechflow-cli/src/speechflow-node-a2a-pitch.ts +221 -0
- package/speechflow-cli/src/speechflow-node-a2a-rnnoise.ts +13 -12
- package/speechflow-cli/src/speechflow-node-a2a-speex.ts +14 -13
- package/speechflow-cli/src/speechflow-node-a2a-vad.ts +24 -23
- package/speechflow-cli/src/speechflow-node-a2a-wav.ts +2 -7
- package/speechflow-cli/src/speechflow-node-a2t-amazon.ts +16 -16
- package/speechflow-cli/src/speechflow-node-a2t-deepgram.ts +16 -16
- package/speechflow-cli/src/speechflow-node-a2t-openai.ts +15 -15
- package/speechflow-cli/src/speechflow-node-t2a-amazon.ts +9 -9
- package/speechflow-cli/src/speechflow-node-t2a-elevenlabs.ts +13 -12
- package/speechflow-cli/src/speechflow-node-t2a-kokoro.ts +4 -4
- package/speechflow-cli/src/speechflow-node-t2t-amazon.ts +3 -3
- package/speechflow-cli/src/speechflow-node-t2t-deepl.ts +2 -2
- package/speechflow-cli/src/speechflow-node-t2t-format.ts +3 -2
- package/speechflow-cli/src/speechflow-node-t2t-google.ts +2 -2
- package/speechflow-cli/src/speechflow-node-t2t-modify.ts +6 -6
- package/speechflow-cli/src/speechflow-node-t2t-ollama.ts +2 -2
- package/speechflow-cli/src/speechflow-node-t2t-openai.ts +2 -2
- package/speechflow-cli/src/speechflow-node-t2t-sentence.ts +13 -13
- package/speechflow-cli/src/speechflow-node-t2t-subtitle.ts +2 -2
- package/speechflow-cli/src/speechflow-node-t2t-transformers.ts +2 -2
- package/speechflow-cli/src/speechflow-node-x2x-filter.ts +2 -2
- package/speechflow-cli/src/speechflow-node-x2x-trace.ts +10 -9
- package/speechflow-cli/src/speechflow-node-xio-device.ts +4 -3
- package/speechflow-cli/src/speechflow-node-xio-file.ts +20 -19
- package/speechflow-cli/src/speechflow-node-xio-mqtt.ts +14 -14
- package/speechflow-cli/src/speechflow-node-xio-websocket.ts +10 -10
- package/speechflow-cli/src/speechflow-node.ts +7 -7
- package/speechflow-cli/src/speechflow-util-audio.ts +2 -2
- package/speechflow-cli/src/speechflow-util-stream.ts +30 -5
- package/speechflow-ui-db/dst/app-font-fa-brands-400.woff2 +0 -0
- package/speechflow-ui-db/dst/app-font-fa-regular-400.woff2 +0 -0
- package/speechflow-ui-db/dst/app-font-fa-solid-900.woff2 +0 -0
- package/speechflow-ui-db/dst/app-font-fa-v4compatibility.woff2 +0 -0
- package/speechflow-ui-db/dst/index.css +1 -1
- package/speechflow-ui-db/dst/index.js +28 -25
- package/speechflow-ui-db/package.json +14 -14
- package/speechflow-ui-st/dst/app-font-fa-brands-400.woff2 +0 -0
- package/speechflow-ui-st/dst/app-font-fa-regular-400.woff2 +0 -0
- package/speechflow-ui-st/dst/app-font-fa-solid-900.woff2 +0 -0
- package/speechflow-ui-st/dst/app-font-fa-v4compatibility.woff2 +0 -0
- package/speechflow-ui-st/dst/index.css +1 -1
- package/speechflow-ui-st/dst/index.js +137 -51
- package/speechflow-ui-st/package.json +15 -15
|
@@ -27,7 +27,7 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
|
|
|
27
27
|
private ws: ws.WebSocket | null = null
|
|
28
28
|
private queue: util.SingleQueue<SpeechFlowChunk | null> | null = null
|
|
29
29
|
private resampler: SpeexResampler | null = null
|
|
30
|
-
private
|
|
30
|
+
private closing = false
|
|
31
31
|
private connectionTimeout: ReturnType<typeof setTimeout> | null = null
|
|
32
32
|
|
|
33
33
|
/* construct node */
|
|
@@ -60,7 +60,7 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
|
|
|
60
60
|
throw new Error("OpenAI transcribe node currently supports PCM-S16LE audio only")
|
|
61
61
|
|
|
62
62
|
/* clear destruction flag */
|
|
63
|
-
this.
|
|
63
|
+
this.closing = false
|
|
64
64
|
|
|
65
65
|
/* create queue for results */
|
|
66
66
|
this.queue = new util.SingleQueue<SpeechFlowChunk | null>()
|
|
@@ -172,7 +172,7 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
|
|
|
172
172
|
const start = DateTime.now().diff(this.timeOpen!) // FIXME: OpenAI does not provide timestamps
|
|
173
173
|
const end = start // FIXME: OpenAI does not provide timestamps
|
|
174
174
|
const metas = metastore.fetch(start, end)
|
|
175
|
-
const meta = metas.reduce((prev: Map<string, any>, curr: Map<string, any>) => {
|
|
175
|
+
const meta = metas.toReversed().reduce((prev: Map<string, any>, curr: Map<string, any>) => {
|
|
176
176
|
curr.forEach((val, key) => { prev.set(key, val) })
|
|
177
177
|
return prev
|
|
178
178
|
}, new Map<string, any>())
|
|
@@ -187,7 +187,7 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
|
|
|
187
187
|
const start = DateTime.now().diff(this.timeOpen!) // FIXME: OpenAI does not provide timestamps
|
|
188
188
|
const end = start // FIXME: OpenAI does not provide timestamps
|
|
189
189
|
const metas = metastore.fetch(start, end)
|
|
190
|
-
const meta = metas.reduce((prev: Map<string, any>, curr: Map<string, any>) => {
|
|
190
|
+
const meta = metas.toReversed().reduce((prev: Map<string, any>, curr: Map<string, any>) => {
|
|
191
191
|
curr.forEach((val, key) => { prev.set(key, val) })
|
|
192
192
|
return prev
|
|
193
193
|
}, new Map<string, any>())
|
|
@@ -226,7 +226,7 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
|
|
|
226
226
|
decodeStrings: false,
|
|
227
227
|
highWaterMark: 1,
|
|
228
228
|
write (chunk: SpeechFlowChunk, encoding, callback) {
|
|
229
|
-
if (self.
|
|
229
|
+
if (self.closing || self.ws === null) {
|
|
230
230
|
callback(new Error("stream already destroyed"))
|
|
231
231
|
return
|
|
232
232
|
}
|
|
@@ -248,7 +248,7 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
|
|
|
248
248
|
})
|
|
249
249
|
}
|
|
250
250
|
catch (error) {
|
|
251
|
-
callback(error
|
|
251
|
+
callback(util.ensureError(error, "failed to send to OpenAI transcribe"))
|
|
252
252
|
return
|
|
253
253
|
}
|
|
254
254
|
}
|
|
@@ -256,12 +256,12 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
|
|
|
256
256
|
}
|
|
257
257
|
},
|
|
258
258
|
read (size) {
|
|
259
|
-
if (self.
|
|
259
|
+
if (self.closing || self.queue === null) {
|
|
260
260
|
this.push(null)
|
|
261
261
|
return
|
|
262
262
|
}
|
|
263
263
|
self.queue.read().then((chunk) => {
|
|
264
|
-
if (self.
|
|
264
|
+
if (self.closing || self.queue === null) {
|
|
265
265
|
this.push(null)
|
|
266
266
|
return
|
|
267
267
|
}
|
|
@@ -274,12 +274,12 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
|
|
|
274
274
|
this.push(chunk)
|
|
275
275
|
}
|
|
276
276
|
}).catch((error: unknown) => {
|
|
277
|
-
if (!self.
|
|
277
|
+
if (!self.closing && self.queue !== null)
|
|
278
278
|
self.log("error", `queue read error: ${util.ensureError(error).message}`)
|
|
279
279
|
})
|
|
280
280
|
},
|
|
281
281
|
final (callback) {
|
|
282
|
-
if (self.
|
|
282
|
+
if (self.closing || self.ws === null) {
|
|
283
283
|
callback()
|
|
284
284
|
return
|
|
285
285
|
}
|
|
@@ -291,7 +291,7 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
|
|
|
291
291
|
}
|
|
292
292
|
catch (error) {
|
|
293
293
|
self.log("warning", `error closing OpenAI connection: ${error}`)
|
|
294
|
-
callback(error
|
|
294
|
+
callback(util.ensureError(error, "failed to close OpenAI connection"))
|
|
295
295
|
}
|
|
296
296
|
}
|
|
297
297
|
})
|
|
@@ -299,8 +299,8 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
|
|
|
299
299
|
|
|
300
300
|
/* close node */
|
|
301
301
|
async close () {
|
|
302
|
-
/* indicate
|
|
303
|
-
this.
|
|
302
|
+
/* indicate closing first to stop all async operations */
|
|
303
|
+
this.closing = true
|
|
304
304
|
|
|
305
305
|
/* clear connection timeout */
|
|
306
306
|
if (this.connectionTimeout !== null) {
|
|
@@ -322,9 +322,9 @@ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
|
|
|
322
322
|
if (this.openai !== null)
|
|
323
323
|
this.openai = null
|
|
324
324
|
|
|
325
|
-
/*
|
|
325
|
+
/* shutdown stream */
|
|
326
326
|
if (this.stream !== null) {
|
|
327
|
-
this.stream
|
|
327
|
+
await util.destroyStream(this.stream)
|
|
328
328
|
this.stream = null
|
|
329
329
|
}
|
|
330
330
|
}
|
|
@@ -26,7 +26,7 @@ export default class SpeechFlowNodeT2AAmazon extends SpeechFlowNode {
|
|
|
26
26
|
|
|
27
27
|
/* internal state */
|
|
28
28
|
private client: PollyClient | null = null
|
|
29
|
-
private
|
|
29
|
+
private closing = false
|
|
30
30
|
private resampler: SpeexResampler | null = null
|
|
31
31
|
|
|
32
32
|
/* construct node */
|
|
@@ -61,7 +61,7 @@ export default class SpeechFlowNodeT2AAmazon extends SpeechFlowNode {
|
|
|
61
61
|
/* open node */
|
|
62
62
|
async open () {
|
|
63
63
|
/* clear destruction flag */
|
|
64
|
-
this.
|
|
64
|
+
this.closing = false
|
|
65
65
|
|
|
66
66
|
/* establish AWS Polly connection */
|
|
67
67
|
this.client = new PollyClient({
|
|
@@ -123,7 +123,7 @@ export default class SpeechFlowNodeT2AAmazon extends SpeechFlowNode {
|
|
|
123
123
|
decodeStrings: false,
|
|
124
124
|
highWaterMark: 1,
|
|
125
125
|
transform (chunk: SpeechFlowChunk, encoding, callback) {
|
|
126
|
-
if (self.
|
|
126
|
+
if (self.closing) {
|
|
127
127
|
callback(new Error("stream already destroyed"))
|
|
128
128
|
return
|
|
129
129
|
}
|
|
@@ -132,7 +132,7 @@ export default class SpeechFlowNodeT2AAmazon extends SpeechFlowNode {
|
|
|
132
132
|
else if (chunk.payload.length > 0) {
|
|
133
133
|
self.log("debug", `send data (${chunk.payload.length} bytes): "${chunk.payload}"`)
|
|
134
134
|
textToSpeech(chunk.payload as string).then((buffer) => {
|
|
135
|
-
if (self.
|
|
135
|
+
if (self.closing)
|
|
136
136
|
throw new Error("stream destroyed during processing")
|
|
137
137
|
const chunkNew = chunk.clone()
|
|
138
138
|
chunkNew.type = "audio"
|
|
@@ -147,7 +147,7 @@ export default class SpeechFlowNodeT2AAmazon extends SpeechFlowNode {
|
|
|
147
147
|
callback()
|
|
148
148
|
},
|
|
149
149
|
final (callback) {
|
|
150
|
-
if (self.
|
|
150
|
+
if (self.closing) {
|
|
151
151
|
callback()
|
|
152
152
|
return
|
|
153
153
|
}
|
|
@@ -159,8 +159,8 @@ export default class SpeechFlowNodeT2AAmazon extends SpeechFlowNode {
|
|
|
159
159
|
|
|
160
160
|
/* close node */
|
|
161
161
|
async close () {
|
|
162
|
-
/* indicate
|
|
163
|
-
this.
|
|
162
|
+
/* indicate closing */
|
|
163
|
+
this.closing = true
|
|
164
164
|
|
|
165
165
|
/* destroy resampler */
|
|
166
166
|
if (this.resampler !== null)
|
|
@@ -171,9 +171,9 @@ export default class SpeechFlowNodeT2AAmazon extends SpeechFlowNode {
|
|
|
171
171
|
this.client.destroy()
|
|
172
172
|
this.client = null
|
|
173
173
|
}
|
|
174
|
-
/*
|
|
174
|
+
/* shutdown stream */
|
|
175
175
|
if (this.stream !== null) {
|
|
176
|
-
this.stream
|
|
176
|
+
await util.destroyStream(this.stream)
|
|
177
177
|
this.stream = null
|
|
178
178
|
}
|
|
179
179
|
}
|
|
@@ -14,6 +14,7 @@ import SpeexResampler from "speex-resampler"
|
|
|
14
14
|
|
|
15
15
|
/* internal dependencies */
|
|
16
16
|
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
17
|
+
import * as util from "./speechflow-util"
|
|
17
18
|
|
|
18
19
|
/* SpeechFlow node for Elevenlabs text-to-speech conversion */
|
|
19
20
|
export default class SpeechFlowNodeT2AElevenlabs extends SpeechFlowNode {
|
|
@@ -22,7 +23,7 @@ export default class SpeechFlowNodeT2AElevenlabs extends SpeechFlowNode {
|
|
|
22
23
|
|
|
23
24
|
/* internal state */
|
|
24
25
|
private elevenlabs: ElevenLabs.ElevenLabsClient | null = null
|
|
25
|
-
private
|
|
26
|
+
private closing = false
|
|
26
27
|
private resampler: SpeexResampler | null = null
|
|
27
28
|
|
|
28
29
|
/* construct node */
|
|
@@ -67,7 +68,7 @@ export default class SpeechFlowNodeT2AElevenlabs extends SpeechFlowNode {
|
|
|
67
68
|
/* open node */
|
|
68
69
|
async open () {
|
|
69
70
|
/* clear destruction flag */
|
|
70
|
-
this.
|
|
71
|
+
this.closing = false
|
|
71
72
|
|
|
72
73
|
/* establish ElevenLabs API connection */
|
|
73
74
|
this.elevenlabs = new ElevenLabs.ElevenLabsClient({
|
|
@@ -116,7 +117,7 @@ export default class SpeechFlowNodeT2AElevenlabs extends SpeechFlowNode {
|
|
|
116
117
|
modelId: model,
|
|
117
118
|
languageCode: this.params.language,
|
|
118
119
|
outputFormat: `pcm_${maxSampleRate}` as ElevenLabs.ElevenLabs.OutputFormat,
|
|
119
|
-
seed: 815, /*
|
|
120
|
+
seed: 815, /* arbitrary, but fixated by us */
|
|
120
121
|
voiceSettings: {
|
|
121
122
|
speed: this.params.speed,
|
|
122
123
|
stability: this.params.stability,
|
|
@@ -140,7 +141,7 @@ export default class SpeechFlowNodeT2AElevenlabs extends SpeechFlowNode {
|
|
|
140
141
|
decodeStrings: false,
|
|
141
142
|
highWaterMark: 1,
|
|
142
143
|
transform (chunk: SpeechFlowChunk, encoding, callback) {
|
|
143
|
-
if (self.
|
|
144
|
+
if (self.closing)
|
|
144
145
|
callback(new Error("stream already destroyed"))
|
|
145
146
|
else if (Buffer.isBuffer(chunk.payload))
|
|
146
147
|
callback(new Error("invalid chunk payload type"))
|
|
@@ -157,14 +158,14 @@ export default class SpeechFlowNodeT2AElevenlabs extends SpeechFlowNode {
|
|
|
157
158
|
}
|
|
158
159
|
}
|
|
159
160
|
try {
|
|
160
|
-
if (self.
|
|
161
|
+
if (self.closing) {
|
|
161
162
|
clearProcessTimeout()
|
|
162
163
|
callback(new Error("stream destroyed during processing"))
|
|
163
164
|
return
|
|
164
165
|
}
|
|
165
166
|
const stream = await speechStream(chunk.payload as string)
|
|
166
167
|
const buffer = await getStreamAsBuffer(stream)
|
|
167
|
-
if (self.
|
|
168
|
+
if (self.closing) {
|
|
168
169
|
clearProcessTimeout()
|
|
169
170
|
callback(new Error("stream destroyed during processing"))
|
|
170
171
|
return
|
|
@@ -180,13 +181,13 @@ export default class SpeechFlowNodeT2AElevenlabs extends SpeechFlowNode {
|
|
|
180
181
|
}
|
|
181
182
|
catch (error) {
|
|
182
183
|
clearProcessTimeout()
|
|
183
|
-
callback(error
|
|
184
|
+
callback(util.ensureError(error, "ElevenLabs processing failed"))
|
|
184
185
|
}
|
|
185
186
|
})()
|
|
186
187
|
}
|
|
187
188
|
},
|
|
188
189
|
final (callback) {
|
|
189
|
-
if (self.
|
|
190
|
+
if (self.closing) {
|
|
190
191
|
callback()
|
|
191
192
|
return
|
|
192
193
|
}
|
|
@@ -198,12 +199,12 @@ export default class SpeechFlowNodeT2AElevenlabs extends SpeechFlowNode {
|
|
|
198
199
|
|
|
199
200
|
/* close node */
|
|
200
201
|
async close () {
|
|
201
|
-
/* indicate
|
|
202
|
-
this.
|
|
202
|
+
/* indicate closing */
|
|
203
|
+
this.closing = true
|
|
203
204
|
|
|
204
|
-
/*
|
|
205
|
+
/* shutdown stream */
|
|
205
206
|
if (this.stream !== null) {
|
|
206
|
-
this.stream
|
|
207
|
+
await util.destroyStream(this.stream)
|
|
207
208
|
this.stream = null
|
|
208
209
|
}
|
|
209
210
|
|
|
@@ -119,7 +119,7 @@ export default class SpeechFlowNodeT2AKokoro extends SpeechFlowNode {
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
/* create transform stream and connect it to the Kokoro API */
|
|
122
|
-
const
|
|
122
|
+
const self = this
|
|
123
123
|
this.stream = new Stream.Transform({
|
|
124
124
|
writableObjectMode: true,
|
|
125
125
|
readableObjectMode: true,
|
|
@@ -130,7 +130,7 @@ export default class SpeechFlowNodeT2AKokoro extends SpeechFlowNode {
|
|
|
130
130
|
callback(new Error("invalid chunk payload type"))
|
|
131
131
|
else {
|
|
132
132
|
text2speech(chunk.payload).then((buffer) => {
|
|
133
|
-
log("info", `Kokoro: received audio (buffer length: ${buffer.byteLength})`)
|
|
133
|
+
self.log("info", `Kokoro: received audio (buffer length: ${buffer.byteLength})`)
|
|
134
134
|
chunk = chunk.clone()
|
|
135
135
|
chunk.type = "audio"
|
|
136
136
|
chunk.payload = buffer
|
|
@@ -150,9 +150,9 @@ export default class SpeechFlowNodeT2AKokoro extends SpeechFlowNode {
|
|
|
150
150
|
|
|
151
151
|
/* close node */
|
|
152
152
|
async close () {
|
|
153
|
-
/*
|
|
153
|
+
/* shutdown stream */
|
|
154
154
|
if (this.stream !== null) {
|
|
155
|
-
this.stream
|
|
155
|
+
await util.destroyStream(this.stream)
|
|
156
156
|
this.stream = null
|
|
157
157
|
}
|
|
158
158
|
|
|
@@ -101,7 +101,7 @@ export default class SpeechFlowNodeT2TAmazon extends SpeechFlowNode {
|
|
|
101
101
|
await new Promise((resolve) => setTimeout(resolve, delayMs))
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
|
-
throw
|
|
104
|
+
throw util.ensureError(lastError)
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
/* establish a duplex stream and connect it to AWS Translate */
|
|
@@ -143,9 +143,9 @@ export default class SpeechFlowNodeT2TAmazon extends SpeechFlowNode {
|
|
|
143
143
|
this.client = null
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
/*
|
|
146
|
+
/* shutdown stream */
|
|
147
147
|
if (this.stream !== null) {
|
|
148
|
-
this.stream
|
|
148
|
+
await util.destroyStream(this.stream)
|
|
149
149
|
this.stream = null
|
|
150
150
|
}
|
|
151
151
|
}
|
|
@@ -108,9 +108,9 @@ export default class SpeechFlowNodeT2TDeepL extends SpeechFlowNode {
|
|
|
108
108
|
|
|
109
109
|
/* close node */
|
|
110
110
|
async close () {
|
|
111
|
-
/*
|
|
111
|
+
/* shutdown stream */
|
|
112
112
|
if (this.stream !== null) {
|
|
113
|
-
this.stream
|
|
113
|
+
await util.destroyStream(this.stream)
|
|
114
114
|
this.stream = null
|
|
115
115
|
}
|
|
116
116
|
|
|
@@ -12,6 +12,7 @@ import wrapText from "wrap-text"
|
|
|
12
12
|
|
|
13
13
|
/* internal dependencies */
|
|
14
14
|
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
15
|
+
import * as util from "./speechflow-util"
|
|
15
16
|
|
|
16
17
|
/* SpeechFlow node for text-to-text formatting */
|
|
17
18
|
export default class SpeechFlowNodeT2TFormat extends SpeechFlowNode {
|
|
@@ -71,9 +72,9 @@ export default class SpeechFlowNodeT2TFormat extends SpeechFlowNode {
|
|
|
71
72
|
|
|
72
73
|
/* close node */
|
|
73
74
|
async close () {
|
|
74
|
-
/*
|
|
75
|
+
/* shutdown stream */
|
|
75
76
|
if (this.stream !== null) {
|
|
76
|
-
this.stream
|
|
77
|
+
await util.destroyStream(this.stream)
|
|
77
78
|
this.stream = null
|
|
78
79
|
}
|
|
79
80
|
}
|
|
@@ -118,9 +118,9 @@ export default class SpeechFlowNodeT2TGoogle extends SpeechFlowNode {
|
|
|
118
118
|
|
|
119
119
|
/* close node */
|
|
120
120
|
async close () {
|
|
121
|
-
/*
|
|
121
|
+
/* shutdown stream */
|
|
122
122
|
if (this.stream !== null) {
|
|
123
|
-
this.stream
|
|
123
|
+
await util.destroyStream(this.stream)
|
|
124
124
|
this.stream = null
|
|
125
125
|
}
|
|
126
126
|
|
|
@@ -26,6 +26,10 @@ export default class SpeechFlowNodeT2TModify extends SpeechFlowNode {
|
|
|
26
26
|
replace: { type: "string", val: "" }
|
|
27
27
|
})
|
|
28
28
|
|
|
29
|
+
/* sanity check parameters */
|
|
30
|
+
if (this.params.match === "")
|
|
31
|
+
throw new Error("match parameter cannot be empty")
|
|
32
|
+
|
|
29
33
|
/* declare node input/output format */
|
|
30
34
|
this.input = "text"
|
|
31
35
|
this.output = "text"
|
|
@@ -33,10 +37,6 @@ export default class SpeechFlowNodeT2TModify extends SpeechFlowNode {
|
|
|
33
37
|
|
|
34
38
|
/* open node */
|
|
35
39
|
async open () {
|
|
36
|
-
/* validate parameters */
|
|
37
|
-
if (this.params.match === "")
|
|
38
|
-
throw new Error("match parameter cannot be empty")
|
|
39
|
-
|
|
40
40
|
/* compile regex pattern */
|
|
41
41
|
const regex = util.run("compiling regex pattern",
|
|
42
42
|
() => new RegExp(this.params.match, "g"))
|
|
@@ -75,9 +75,9 @@ export default class SpeechFlowNodeT2TModify extends SpeechFlowNode {
|
|
|
75
75
|
|
|
76
76
|
/* close node */
|
|
77
77
|
async close () {
|
|
78
|
-
/*
|
|
78
|
+
/* shutdown stream */
|
|
79
79
|
if (this.stream !== null) {
|
|
80
|
-
this.stream
|
|
80
|
+
await util.destroyStream(this.stream)
|
|
81
81
|
this.stream = null
|
|
82
82
|
}
|
|
83
83
|
}
|
|
@@ -266,9 +266,9 @@ export default class SpeechFlowNodeT2TOllama extends SpeechFlowNode {
|
|
|
266
266
|
|
|
267
267
|
/* close node */
|
|
268
268
|
async close () {
|
|
269
|
-
/*
|
|
269
|
+
/* shutdown stream */
|
|
270
270
|
if (this.stream !== null) {
|
|
271
|
-
this.stream
|
|
271
|
+
await util.destroyStream(this.stream)
|
|
272
272
|
this.stream = null
|
|
273
273
|
}
|
|
274
274
|
|
|
@@ -234,9 +234,9 @@ export default class SpeechFlowNodeT2TOpenAI extends SpeechFlowNode {
|
|
|
234
234
|
|
|
235
235
|
/* close node */
|
|
236
236
|
async close () {
|
|
237
|
-
/*
|
|
237
|
+
/* shutdown stream */
|
|
238
238
|
if (this.stream !== null) {
|
|
239
|
-
this.stream
|
|
239
|
+
await util.destroyStream(this.stream)
|
|
240
240
|
this.stream = null
|
|
241
241
|
}
|
|
242
242
|
|
|
@@ -33,7 +33,7 @@ export default class SpeechFlowNodeT2TSentence extends SpeechFlowNode {
|
|
|
33
33
|
private queueRecv = this.queue.pointerUse("recv")
|
|
34
34
|
private queueSplit = this.queue.pointerUse("split")
|
|
35
35
|
private queueSend = this.queue.pointerUse("send")
|
|
36
|
-
private
|
|
36
|
+
private closing = false
|
|
37
37
|
private workingOffTimer: ReturnType<typeof setTimeout> | null = null
|
|
38
38
|
|
|
39
39
|
/* construct node */
|
|
@@ -51,12 +51,12 @@ export default class SpeechFlowNodeT2TSentence extends SpeechFlowNode {
|
|
|
51
51
|
/* open node */
|
|
52
52
|
async open () {
|
|
53
53
|
/* clear destruction flag */
|
|
54
|
-
this.
|
|
54
|
+
this.closing = false
|
|
55
55
|
|
|
56
56
|
/* work off queued text frames */
|
|
57
57
|
let workingOff = false
|
|
58
58
|
const workOffQueue = async () => {
|
|
59
|
-
if (this.
|
|
59
|
+
if (this.closing)
|
|
60
60
|
return
|
|
61
61
|
|
|
62
62
|
/* control working off round */
|
|
@@ -70,7 +70,7 @@ export default class SpeechFlowNodeT2TSentence extends SpeechFlowNode {
|
|
|
70
70
|
this.queue.off("write", workOffQueue)
|
|
71
71
|
|
|
72
72
|
/* try to work off one or more chunks */
|
|
73
|
-
while (!this.
|
|
73
|
+
while (!this.closing) {
|
|
74
74
|
const element = this.queueSplit.peek()
|
|
75
75
|
if (element === undefined)
|
|
76
76
|
break
|
|
@@ -134,7 +134,7 @@ export default class SpeechFlowNodeT2TSentence extends SpeechFlowNode {
|
|
|
134
134
|
|
|
135
135
|
/* re-initiate working off round (if still not destroyed) */
|
|
136
136
|
workingOff = false
|
|
137
|
-
if (!this.
|
|
137
|
+
if (!this.closing) {
|
|
138
138
|
this.workingOffTimer = setTimeout(workOffQueue, 100)
|
|
139
139
|
this.queue.once("write", workOffQueue)
|
|
140
140
|
}
|
|
@@ -151,7 +151,7 @@ export default class SpeechFlowNodeT2TSentence extends SpeechFlowNode {
|
|
|
151
151
|
|
|
152
152
|
/* receive text chunk (writable side of stream) */
|
|
153
153
|
write (chunk: SpeechFlowChunk, encoding, callback) {
|
|
154
|
-
if (self.
|
|
154
|
+
if (self.closing)
|
|
155
155
|
callback(new Error("stream already destroyed"))
|
|
156
156
|
else if (Buffer.isBuffer(chunk.payload))
|
|
157
157
|
callback(new Error("expected text input as string chunks"))
|
|
@@ -166,7 +166,7 @@ export default class SpeechFlowNodeT2TSentence extends SpeechFlowNode {
|
|
|
166
166
|
|
|
167
167
|
/* receive no more text chunks (writable side of stream) */
|
|
168
168
|
final (callback) {
|
|
169
|
-
if (self.
|
|
169
|
+
if (self.closing) {
|
|
170
170
|
callback()
|
|
171
171
|
return
|
|
172
172
|
}
|
|
@@ -179,7 +179,7 @@ export default class SpeechFlowNodeT2TSentence extends SpeechFlowNode {
|
|
|
179
179
|
read (_size) {
|
|
180
180
|
/* flush pending text chunks */
|
|
181
181
|
const flushPendingChunks = () => {
|
|
182
|
-
if (self.
|
|
182
|
+
if (self.closing) {
|
|
183
183
|
this.push(null)
|
|
184
184
|
return
|
|
185
185
|
}
|
|
@@ -210,7 +210,7 @@ export default class SpeechFlowNodeT2TSentence extends SpeechFlowNode {
|
|
|
210
210
|
self.queue.trim()
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
|
-
else if (!self.
|
|
213
|
+
else if (!self.closing)
|
|
214
214
|
self.queue.once("write", flushPendingChunks)
|
|
215
215
|
}
|
|
216
216
|
flushPendingChunks()
|
|
@@ -220,8 +220,8 @@ export default class SpeechFlowNodeT2TSentence extends SpeechFlowNode {
|
|
|
220
220
|
|
|
221
221
|
/* close node */
|
|
222
222
|
async close () {
|
|
223
|
-
/* indicate
|
|
224
|
-
this.
|
|
223
|
+
/* indicate closing */
|
|
224
|
+
this.closing = true
|
|
225
225
|
|
|
226
226
|
/* clean up timer */
|
|
227
227
|
if (this.workingOffTimer !== null) {
|
|
@@ -232,9 +232,9 @@ export default class SpeechFlowNodeT2TSentence extends SpeechFlowNode {
|
|
|
232
232
|
/* remove any pending event listeners */
|
|
233
233
|
this.queue.removeAllListeners("write")
|
|
234
234
|
|
|
235
|
-
/*
|
|
235
|
+
/* shutdown stream */
|
|
236
236
|
if (this.stream !== null) {
|
|
237
|
-
this.stream
|
|
237
|
+
await util.destroyStream(this.stream)
|
|
238
238
|
this.stream = null
|
|
239
239
|
}
|
|
240
240
|
}
|
|
@@ -257,9 +257,9 @@ export default class SpeechFlowNodeT2TSubtitle extends SpeechFlowNode {
|
|
|
257
257
|
|
|
258
258
|
/* close node */
|
|
259
259
|
async close () {
|
|
260
|
-
/*
|
|
260
|
+
/* shutdown stream */
|
|
261
261
|
if (this.stream !== null) {
|
|
262
|
-
this.stream
|
|
262
|
+
await util.destroyStream(this.stream)
|
|
263
263
|
this.stream = null
|
|
264
264
|
}
|
|
265
265
|
|
|
@@ -227,9 +227,9 @@ export default class SpeechFlowNodeT2TTransformers extends SpeechFlowNode {
|
|
|
227
227
|
|
|
228
228
|
/* close node */
|
|
229
229
|
async close () {
|
|
230
|
-
/*
|
|
230
|
+
/* shutdown stream */
|
|
231
231
|
if (this.stream !== null) {
|
|
232
|
-
this.stream
|
|
232
|
+
await util.destroyStream(this.stream)
|
|
233
233
|
this.stream = null
|
|
234
234
|
}
|
|
235
235
|
|
|
@@ -125,9 +125,9 @@ export default class SpeechFlowNodeX2XFilter extends SpeechFlowNode {
|
|
|
125
125
|
|
|
126
126
|
/* close node */
|
|
127
127
|
async close () {
|
|
128
|
-
/*
|
|
128
|
+
/* shutdown stream */
|
|
129
129
|
if (this.stream !== null) {
|
|
130
|
-
this.stream
|
|
130
|
+
await util.destroyStream(this.stream)
|
|
131
131
|
this.stream = null
|
|
132
132
|
}
|
|
133
133
|
}
|
|
@@ -12,6 +12,7 @@ import { Duration } from "luxon"
|
|
|
12
12
|
|
|
13
13
|
/* internal dependencies */
|
|
14
14
|
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
15
|
+
import * as util from "./speechflow-util"
|
|
15
16
|
|
|
16
17
|
/* SpeechFlow node for data flow tracing */
|
|
17
18
|
export default class SpeechFlowNodeX2XTrace extends SpeechFlowNode {
|
|
@@ -19,7 +20,7 @@ export default class SpeechFlowNodeX2XTrace extends SpeechFlowNode {
|
|
|
19
20
|
public static name = "x2x-trace"
|
|
20
21
|
|
|
21
22
|
/* internal state */
|
|
22
|
-
private
|
|
23
|
+
private closing = false
|
|
23
24
|
|
|
24
25
|
/* construct node */
|
|
25
26
|
constructor (id: string, cfg: { [ id: string ]: any }, opts: { [ id: string ]: any }, args: any[]) {
|
|
@@ -56,7 +57,7 @@ export default class SpeechFlowNodeX2XTrace extends SpeechFlowNode {
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
/* clear destruction flag */
|
|
59
|
-
this.
|
|
60
|
+
this.closing = false
|
|
60
61
|
|
|
61
62
|
/* helper functions for formatting */
|
|
62
63
|
const fmtTime = (t: Duration) => t.toFormat("hh:mm:ss.SSS")
|
|
@@ -84,7 +85,7 @@ export default class SpeechFlowNodeX2XTrace extends SpeechFlowNode {
|
|
|
84
85
|
highWaterMark: 1,
|
|
85
86
|
transform (chunk: SpeechFlowChunk, encoding, callback) {
|
|
86
87
|
let error: Error | undefined
|
|
87
|
-
if (self.
|
|
88
|
+
if (self.closing) {
|
|
88
89
|
callback(new Error("stream already destroyed"))
|
|
89
90
|
return
|
|
90
91
|
}
|
|
@@ -118,7 +119,7 @@ export default class SpeechFlowNodeX2XTrace extends SpeechFlowNode {
|
|
|
118
119
|
}
|
|
119
120
|
},
|
|
120
121
|
final (callback) {
|
|
121
|
-
if (self.
|
|
122
|
+
if (self.closing || self.params.mode === "sink") {
|
|
122
123
|
callback()
|
|
123
124
|
return
|
|
124
125
|
}
|
|
@@ -130,13 +131,13 @@ export default class SpeechFlowNodeX2XTrace extends SpeechFlowNode {
|
|
|
130
131
|
|
|
131
132
|
/* close node */
|
|
132
133
|
async close () {
|
|
133
|
-
/*
|
|
134
|
+
/* indicate closing */
|
|
135
|
+
this.closing = true
|
|
136
|
+
|
|
137
|
+
/* shutdown stream */
|
|
134
138
|
if (this.stream !== null) {
|
|
135
|
-
this.stream
|
|
139
|
+
await util.destroyStream(this.stream)
|
|
136
140
|
this.stream = null
|
|
137
141
|
}
|
|
138
|
-
|
|
139
|
-
/* indicate destruction */
|
|
140
|
-
this.destroyed = true
|
|
141
142
|
}
|
|
142
143
|
}
|
|
@@ -36,6 +36,10 @@ export default class SpeechFlowNodeXIODevice extends SpeechFlowNode {
|
|
|
36
36
|
chunk: { type: "number", pos: 2, val: 200, match: (n: number) => n >= 10 && n <= 1000 }
|
|
37
37
|
})
|
|
38
38
|
|
|
39
|
+
/* sanity check parameters */
|
|
40
|
+
if (this.params.device === "")
|
|
41
|
+
throw new Error("required parameter \"device\" has to be given")
|
|
42
|
+
|
|
39
43
|
/* declare node input/output format */
|
|
40
44
|
if (this.params.mode === "rw") {
|
|
41
45
|
this.input = "audio"
|
|
@@ -163,9 +167,6 @@ export default class SpeechFlowNodeXIODevice extends SpeechFlowNode {
|
|
|
163
167
|
|
|
164
168
|
/* open node */
|
|
165
169
|
async open () {
|
|
166
|
-
if (this.params.device === "")
|
|
167
|
-
throw new Error("required parameter \"device\" has to be given")
|
|
168
|
-
|
|
169
170
|
/* determine device */
|
|
170
171
|
const device = this.audioDeviceFromURL(this.params.mode, this.params.device)
|
|
171
172
|
|