speechflow 1.3.1 → 1.3.2
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 +6 -0
- package/dst/speechflow-node-a2a-gender.d.ts +2 -0
- package/dst/speechflow-node-a2a-gender.js +137 -59
- package/dst/speechflow-node-a2a-gender.js.map +1 -1
- package/dst/speechflow-node-a2a-meter.d.ts +3 -1
- package/dst/speechflow-node-a2a-meter.js +80 -39
- package/dst/speechflow-node-a2a-meter.js.map +1 -1
- package/dst/speechflow-node-a2a-mute.d.ts +1 -0
- package/dst/speechflow-node-a2a-mute.js +37 -11
- package/dst/speechflow-node-a2a-mute.js.map +1 -1
- package/dst/speechflow-node-a2a-vad.d.ts +3 -0
- package/dst/speechflow-node-a2a-vad.js +194 -96
- package/dst/speechflow-node-a2a-vad.js.map +1 -1
- package/dst/speechflow-node-a2a-wav.js +27 -11
- package/dst/speechflow-node-a2a-wav.js.map +1 -1
- package/dst/speechflow-node-a2t-deepgram.d.ts +4 -0
- package/dst/speechflow-node-a2t-deepgram.js +136 -46
- package/dst/speechflow-node-a2t-deepgram.js.map +1 -1
- package/dst/speechflow-node-t2a-elevenlabs.d.ts +2 -0
- package/dst/speechflow-node-t2a-elevenlabs.js +61 -12
- package/dst/speechflow-node-t2a-elevenlabs.js.map +1 -1
- package/dst/speechflow-node-t2a-kokoro.d.ts +1 -0
- package/dst/speechflow-node-t2a-kokoro.js +10 -4
- package/dst/speechflow-node-t2a-kokoro.js.map +1 -1
- package/dst/speechflow-node-t2t-deepl.js +8 -4
- package/dst/speechflow-node-t2t-deepl.js.map +1 -1
- package/dst/speechflow-node-t2t-format.js +2 -2
- package/dst/speechflow-node-t2t-format.js.map +1 -1
- package/dst/speechflow-node-t2t-ollama.js +1 -1
- package/dst/speechflow-node-t2t-ollama.js.map +1 -1
- package/dst/speechflow-node-t2t-openai.js +1 -1
- package/dst/speechflow-node-t2t-openai.js.map +1 -1
- package/dst/speechflow-node-t2t-sentence.d.ts +1 -1
- package/dst/speechflow-node-t2t-sentence.js +34 -18
- package/dst/speechflow-node-t2t-sentence.js.map +1 -1
- package/dst/speechflow-node-t2t-subtitle.d.ts +0 -1
- package/dst/speechflow-node-t2t-subtitle.js +78 -190
- package/dst/speechflow-node-t2t-subtitle.js.map +1 -1
- package/dst/speechflow-node-t2t-transformers.js +2 -2
- package/dst/speechflow-node-t2t-transformers.js.map +1 -1
- package/dst/speechflow-node-x2x-filter.js +4 -4
- package/dst/speechflow-node-x2x-trace.js +6 -13
- package/dst/speechflow-node-x2x-trace.js.map +1 -1
- package/dst/speechflow-node-xio-device.js +12 -8
- package/dst/speechflow-node-xio-device.js.map +1 -1
- package/dst/speechflow-node-xio-file.js +9 -3
- package/dst/speechflow-node-xio-file.js.map +1 -1
- package/dst/speechflow-node-xio-mqtt.js +5 -2
- package/dst/speechflow-node-xio-mqtt.js.map +1 -1
- package/dst/speechflow-node-xio-websocket.js +11 -11
- package/dst/speechflow-node-xio-websocket.js.map +1 -1
- package/dst/speechflow-node.d.ts +0 -2
- package/dst/speechflow-node.js +0 -3
- package/dst/speechflow-node.js.map +1 -1
- package/dst/speechflow-utils.d.ts +5 -0
- package/dst/speechflow-utils.js +77 -44
- package/dst/speechflow-utils.js.map +1 -1
- package/dst/speechflow.js +101 -82
- package/dst/speechflow.js.map +1 -1
- package/etc/eslint.mjs +1 -2
- package/etc/stx.conf +3 -3
- package/package.json +6 -6
- package/src/speechflow-node-a2a-gender.ts +148 -64
- package/src/speechflow-node-a2a-meter.ts +87 -40
- package/src/speechflow-node-a2a-mute.ts +39 -11
- package/src/speechflow-node-a2a-vad.ts +206 -100
- package/src/speechflow-node-a2a-wav.ts +27 -11
- package/src/speechflow-node-a2t-deepgram.ts +139 -43
- package/src/speechflow-node-t2a-elevenlabs.ts +65 -12
- package/src/speechflow-node-t2a-kokoro.ts +11 -4
- package/src/speechflow-node-t2t-deepl.ts +9 -4
- package/src/speechflow-node-t2t-format.ts +2 -2
- package/src/speechflow-node-t2t-ollama.ts +1 -1
- package/src/speechflow-node-t2t-openai.ts +1 -1
- package/src/speechflow-node-t2t-sentence.ts +37 -20
- package/src/speechflow-node-t2t-transformers.ts +4 -3
- package/src/speechflow-node-x2x-filter.ts +4 -4
- package/src/speechflow-node-x2x-trace.ts +1 -1
- package/src/speechflow-node-xio-device.ts +12 -8
- package/src/speechflow-node-xio-file.ts +9 -3
- package/src/speechflow-node-xio-mqtt.ts +5 -2
- package/src/speechflow-node-xio-websocket.ts +12 -12
- package/src/speechflow-utils.ts +78 -44
- package/src/speechflow.ts +114 -35
package/src/speechflow.ts
CHANGED
|
@@ -176,6 +176,21 @@ type wsPeerInfo = {
|
|
|
176
176
|
logPrefix: pkg.name
|
|
177
177
|
})
|
|
178
178
|
|
|
179
|
+
/* catch uncaught exceptions */
|
|
180
|
+
process.on("uncaughtException", (err) => {
|
|
181
|
+
cli!.log("error", `uncaught exception: ${err.message}`)
|
|
182
|
+
process.exit(1)
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
/* catch unhandled promise rejections */
|
|
186
|
+
process.on("unhandledRejection", (reason) => {
|
|
187
|
+
if (reason instanceof Error)
|
|
188
|
+
cli!.log("error", `unhandled rejection: ${reason.message}`)
|
|
189
|
+
else
|
|
190
|
+
cli!.log("error", `unhandled rejection: ${reason}`)
|
|
191
|
+
process.exit(1)
|
|
192
|
+
})
|
|
193
|
+
|
|
179
194
|
/* provide startup information */
|
|
180
195
|
cli.log("info", `starting SpeechFlow ${pkg["x-stdver"]} (${pkg["x-release"]})`)
|
|
181
196
|
|
|
@@ -185,21 +200,6 @@ type wsPeerInfo = {
|
|
|
185
200
|
for (const key of Object.keys(result.parsed))
|
|
186
201
|
cli.log("info", `loaded environment variable "${key}" from ".env" files`)
|
|
187
202
|
|
|
188
|
-
/* handle uncaught exceptions */
|
|
189
|
-
process.on("uncaughtException", async (err: Error) => {
|
|
190
|
-
cli!.log("warning", `process crashed with a fatal error: ${err} ${err.stack}`)
|
|
191
|
-
process.exit(1)
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
/* handle unhandled promise rejections */
|
|
195
|
-
process.on("unhandledRejection", async (reason, promise) => {
|
|
196
|
-
if (reason instanceof Error)
|
|
197
|
-
cli!.log("error", `promise rejection not handled: ${reason.message}: ${reason.stack}`)
|
|
198
|
-
else
|
|
199
|
-
cli!.log("error", `promise rejection not handled: ${reason}`)
|
|
200
|
-
process.exit(1)
|
|
201
|
-
})
|
|
202
|
-
|
|
203
203
|
/* sanity check usage */
|
|
204
204
|
let n = 0
|
|
205
205
|
if (typeof args.e === "string" && args.e !== "") n++
|
|
@@ -220,7 +220,16 @@ type wsPeerInfo = {
|
|
|
220
220
|
throw new Error("invalid configuration file specification (expected \"<id>@<yaml-config-file>\")")
|
|
221
221
|
const [ , id, file ] = m
|
|
222
222
|
const yaml = await cli.input(file, { encoding: "utf8" })
|
|
223
|
-
|
|
223
|
+
let obj: any
|
|
224
|
+
try {
|
|
225
|
+
obj = jsYAML.load(yaml)
|
|
226
|
+
}
|
|
227
|
+
catch (err) {
|
|
228
|
+
if (err instanceof Error)
|
|
229
|
+
throw new Error(`failed to parse YAML configuration: ${err.message}`)
|
|
230
|
+
else
|
|
231
|
+
throw new Error(`failed to parse YAML configuration: ${err}`)
|
|
232
|
+
}
|
|
224
233
|
if (obj[id] === undefined)
|
|
225
234
|
throw new Error(`no such id "${id}" found in configuration file`)
|
|
226
235
|
config = obj[id] as string
|
|
@@ -308,7 +317,14 @@ type wsPeerInfo = {
|
|
|
308
317
|
for (const name of Object.keys(nodes)) {
|
|
309
318
|
cli!.log("info", `gathering status of node <${name}>`)
|
|
310
319
|
const node = new nodes[name](name, cfg, {}, [])
|
|
311
|
-
const status = await
|
|
320
|
+
const status = await Promise.race<{ [ key: string ]: string | number }>([
|
|
321
|
+
node.status(),
|
|
322
|
+
new Promise((resolve, reject) => setTimeout(() =>
|
|
323
|
+
reject(new Error("timeout")), 10 * 1000))
|
|
324
|
+
]).catch((err: Error) => {
|
|
325
|
+
cli!.log("warning", `[${node.id}]: failed to gather status of node <${node.id}>: ${err.message}`)
|
|
326
|
+
return {} as { [ key: string ]: string | number }
|
|
327
|
+
})
|
|
312
328
|
if (Object.keys(status).length > 0) {
|
|
313
329
|
let first = true
|
|
314
330
|
for (const key of Object.keys(status)) {
|
|
@@ -337,9 +353,9 @@ type wsPeerInfo = {
|
|
|
337
353
|
}
|
|
338
354
|
catch (err) {
|
|
339
355
|
if (err instanceof Error && err.name === "FlowLinkError")
|
|
340
|
-
cli!.log("error", `failed to parse SpeechFlow configuration: ${err.toString()}
|
|
356
|
+
cli!.log("error", `failed to parse SpeechFlow configuration: ${err.toString()}`)
|
|
341
357
|
else if (err instanceof Error)
|
|
342
|
-
cli!.log("error", `failed to parse SpeechFlow configuration: ${err.message}
|
|
358
|
+
cli!.log("error", `failed to parse SpeechFlow configuration: ${err.message}`)
|
|
343
359
|
else
|
|
344
360
|
cli!.log("error", "failed to parse SpeechFlow configuration: internal error")
|
|
345
361
|
process.exit(1)
|
|
@@ -438,9 +454,13 @@ type wsPeerInfo = {
|
|
|
438
454
|
/* open node */
|
|
439
455
|
cli!.log("info", `open node <${node.id}>`)
|
|
440
456
|
node.setTimeZero(timeZero)
|
|
441
|
-
await
|
|
442
|
-
|
|
443
|
-
|
|
457
|
+
await Promise.race<void>([
|
|
458
|
+
node.open(),
|
|
459
|
+
new Promise((resolve, reject) => setTimeout(() =>
|
|
460
|
+
reject(new Error("timeout")), 10 * 1000))
|
|
461
|
+
]).catch((err: Error) => {
|
|
462
|
+
cli!.log("error", `[${node.id}]: failed to open node <${node.id}>: ${err.message}`)
|
|
463
|
+
throw new Error(`failed to open node <${node.id}>: ${err.message}`)
|
|
444
464
|
})
|
|
445
465
|
}
|
|
446
466
|
|
|
@@ -462,9 +482,10 @@ type wsPeerInfo = {
|
|
|
462
482
|
}
|
|
463
483
|
}
|
|
464
484
|
|
|
465
|
-
/* graph processing: PASS
|
|
485
|
+
/* graph processing: PASS 5: track stream finishing */
|
|
466
486
|
const activeNodes = new Set<SpeechFlowNode>()
|
|
467
487
|
const finishEvents = new EventEmitter()
|
|
488
|
+
finishEvents.setMaxListeners(graphNodes.size + 10)
|
|
468
489
|
for (const node of graphNodes) {
|
|
469
490
|
if (node.stream === null)
|
|
470
491
|
throw new Error(`stream of node <${node.id}> still not initialized`)
|
|
@@ -512,9 +533,12 @@ type wsPeerInfo = {
|
|
|
512
533
|
throw new Error(`external request failed: no such node <${name}>`)
|
|
513
534
|
}
|
|
514
535
|
else {
|
|
515
|
-
await
|
|
516
|
-
|
|
517
|
-
|
|
536
|
+
await Promise.race<void>([
|
|
537
|
+
foundNode.receiveRequest(args),
|
|
538
|
+
new Promise((resolve, reject) => setTimeout(() =>
|
|
539
|
+
reject(new Error("timeout")), 10 * 1000))
|
|
540
|
+
]).catch((err: Error) => {
|
|
541
|
+
cli!.log("warning", `external request to node <${name}> failed: ${err.message}`)
|
|
518
542
|
})
|
|
519
543
|
}
|
|
520
544
|
}
|
|
@@ -623,7 +647,7 @@ type wsPeerInfo = {
|
|
|
623
647
|
}
|
|
624
648
|
})
|
|
625
649
|
await hapi.start()
|
|
626
|
-
cli!.log("info", `HAPI: started REST/WebSocket network service: http://${args.
|
|
650
|
+
cli!.log("info", `HAPI: started REST/WebSocket network service: http://${args.a}:${args.p}`)
|
|
627
651
|
|
|
628
652
|
/* hook for sendResponse method of nodes */
|
|
629
653
|
for (const node of graphNodes) {
|
|
@@ -631,7 +655,8 @@ type wsPeerInfo = {
|
|
|
631
655
|
const data = JSON.stringify({ response: "NOTIFY", node: node.id, args })
|
|
632
656
|
for (const [ peer, info ] of wsPeers.entries()) {
|
|
633
657
|
cli!.log("info", `HAPI: peer ${peer}: ${data}`)
|
|
634
|
-
info.ws.
|
|
658
|
+
if (info.ws.readyState === WebSocket.OPEN)
|
|
659
|
+
info.ws.send(data)
|
|
635
660
|
}
|
|
636
661
|
})
|
|
637
662
|
}
|
|
@@ -653,9 +678,39 @@ type wsPeerInfo = {
|
|
|
653
678
|
cli!.log("warning", `**** received signal ${signal} -- shutting down service ****`)
|
|
654
679
|
|
|
655
680
|
/* shutdown HAPI service */
|
|
656
|
-
cli!.log("info", `HAPI: stopping REST/WebSocket network service: http://${args.
|
|
681
|
+
cli!.log("info", `HAPI: stopping REST/WebSocket network service: http://${args.a}:${args.p}`)
|
|
657
682
|
await hapi.stop({ timeout: 2000 })
|
|
658
683
|
|
|
684
|
+
/* clear WebSocket connections */
|
|
685
|
+
if (wsPeers.size > 0) {
|
|
686
|
+
cli!.log("info", "HAPI: closing WebSocket connections")
|
|
687
|
+
const closePromises: Promise<void>[] = []
|
|
688
|
+
for (const [ peer, info ] of wsPeers.entries()) {
|
|
689
|
+
closePromises.push(new Promise<void>((resolve, reject) => {
|
|
690
|
+
if (info.ws.readyState !== WebSocket.OPEN)
|
|
691
|
+
resolve()
|
|
692
|
+
else {
|
|
693
|
+
const timeout = setTimeout(() => {
|
|
694
|
+
reject(new Error(`timeout for peer ${peer}`))
|
|
695
|
+
}, 2 * 1000)
|
|
696
|
+
info.ws.once("close", () => {
|
|
697
|
+
clearTimeout(timeout)
|
|
698
|
+
resolve()
|
|
699
|
+
})
|
|
700
|
+
info.ws.close()
|
|
701
|
+
}
|
|
702
|
+
}))
|
|
703
|
+
}
|
|
704
|
+
await Promise.race([
|
|
705
|
+
Promise.all(closePromises),
|
|
706
|
+
new Promise((resolve, reject) =>
|
|
707
|
+
setTimeout(() => reject(new Error("timeout for all peers")), 5 * 1000))
|
|
708
|
+
]).catch((err) => {
|
|
709
|
+
cli!.log("warning", `HAPI: WebSockets failed to close: ${err}`)
|
|
710
|
+
})
|
|
711
|
+
wsPeers.clear()
|
|
712
|
+
}
|
|
713
|
+
|
|
659
714
|
/* graph processing: PASS 1: disconnect node streams */
|
|
660
715
|
for (const node of graphNodes) {
|
|
661
716
|
if (node.stream === null) {
|
|
@@ -685,8 +740,12 @@ type wsPeerInfo = {
|
|
|
685
740
|
/* graph processing: PASS 2: close nodes */
|
|
686
741
|
for (const node of graphNodes) {
|
|
687
742
|
cli!.log("info", `close node <${node.id}>`)
|
|
688
|
-
await
|
|
689
|
-
|
|
743
|
+
await Promise.race<void>([
|
|
744
|
+
node.close(),
|
|
745
|
+
new Promise((resolve, reject) => setTimeout(() =>
|
|
746
|
+
reject(new Error("timeout")), 10 * 1000))
|
|
747
|
+
]).catch((err: Error) => {
|
|
748
|
+
cli!.log("warning", `node <${node.id}> failed to close: ${err.message}`)
|
|
690
749
|
})
|
|
691
750
|
}
|
|
692
751
|
|
|
@@ -705,6 +764,12 @@ type wsPeerInfo = {
|
|
|
705
764
|
graphNodes.delete(node)
|
|
706
765
|
}
|
|
707
766
|
|
|
767
|
+
/* clear event emitters */
|
|
768
|
+
finishEvents.removeAllListeners()
|
|
769
|
+
|
|
770
|
+
/* clear active nodes */
|
|
771
|
+
activeNodes.clear()
|
|
772
|
+
|
|
708
773
|
/* terminate process */
|
|
709
774
|
if (signal === "finished") {
|
|
710
775
|
cli!.log("info", "terminate process (exit code 0)")
|
|
@@ -715,24 +780,38 @@ type wsPeerInfo = {
|
|
|
715
780
|
process.exit(1)
|
|
716
781
|
}
|
|
717
782
|
}
|
|
783
|
+
|
|
784
|
+
/* hook into regular finish */
|
|
718
785
|
finishEvents.on("finished", () => { shutdown("finished") })
|
|
786
|
+
|
|
787
|
+
/* hook into process signals */
|
|
719
788
|
process.on("SIGINT", () => { shutdown("SIGINT") })
|
|
720
789
|
process.on("SIGUSR1", () => { shutdown("SIGUSR1") })
|
|
721
790
|
process.on("SIGUSR2", () => { shutdown("SIGUSR2") })
|
|
722
791
|
process.on("SIGTERM", () => { shutdown("SIGTERM") })
|
|
792
|
+
|
|
793
|
+
/* re-hook into uncaught exception handler */
|
|
794
|
+
process.removeAllListeners("uncaughtException")
|
|
723
795
|
process.on("uncaughtException", (err) => {
|
|
724
|
-
cli!.log("error", `uncaught exception: ${err}`)
|
|
796
|
+
cli!.log("error", `uncaught exception: ${err.message}`)
|
|
725
797
|
shutdown("exception")
|
|
726
798
|
})
|
|
799
|
+
|
|
800
|
+
/* re-hook into unhandled promise rejection handler */
|
|
801
|
+
process.removeAllListeners("unhandledRejection")
|
|
727
802
|
process.on("unhandledRejection", (reason) => {
|
|
728
|
-
|
|
803
|
+
if (reason instanceof Error)
|
|
804
|
+
cli!.log("error", `unhandled rejection: ${reason.message}`)
|
|
805
|
+
else
|
|
806
|
+
cli!.log("error", `unhandled rejection: ${reason}`)
|
|
729
807
|
shutdown("exception")
|
|
730
808
|
})
|
|
731
809
|
})().catch((err: Error) => {
|
|
810
|
+
/* top-level exception handling */
|
|
732
811
|
if (cli !== null)
|
|
733
|
-
cli.log("error", err.message)
|
|
812
|
+
cli.log("error", `${err.message}:\n${err.stack}`)
|
|
734
813
|
else
|
|
735
|
-
process.stderr.write(`${pkg.name}: ${chalk.red("ERROR")}: ${err.message}
|
|
814
|
+
process.stderr.write(`${pkg.name}: ${chalk.red("ERROR")}: ${err.message}\n${err.stack}\n`)
|
|
736
815
|
process.exit(1)
|
|
737
816
|
})
|
|
738
817
|
|