speechflow 1.1.0 → 1.2.1
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 +18 -0
- package/README.md +37 -3
- package/dst/speechflow-node-a2a-gender.d.ts +17 -0
- package/dst/speechflow-node-a2a-gender.js +272 -0
- package/dst/speechflow-node-a2a-gender.js.map +1 -0
- package/dst/speechflow-node-a2a-meter.js +2 -2
- package/dst/speechflow-node-a2a-meter.js.map +1 -1
- package/dst/speechflow-node-a2a-mute.js +1 -0
- package/dst/speechflow-node-a2a-mute.js.map +1 -1
- package/dst/speechflow-node-a2a-vad.js +47 -63
- package/dst/speechflow-node-a2a-vad.js.map +1 -1
- package/dst/speechflow-node-a2a-wav.js +145 -122
- package/dst/speechflow-node-a2a-wav.js.map +1 -1
- package/dst/speechflow-node-a2t-deepgram.js +13 -3
- package/dst/speechflow-node-a2t-deepgram.js.map +1 -1
- package/dst/speechflow-node-t2a-elevenlabs.js +10 -5
- package/dst/speechflow-node-t2a-elevenlabs.js.map +1 -1
- package/dst/speechflow-node-t2a-kokoro.js.map +1 -1
- package/dst/speechflow-node-t2t-deepl.js.map +1 -1
- package/dst/speechflow-node-t2t-format.js.map +1 -1
- package/dst/speechflow-node-t2t-ollama.js.map +1 -1
- package/dst/speechflow-node-t2t-openai.js.map +1 -1
- package/dst/speechflow-node-t2t-subtitle.js.map +1 -1
- package/dst/speechflow-node-t2t-transformers.js.map +1 -1
- package/dst/speechflow-node-x2x-filter.d.ts +11 -0
- package/dst/speechflow-node-x2x-filter.js +113 -0
- package/dst/speechflow-node-x2x-filter.js.map +1 -0
- package/dst/speechflow-node-x2x-trace.js +24 -10
- package/dst/speechflow-node-x2x-trace.js.map +1 -1
- package/dst/speechflow-node-xio-device.js +14 -5
- package/dst/speechflow-node-xio-device.js.map +1 -1
- package/dst/speechflow-node-xio-file.js +58 -27
- package/dst/speechflow-node-xio-file.js.map +1 -1
- package/dst/speechflow-node-xio-mqtt.js.map +1 -1
- package/dst/speechflow-node-xio-websocket.js.map +1 -1
- package/dst/speechflow-node.js +1 -0
- package/dst/speechflow-node.js.map +1 -1
- package/dst/speechflow-utils.d.ts +14 -1
- package/dst/speechflow-utils.js +110 -2
- package/dst/speechflow-utils.js.map +1 -1
- package/dst/speechflow.js +56 -53
- package/dst/speechflow.js.map +1 -1
- package/etc/speechflow.yaml +51 -24
- package/package.json +6 -5
- package/src/speechflow-node-a2a-gender.ts +272 -0
- package/src/speechflow-node-a2a-meter.ts +3 -3
- package/src/speechflow-node-a2a-mute.ts +1 -0
- package/src/speechflow-node-a2a-vad.ts +58 -68
- package/src/speechflow-node-a2a-wav.ts +128 -91
- package/src/speechflow-node-a2t-deepgram.ts +15 -4
- package/src/speechflow-node-t2a-elevenlabs.ts +13 -8
- package/src/speechflow-node-t2a-kokoro.ts +3 -3
- package/src/speechflow-node-t2t-deepl.ts +2 -2
- package/src/speechflow-node-t2t-format.ts +2 -2
- package/src/speechflow-node-t2t-ollama.ts +2 -2
- package/src/speechflow-node-t2t-openai.ts +2 -2
- package/src/speechflow-node-t2t-subtitle.ts +1 -1
- package/src/speechflow-node-t2t-transformers.ts +2 -2
- package/src/speechflow-node-x2x-filter.ts +122 -0
- package/src/speechflow-node-x2x-trace.ts +28 -11
- package/src/speechflow-node-xio-device.ts +20 -8
- package/src/speechflow-node-xio-file.ts +74 -36
- package/src/speechflow-node-xio-mqtt.ts +3 -3
- package/src/speechflow-node-xio-websocket.ts +1 -1
- package/src/speechflow-node.ts +2 -0
- package/src/speechflow-utils.ts +81 -2
- package/src/speechflow.ts +84 -81
package/src/speechflow.ts
CHANGED
|
@@ -5,33 +5,33 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
/* standard dependencies */
|
|
8
|
-
import path
|
|
9
|
-
import Stream
|
|
10
|
-
import { EventEmitter }
|
|
11
|
-
import http
|
|
12
|
-
import * as HAPI
|
|
13
|
-
import WebSocket
|
|
14
|
-
import HAPIWebSocket
|
|
15
|
-
import HAPIHeader
|
|
8
|
+
import path from "node:path"
|
|
9
|
+
import Stream from "node:stream"
|
|
10
|
+
import { EventEmitter } from "node:events"
|
|
11
|
+
import http from "node:http"
|
|
12
|
+
import * as HAPI from "@hapi/hapi"
|
|
13
|
+
import WebSocket from "ws"
|
|
14
|
+
import HAPIWebSocket from "hapi-plugin-websocket"
|
|
15
|
+
import HAPIHeader from "hapi-plugin-header"
|
|
16
16
|
|
|
17
17
|
/* external dependencies */
|
|
18
|
-
import { DateTime }
|
|
19
|
-
import CLIio
|
|
20
|
-
import yargs
|
|
21
|
-
import { hideBin }
|
|
22
|
-
import jsYAML
|
|
23
|
-
import FlowLink
|
|
24
|
-
import objectPath
|
|
25
|
-
import installedPackages
|
|
26
|
-
import dotenvx
|
|
27
|
-
import syspath
|
|
28
|
-
import * as arktype
|
|
29
|
-
import Table
|
|
30
|
-
import chalk
|
|
18
|
+
import { DateTime } from "luxon"
|
|
19
|
+
import CLIio from "cli-io"
|
|
20
|
+
import yargs from "yargs"
|
|
21
|
+
import { hideBin } from "yargs/helpers"
|
|
22
|
+
import jsYAML from "js-yaml"
|
|
23
|
+
import FlowLink from "flowlink"
|
|
24
|
+
import objectPath from "object-path"
|
|
25
|
+
import installedPackages from "installed-packages"
|
|
26
|
+
import dotenvx from "@dotenvx/dotenvx"
|
|
27
|
+
import syspath from "syspath"
|
|
28
|
+
import * as arktype from "arktype"
|
|
29
|
+
import Table from "cli-table3"
|
|
30
|
+
import chalk from "chalk"
|
|
31
31
|
|
|
32
32
|
/* internal dependencies */
|
|
33
|
-
import SpeechFlowNode
|
|
34
|
-
import pkg
|
|
33
|
+
import SpeechFlowNode from "./speechflow-node"
|
|
34
|
+
import pkg from "../package.json"
|
|
35
35
|
|
|
36
36
|
/* central CLI context */
|
|
37
37
|
let cli: CLIio | null = null
|
|
@@ -231,19 +231,21 @@ type wsPeerInfo = {
|
|
|
231
231
|
/* load internal SpeechFlow nodes */
|
|
232
232
|
const pkgsI = [
|
|
233
233
|
"./speechflow-node-a2a-ffmpeg.js",
|
|
234
|
-
"./speechflow-node-a2a-
|
|
235
|
-
"./speechflow-node-a2a-mute.js",
|
|
234
|
+
"./speechflow-node-a2a-gender.js",
|
|
236
235
|
"./speechflow-node-a2a-meter.js",
|
|
236
|
+
"./speechflow-node-a2a-mute.js",
|
|
237
237
|
"./speechflow-node-a2a-vad.js",
|
|
238
|
+
"./speechflow-node-a2a-wav.js",
|
|
238
239
|
"./speechflow-node-a2t-deepgram.js",
|
|
239
240
|
"./speechflow-node-t2a-elevenlabs.js",
|
|
240
241
|
"./speechflow-node-t2a-kokoro.js",
|
|
241
242
|
"./speechflow-node-t2t-deepl.js",
|
|
242
|
-
"./speechflow-node-t2t-
|
|
243
|
+
"./speechflow-node-t2t-format.js",
|
|
243
244
|
"./speechflow-node-t2t-ollama.js",
|
|
244
|
-
"./speechflow-node-t2t-
|
|
245
|
+
"./speechflow-node-t2t-openai.js",
|
|
245
246
|
"./speechflow-node-t2t-subtitle.js",
|
|
246
|
-
"./speechflow-node-t2t-
|
|
247
|
+
"./speechflow-node-t2t-transformers.js",
|
|
248
|
+
"./speechflow-node-x2x-filter.js",
|
|
247
249
|
"./speechflow-node-x2x-trace.js",
|
|
248
250
|
"./speechflow-node-xio-device.js",
|
|
249
251
|
"./speechflow-node-xio-file.js",
|
|
@@ -255,7 +257,7 @@ type wsPeerInfo = {
|
|
|
255
257
|
while (node.default !== undefined)
|
|
256
258
|
node = node.default
|
|
257
259
|
if (typeof node === "function" && typeof node.name === "string") {
|
|
258
|
-
cli.log("info", `loading SpeechFlow node
|
|
260
|
+
cli.log("info", `loading SpeechFlow node <${node.name}> from internal module`)
|
|
259
261
|
nodes[node.name] = node as typeof SpeechFlowNode
|
|
260
262
|
}
|
|
261
263
|
}
|
|
@@ -269,11 +271,11 @@ type wsPeerInfo = {
|
|
|
269
271
|
node = node.default
|
|
270
272
|
if (typeof node === "function" && typeof node.name === "string") {
|
|
271
273
|
if (nodes[node.name] !== undefined) {
|
|
272
|
-
cli.log("warning", `failed loading SpeechFlow node
|
|
274
|
+
cli.log("warning", `failed loading SpeechFlow node <${node.name}> ` +
|
|
273
275
|
`from external module "${pkg}" -- node already exists`)
|
|
274
276
|
continue
|
|
275
277
|
}
|
|
276
|
-
cli.log("info", `loading SpeechFlow node
|
|
278
|
+
cli.log("info", `loading SpeechFlow node <${node.name}> from external module "${pkg}"`)
|
|
277
279
|
nodes[node.name] = node as typeof SpeechFlowNode
|
|
278
280
|
}
|
|
279
281
|
}
|
|
@@ -351,7 +353,7 @@ type wsPeerInfo = {
|
|
|
351
353
|
},
|
|
352
354
|
createNode (id: string, opts: { [ id: string ]: any }, args: any[]) {
|
|
353
355
|
if (nodes[id] === undefined)
|
|
354
|
-
throw new Error(`unknown node
|
|
356
|
+
throw new Error(`unknown node <${id}>`)
|
|
355
357
|
let node: SpeechFlowNode
|
|
356
358
|
try {
|
|
357
359
|
let num = nodeNums.get(nodes[id]) ?? 0
|
|
@@ -362,19 +364,19 @@ type wsPeerInfo = {
|
|
|
362
364
|
catch (err) {
|
|
363
365
|
/* fatal error */
|
|
364
366
|
if (err instanceof Error)
|
|
365
|
-
cli!.log("error", `creation of <${id}>
|
|
367
|
+
cli!.log("error", `creation of node <${id}> failed: ${err.message}`)
|
|
366
368
|
else
|
|
367
|
-
cli!.log("error", `creation of <${id}>
|
|
369
|
+
cli!.log("error", `creation of node <${id}> failed: ${err}`)
|
|
368
370
|
process.exit(1)
|
|
369
371
|
}
|
|
370
372
|
const params = Object.keys(node.params)
|
|
371
373
|
.map((key) => `${key}: ${JSON.stringify(node.params[key])}`).join(", ")
|
|
372
|
-
cli!.log("info", `create node
|
|
374
|
+
cli!.log("info", `create node <${node.id}> (${params})`)
|
|
373
375
|
graphNodes.add(node)
|
|
374
376
|
return node
|
|
375
377
|
},
|
|
376
378
|
connectNode (node1: SpeechFlowNode, node2: SpeechFlowNode) {
|
|
377
|
-
cli!.log("info", `connect node
|
|
379
|
+
cli!.log("info", `connect node <${node1.id}> to node <${node2.id}>`)
|
|
378
380
|
node1.connect(node2)
|
|
379
381
|
}
|
|
380
382
|
})
|
|
@@ -397,7 +399,7 @@ type wsPeerInfo = {
|
|
|
397
399
|
|
|
398
400
|
/* ensure necessary incoming links */
|
|
399
401
|
if (node.input !== "none" && connectionsIn.length === 0)
|
|
400
|
-
throw new Error(`node
|
|
402
|
+
throw new Error(`node <${node.id}> requires input but has no input nodes connected`)
|
|
401
403
|
|
|
402
404
|
/* prune unnecessary incoming links */
|
|
403
405
|
if (node.input === "none" && connectionsIn.length > 0)
|
|
@@ -405,7 +407,7 @@ type wsPeerInfo = {
|
|
|
405
407
|
|
|
406
408
|
/* ensure necessary outgoing links */
|
|
407
409
|
if (node.output !== "none" && connectionsOut.length === 0)
|
|
408
|
-
throw new Error(`node
|
|
410
|
+
throw new Error(`node <${node.id}> requires output but has no output nodes connected`)
|
|
409
411
|
|
|
410
412
|
/* prune unnecessary outgoing links */
|
|
411
413
|
if (node.output === "none" && connectionsOut.length > 0)
|
|
@@ -416,8 +418,8 @@ type wsPeerInfo = {
|
|
|
416
418
|
connectionsOut = Array.from(node.connectionsOut)
|
|
417
419
|
for (const other of connectionsOut)
|
|
418
420
|
if (other.input !== node.output)
|
|
419
|
-
throw new Error(`${node.output} output node
|
|
420
|
-
`connected to ${other.input} input node
|
|
421
|
+
throw new Error(`${node.output} output node <${node.id}> cannot be ` +
|
|
422
|
+
`connected to ${other.input} input node <${other.id}> (payload is incompatible)`)
|
|
421
423
|
}
|
|
422
424
|
|
|
423
425
|
/* graph processing: PASS 3: open nodes */
|
|
@@ -431,34 +433,34 @@ type wsPeerInfo = {
|
|
|
431
433
|
})
|
|
432
434
|
|
|
433
435
|
/* open node */
|
|
434
|
-
cli!.log("info", `open node
|
|
436
|
+
cli!.log("info", `open node <${node.id}>`)
|
|
435
437
|
await node.open().catch((err: Error) => {
|
|
436
438
|
cli!.log("error", `[${node.id}]: ${err.message}`)
|
|
437
|
-
throw new Error(`failed to open node
|
|
439
|
+
throw new Error(`failed to open node <${node.id}>`)
|
|
438
440
|
})
|
|
439
441
|
}
|
|
440
442
|
|
|
441
443
|
/* graph processing: PASS 4: set time zero in all nodes */
|
|
442
444
|
const timeZero = DateTime.now()
|
|
443
445
|
for (const node of graphNodes) {
|
|
444
|
-
cli!.log("info", `set time zero in node
|
|
446
|
+
cli!.log("info", `set time zero in node <${node.id}>`)
|
|
445
447
|
node.setTimeZero(timeZero)
|
|
446
448
|
}
|
|
447
449
|
|
|
448
450
|
/* graph processing: PASS 5: connect node streams */
|
|
449
451
|
for (const node of graphNodes) {
|
|
450
452
|
if (node.stream === null)
|
|
451
|
-
throw new Error(`stream of node
|
|
453
|
+
throw new Error(`stream of node <${node.id}> still not initialized`)
|
|
452
454
|
for (const other of Array.from(node.connectionsOut)) {
|
|
453
455
|
if (other.stream === null)
|
|
454
|
-
throw new Error(`stream of incoming node
|
|
455
|
-
cli!.log("info", `connect stream of node
|
|
456
|
+
throw new Error(`stream of incoming node <${other.id}> still not initialized`)
|
|
457
|
+
cli!.log("info", `connect stream of node <${node.id}> to stream of node <${other.id}>`)
|
|
456
458
|
if (!( node.stream instanceof Stream.Readable
|
|
457
459
|
|| node.stream instanceof Stream.Duplex ))
|
|
458
|
-
throw new Error(`stream of output node
|
|
460
|
+
throw new Error(`stream of output node <${node.id}> is neither of Readable nor Duplex type`)
|
|
459
461
|
if (!( other.stream instanceof Stream.Writable
|
|
460
462
|
|| other.stream instanceof Stream.Duplex ))
|
|
461
|
-
throw new Error(`stream of input node
|
|
463
|
+
throw new Error(`stream of input node <${other.id}> is neither of Writable nor Duplex type`)
|
|
462
464
|
node.stream.pipe(other.stream)
|
|
463
465
|
}
|
|
464
466
|
}
|
|
@@ -468,19 +470,26 @@ type wsPeerInfo = {
|
|
|
468
470
|
const finishEvents = new EventEmitter()
|
|
469
471
|
for (const node of graphNodes) {
|
|
470
472
|
if (node.stream === null)
|
|
471
|
-
throw new Error(`stream of node
|
|
472
|
-
cli!.log("info", `observe stream of node
|
|
473
|
+
throw new Error(`stream of node <${node.id}> still not initialized`)
|
|
474
|
+
cli!.log("info", `observe stream of node <${node.id}> for finish event`)
|
|
473
475
|
activeNodes.add(node)
|
|
474
|
-
node
|
|
475
|
-
activeNodes.
|
|
476
|
-
|
|
476
|
+
const deactivateNode = (node: SpeechFlowNode, msg: string) => {
|
|
477
|
+
if (activeNodes.has(node))
|
|
478
|
+
activeNodes.delete(node)
|
|
479
|
+
cli!.log("info", `${msg} (${activeNodes.size} active nodes remaining)`)
|
|
477
480
|
if (activeNodes.size === 0) {
|
|
478
481
|
const timeFinished = DateTime.now()
|
|
479
482
|
const duration = timeFinished.diff(timeZero)
|
|
480
|
-
cli!.log("info", "everything finished -- stream processing in SpeechFlow graph stops " +
|
|
481
|
-
`(total duration: ${duration.toFormat("hh:mm:ss.SSS")})
|
|
483
|
+
cli!.log("info", "**** everything finished -- stream processing in SpeechFlow graph stops " +
|
|
484
|
+
`(total duration: ${duration.toFormat("hh:mm:ss.SSS")}) ****`)
|
|
482
485
|
finishEvents.emit("finished")
|
|
483
486
|
}
|
|
487
|
+
}
|
|
488
|
+
node.stream.on("end", () => {
|
|
489
|
+
deactivateNode(node, `readable stream side of node <${node.id}> raised "end" event`)
|
|
490
|
+
})
|
|
491
|
+
node.stream.on("finish", () => {
|
|
492
|
+
deactivateNode(node, `writable stream side of node <${node.id}> raised "finish" event`)
|
|
484
493
|
})
|
|
485
494
|
}
|
|
486
495
|
|
|
@@ -649,40 +658,40 @@ type wsPeerInfo = {
|
|
|
649
658
|
/* graph processing: PASS 1: disconnect node streams */
|
|
650
659
|
for (const node of graphNodes) {
|
|
651
660
|
if (node.stream === null) {
|
|
652
|
-
cli!.log("warning", `stream of node
|
|
661
|
+
cli!.log("warning", `stream of node <${node.id}> no longer initialized`)
|
|
653
662
|
continue
|
|
654
663
|
}
|
|
655
664
|
for (const other of Array.from(node.connectionsOut)) {
|
|
656
665
|
if (other.stream === null) {
|
|
657
|
-
cli!.log("warning", `stream of incoming node
|
|
666
|
+
cli!.log("warning", `stream of incoming node <${other.id}> no longer initialized`)
|
|
658
667
|
continue
|
|
659
668
|
}
|
|
660
669
|
if (!( node.stream instanceof Stream.Readable
|
|
661
670
|
|| node.stream instanceof Stream.Duplex )) {
|
|
662
|
-
cli!.log("warning", `stream of output node
|
|
671
|
+
cli!.log("warning", `stream of output node <${node.id}> is neither of Readable nor Duplex type`)
|
|
663
672
|
continue
|
|
664
673
|
}
|
|
665
674
|
if (!( other.stream instanceof Stream.Writable
|
|
666
675
|
|| other.stream instanceof Stream.Duplex )) {
|
|
667
|
-
cli!.log("warning", `stream of input node
|
|
676
|
+
cli!.log("warning", `stream of input node <${other.id}> is neither of Writable nor Duplex type`)
|
|
668
677
|
continue
|
|
669
678
|
}
|
|
670
|
-
cli!.log("info", `disconnect stream of node
|
|
679
|
+
cli!.log("info", `disconnect stream of node <${node.id}> from stream of node <${other.id}>`)
|
|
671
680
|
node.stream.unpipe(other.stream)
|
|
672
681
|
}
|
|
673
682
|
}
|
|
674
683
|
|
|
675
684
|
/* graph processing: PASS 2: close nodes */
|
|
676
685
|
for (const node of graphNodes) {
|
|
677
|
-
cli!.log("info", `close node
|
|
686
|
+
cli!.log("info", `close node <${node.id}>`)
|
|
678
687
|
await node.close().catch((err) => {
|
|
679
|
-
cli!.log("warning", `node
|
|
688
|
+
cli!.log("warning", `node <${node.id}> failed to close: ${err}`)
|
|
680
689
|
})
|
|
681
690
|
}
|
|
682
691
|
|
|
683
692
|
/* graph processing: PASS 3: disconnect nodes */
|
|
684
693
|
for (const node of graphNodes) {
|
|
685
|
-
cli!.log("info", `disconnect node
|
|
694
|
+
cli!.log("info", `disconnect node <${node.id}>`)
|
|
686
695
|
const connectionsIn = Array.from(node.connectionsIn)
|
|
687
696
|
const connectionsOut = Array.from(node.connectionsOut)
|
|
688
697
|
connectionsIn.forEach((other) => { other.disconnect(node) })
|
|
@@ -691,31 +700,25 @@ type wsPeerInfo = {
|
|
|
691
700
|
|
|
692
701
|
/* graph processing: PASS 4: shutdown nodes */
|
|
693
702
|
for (const node of graphNodes) {
|
|
694
|
-
cli!.log("info", `destroy node
|
|
703
|
+
cli!.log("info", `destroy node <${node.id}>`)
|
|
695
704
|
graphNodes.delete(node)
|
|
696
705
|
}
|
|
697
706
|
|
|
698
707
|
/* terminate process */
|
|
699
|
-
if (signal === "finished")
|
|
708
|
+
if (signal === "finished") {
|
|
709
|
+
cli!.log("info", "terminate process (exit code 0)")
|
|
700
710
|
process.exit(0)
|
|
701
|
-
|
|
711
|
+
}
|
|
712
|
+
else {
|
|
713
|
+
cli!.log("info", "terminate process (exit code 1)")
|
|
702
714
|
process.exit(1)
|
|
715
|
+
}
|
|
703
716
|
}
|
|
704
|
-
finishEvents.on("finished", () => {
|
|
705
|
-
shutdown("
|
|
706
|
-
})
|
|
707
|
-
process.on("
|
|
708
|
-
|
|
709
|
-
})
|
|
710
|
-
process.on("SIGUSR1", () => {
|
|
711
|
-
shutdown("SIGUSR1")
|
|
712
|
-
})
|
|
713
|
-
process.on("SIGUSR2", () => {
|
|
714
|
-
shutdown("SIGUSR2")
|
|
715
|
-
})
|
|
716
|
-
process.on("SIGTERM", () => {
|
|
717
|
-
shutdown("SIGTERM")
|
|
718
|
-
})
|
|
717
|
+
finishEvents.on("finished", () => { shutdown("finished") })
|
|
718
|
+
process.on("SIGINT", () => { shutdown("SIGINT") })
|
|
719
|
+
process.on("SIGUSR1", () => { shutdown("SIGUSR1") })
|
|
720
|
+
process.on("SIGUSR2", () => { shutdown("SIGUSR2") })
|
|
721
|
+
process.on("SIGTERM", () => { shutdown("SIGTERM") })
|
|
719
722
|
})().catch((err: Error) => {
|
|
720
723
|
if (cli !== null)
|
|
721
724
|
cli.log("error", err.message)
|