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.
Files changed (67) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +37 -3
  3. package/dst/speechflow-node-a2a-gender.d.ts +17 -0
  4. package/dst/speechflow-node-a2a-gender.js +272 -0
  5. package/dst/speechflow-node-a2a-gender.js.map +1 -0
  6. package/dst/speechflow-node-a2a-meter.js +2 -2
  7. package/dst/speechflow-node-a2a-meter.js.map +1 -1
  8. package/dst/speechflow-node-a2a-mute.js +1 -0
  9. package/dst/speechflow-node-a2a-mute.js.map +1 -1
  10. package/dst/speechflow-node-a2a-vad.js +47 -63
  11. package/dst/speechflow-node-a2a-vad.js.map +1 -1
  12. package/dst/speechflow-node-a2a-wav.js +145 -122
  13. package/dst/speechflow-node-a2a-wav.js.map +1 -1
  14. package/dst/speechflow-node-a2t-deepgram.js +13 -3
  15. package/dst/speechflow-node-a2t-deepgram.js.map +1 -1
  16. package/dst/speechflow-node-t2a-elevenlabs.js +10 -5
  17. package/dst/speechflow-node-t2a-elevenlabs.js.map +1 -1
  18. package/dst/speechflow-node-t2a-kokoro.js.map +1 -1
  19. package/dst/speechflow-node-t2t-deepl.js.map +1 -1
  20. package/dst/speechflow-node-t2t-format.js.map +1 -1
  21. package/dst/speechflow-node-t2t-ollama.js.map +1 -1
  22. package/dst/speechflow-node-t2t-openai.js.map +1 -1
  23. package/dst/speechflow-node-t2t-subtitle.js.map +1 -1
  24. package/dst/speechflow-node-t2t-transformers.js.map +1 -1
  25. package/dst/speechflow-node-x2x-filter.d.ts +11 -0
  26. package/dst/speechflow-node-x2x-filter.js +113 -0
  27. package/dst/speechflow-node-x2x-filter.js.map +1 -0
  28. package/dst/speechflow-node-x2x-trace.js +24 -10
  29. package/dst/speechflow-node-x2x-trace.js.map +1 -1
  30. package/dst/speechflow-node-xio-device.js +14 -5
  31. package/dst/speechflow-node-xio-device.js.map +1 -1
  32. package/dst/speechflow-node-xio-file.js +58 -27
  33. package/dst/speechflow-node-xio-file.js.map +1 -1
  34. package/dst/speechflow-node-xio-mqtt.js.map +1 -1
  35. package/dst/speechflow-node-xio-websocket.js.map +1 -1
  36. package/dst/speechflow-node.js +1 -0
  37. package/dst/speechflow-node.js.map +1 -1
  38. package/dst/speechflow-utils.d.ts +14 -1
  39. package/dst/speechflow-utils.js +110 -2
  40. package/dst/speechflow-utils.js.map +1 -1
  41. package/dst/speechflow.js +56 -53
  42. package/dst/speechflow.js.map +1 -1
  43. package/etc/speechflow.yaml +51 -24
  44. package/package.json +6 -5
  45. package/src/speechflow-node-a2a-gender.ts +272 -0
  46. package/src/speechflow-node-a2a-meter.ts +3 -3
  47. package/src/speechflow-node-a2a-mute.ts +1 -0
  48. package/src/speechflow-node-a2a-vad.ts +58 -68
  49. package/src/speechflow-node-a2a-wav.ts +128 -91
  50. package/src/speechflow-node-a2t-deepgram.ts +15 -4
  51. package/src/speechflow-node-t2a-elevenlabs.ts +13 -8
  52. package/src/speechflow-node-t2a-kokoro.ts +3 -3
  53. package/src/speechflow-node-t2t-deepl.ts +2 -2
  54. package/src/speechflow-node-t2t-format.ts +2 -2
  55. package/src/speechflow-node-t2t-ollama.ts +2 -2
  56. package/src/speechflow-node-t2t-openai.ts +2 -2
  57. package/src/speechflow-node-t2t-subtitle.ts +1 -1
  58. package/src/speechflow-node-t2t-transformers.ts +2 -2
  59. package/src/speechflow-node-x2x-filter.ts +122 -0
  60. package/src/speechflow-node-x2x-trace.ts +28 -11
  61. package/src/speechflow-node-xio-device.ts +20 -8
  62. package/src/speechflow-node-xio-file.ts +74 -36
  63. package/src/speechflow-node-xio-mqtt.ts +3 -3
  64. package/src/speechflow-node-xio-websocket.ts +1 -1
  65. package/src/speechflow-node.ts +2 -0
  66. package/src/speechflow-utils.ts +81 -2
  67. 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 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"
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 } 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"
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 from "./speechflow-node"
34
- import pkg from "../package.json"
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-wav.js",
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-openai.js",
243
+ "./speechflow-node-t2t-format.js",
243
244
  "./speechflow-node-t2t-ollama.js",
244
- "./speechflow-node-t2t-transformers.js",
245
+ "./speechflow-node-t2t-openai.js",
245
246
  "./speechflow-node-t2t-subtitle.js",
246
- "./speechflow-node-t2t-format.js",
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 "${node.name}" from internal module`)
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 "${node.name}" ` +
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 "${node.name}" from external module "${pkg}"`)
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 "${id}"`)
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}> node failed: ${err.message}`)
367
+ cli!.log("error", `creation of node <${id}> failed: ${err.message}`)
366
368
  else
367
- cli!.log("error", `creation of <${id}> node failed: ${err}`)
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 "${node.id}" (${params})`)
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 "${node1.id}" to node "${node2.id}"`)
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 "${node.id}" requires input but has no input nodes connected`)
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 "${node.id}" requires output but has no output nodes connected`)
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 "${node.id}" cannot be ` +
420
- `connected to ${other.input} input node "${other.id}" (payload is incompatible)`)
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 "${node.id}"`)
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 "${node.id}"`)
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 "${node.id}"`)
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 "${node.id}" still not initialized`)
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 "${other.id}" still not initialized`)
455
- cli!.log("info", `connect stream of node "${node.id}" to stream of node "${other.id}"`)
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 "${node.id}" is neither of Readable nor Duplex type`)
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 "${other.id}" is neither of Writable nor Duplex type`)
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 "${node.id}" still not initialized`)
472
- cli!.log("info", `observe stream of node "${node.id}" for finish event`)
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.stream.on("finish", () => {
475
- activeNodes.delete(node)
476
- cli!.log("info", `stream of node "${node.id}" finished (${activeNodes.size} nodes remaining active)`)
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 "${node.id}" no longer initialized`)
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 "${other.id}" no longer initialized`)
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 "${node.id}" is neither of Readable nor Duplex type`)
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 "${other.id}" is neither of Writable nor Duplex type`)
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 "${node.id}" from stream of node "${other.id}"`)
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 "${node.id}"`)
686
+ cli!.log("info", `close node <${node.id}>`)
678
687
  await node.close().catch((err) => {
679
- cli!.log("warning", `node "${node.id}" failed to close: ${err}`)
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 "${node.id}"`)
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 "${node.id}"`)
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
- else
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("finished")
706
- })
707
- process.on("SIGINT", () => {
708
- shutdown("SIGINT")
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)