speechflow 1.5.1 → 1.6.0

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 (168) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +191 -170
  3. package/etc/claude.md +83 -46
  4. package/etc/speechflow.yaml +84 -84
  5. package/package.json +3 -3
  6. package/speechflow-cli/dst/speechflow-node-a2a-compressor.d.ts +1 -1
  7. package/speechflow-cli/dst/speechflow-node-a2a-compressor.js +3 -3
  8. package/speechflow-cli/dst/speechflow-node-a2a-compressor.js.map +1 -1
  9. package/speechflow-cli/dst/speechflow-node-a2a-expander.d.ts +1 -1
  10. package/speechflow-cli/dst/speechflow-node-a2a-expander.js +3 -3
  11. package/speechflow-cli/dst/speechflow-node-a2a-expander.js.map +1 -1
  12. package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.d.ts +1 -1
  13. package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js +3 -3
  14. package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js.map +1 -1
  15. package/speechflow-cli/dst/speechflow-node-a2a-filler.d.ts +1 -1
  16. package/speechflow-cli/dst/speechflow-node-a2a-filler.js +3 -3
  17. package/speechflow-cli/dst/speechflow-node-a2a-filler.js.map +1 -1
  18. package/speechflow-cli/dst/speechflow-node-a2a-gain.d.ts +1 -1
  19. package/speechflow-cli/dst/speechflow-node-a2a-gain.js +3 -3
  20. package/speechflow-cli/dst/speechflow-node-a2a-gain.js.map +1 -1
  21. package/speechflow-cli/dst/speechflow-node-a2a-gender.d.ts +1 -1
  22. package/speechflow-cli/dst/speechflow-node-a2a-gender.js +3 -3
  23. package/speechflow-cli/dst/speechflow-node-a2a-gender.js.map +1 -1
  24. package/speechflow-cli/dst/speechflow-node-a2a-meter.d.ts +1 -1
  25. package/speechflow-cli/dst/speechflow-node-a2a-meter.js +3 -3
  26. package/speechflow-cli/dst/speechflow-node-a2a-meter.js.map +1 -1
  27. package/speechflow-cli/dst/speechflow-node-a2a-mute.d.ts +1 -1
  28. package/speechflow-cli/dst/speechflow-node-a2a-mute.js +3 -3
  29. package/speechflow-cli/dst/speechflow-node-a2a-mute.js.map +1 -1
  30. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.d.ts +1 -1
  31. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js +3 -3
  32. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js.map +1 -1
  33. package/speechflow-cli/dst/speechflow-node-a2a-speex.d.ts +1 -1
  34. package/speechflow-cli/dst/speechflow-node-a2a-speex.js +3 -3
  35. package/speechflow-cli/dst/speechflow-node-a2a-speex.js.map +1 -1
  36. package/speechflow-cli/dst/speechflow-node-a2a-vad.d.ts +1 -1
  37. package/speechflow-cli/dst/speechflow-node-a2a-vad.js +3 -3
  38. package/speechflow-cli/dst/speechflow-node-a2a-vad.js.map +1 -1
  39. package/speechflow-cli/dst/speechflow-node-a2a-wav.d.ts +1 -1
  40. package/speechflow-cli/dst/speechflow-node-a2a-wav.js +3 -3
  41. package/speechflow-cli/dst/speechflow-node-a2a-wav.js.map +1 -1
  42. package/speechflow-cli/dst/speechflow-node-a2t-amazon.d.ts +18 -0
  43. package/speechflow-cli/dst/speechflow-node-a2t-amazon.js +312 -0
  44. package/speechflow-cli/dst/speechflow-node-a2t-amazon.js.map +1 -0
  45. package/speechflow-cli/dst/speechflow-node-a2t-awstranscribe.d.ts +1 -1
  46. package/speechflow-cli/dst/speechflow-node-a2t-awstranscribe.js +3 -3
  47. package/speechflow-cli/dst/speechflow-node-a2t-awstranscribe.js.map +1 -1
  48. package/speechflow-cli/dst/speechflow-node-a2t-deepgram.d.ts +1 -1
  49. package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js +3 -3
  50. package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js.map +1 -1
  51. package/speechflow-cli/dst/speechflow-node-a2t-openai.d.ts +19 -0
  52. package/speechflow-cli/dst/speechflow-node-a2t-openai.js +351 -0
  53. package/speechflow-cli/dst/speechflow-node-a2t-openai.js.map +1 -0
  54. package/speechflow-cli/dst/speechflow-node-a2t-openaitranscribe.d.ts +1 -1
  55. package/speechflow-cli/dst/speechflow-node-a2t-openaitranscribe.js +5 -5
  56. package/speechflow-cli/dst/speechflow-node-a2t-openaitranscribe.js.map +1 -1
  57. package/speechflow-cli/dst/speechflow-node-t2a-amazon.d.ts +16 -0
  58. package/speechflow-cli/dst/speechflow-node-t2a-amazon.js +204 -0
  59. package/speechflow-cli/dst/speechflow-node-t2a-amazon.js.map +1 -0
  60. package/speechflow-cli/dst/speechflow-node-t2a-awspolly.d.ts +1 -1
  61. package/speechflow-cli/dst/speechflow-node-t2a-awspolly.js +5 -5
  62. package/speechflow-cli/dst/speechflow-node-t2a-awspolly.js.map +1 -1
  63. package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.d.ts +1 -1
  64. package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js +5 -5
  65. package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js.map +1 -1
  66. package/speechflow-cli/dst/speechflow-node-t2a-kokoro.d.ts +1 -1
  67. package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js +5 -5
  68. package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js.map +1 -1
  69. package/speechflow-cli/dst/speechflow-node-t2t-amazon.d.ts +13 -0
  70. package/speechflow-cli/dst/speechflow-node-t2t-amazon.js +175 -0
  71. package/speechflow-cli/dst/speechflow-node-t2t-amazon.js.map +1 -0
  72. package/speechflow-cli/dst/speechflow-node-t2t-awstranslate.d.ts +1 -1
  73. package/speechflow-cli/dst/speechflow-node-t2t-awstranslate.js +3 -3
  74. package/speechflow-cli/dst/speechflow-node-t2t-awstranslate.js.map +1 -1
  75. package/speechflow-cli/dst/speechflow-node-t2t-deepl.d.ts +1 -1
  76. package/speechflow-cli/dst/speechflow-node-t2t-deepl.js +3 -3
  77. package/speechflow-cli/dst/speechflow-node-t2t-deepl.js.map +1 -1
  78. package/speechflow-cli/dst/speechflow-node-t2t-format.d.ts +1 -1
  79. package/speechflow-cli/dst/speechflow-node-t2t-format.js +3 -3
  80. package/speechflow-cli/dst/speechflow-node-t2t-format.js.map +1 -1
  81. package/speechflow-cli/dst/speechflow-node-t2t-google.d.ts +1 -1
  82. package/speechflow-cli/dst/speechflow-node-t2t-google.js +3 -3
  83. package/speechflow-cli/dst/speechflow-node-t2t-google.js.map +1 -1
  84. package/speechflow-cli/dst/speechflow-node-t2t-modify.d.ts +11 -0
  85. package/speechflow-cli/dst/speechflow-node-t2t-modify.js +111 -0
  86. package/speechflow-cli/dst/speechflow-node-t2t-modify.js.map +1 -0
  87. package/speechflow-cli/dst/speechflow-node-t2t-ollama.d.ts +1 -1
  88. package/speechflow-cli/dst/speechflow-node-t2t-ollama.js +3 -3
  89. package/speechflow-cli/dst/speechflow-node-t2t-ollama.js.map +1 -1
  90. package/speechflow-cli/dst/speechflow-node-t2t-openai.d.ts +1 -1
  91. package/speechflow-cli/dst/speechflow-node-t2t-openai.js +3 -3
  92. package/speechflow-cli/dst/speechflow-node-t2t-openai.js.map +1 -1
  93. package/speechflow-cli/dst/speechflow-node-t2t-sentence.d.ts +1 -1
  94. package/speechflow-cli/dst/speechflow-node-t2t-sentence.js +3 -3
  95. package/speechflow-cli/dst/speechflow-node-t2t-sentence.js.map +1 -1
  96. package/speechflow-cli/dst/speechflow-node-t2t-subtitle.d.ts +1 -1
  97. package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js +3 -3
  98. package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js.map +1 -1
  99. package/speechflow-cli/dst/speechflow-node-t2t-transformers.d.ts +1 -1
  100. package/speechflow-cli/dst/speechflow-node-t2t-transformers.js +3 -3
  101. package/speechflow-cli/dst/speechflow-node-t2t-transformers.js.map +1 -1
  102. package/speechflow-cli/dst/speechflow-node-x2x-filter.d.ts +1 -1
  103. package/speechflow-cli/dst/speechflow-node-x2x-filter.js +3 -3
  104. package/speechflow-cli/dst/speechflow-node-x2x-filter.js.map +1 -1
  105. package/speechflow-cli/dst/speechflow-node-x2x-trace.d.ts +1 -1
  106. package/speechflow-cli/dst/speechflow-node-x2x-trace.js +3 -3
  107. package/speechflow-cli/dst/speechflow-node-x2x-trace.js.map +1 -1
  108. package/speechflow-cli/dst/speechflow-node-xio-device.d.ts +1 -1
  109. package/speechflow-cli/dst/speechflow-node-xio-device.js +3 -3
  110. package/speechflow-cli/dst/speechflow-node-xio-device.js.map +1 -1
  111. package/speechflow-cli/dst/speechflow-node-xio-file.d.ts +1 -1
  112. package/speechflow-cli/dst/speechflow-node-xio-file.js +43 -22
  113. package/speechflow-cli/dst/speechflow-node-xio-file.js.map +1 -1
  114. package/speechflow-cli/dst/speechflow-node-xio-mqtt.d.ts +1 -1
  115. package/speechflow-cli/dst/speechflow-node-xio-mqtt.js +3 -3
  116. package/speechflow-cli/dst/speechflow-node-xio-mqtt.js.map +1 -1
  117. package/speechflow-cli/dst/speechflow-node-xio-websocket.d.ts +1 -1
  118. package/speechflow-cli/dst/speechflow-node-xio-websocket.js +3 -3
  119. package/speechflow-cli/dst/speechflow-node-xio-websocket.js.map +1 -1
  120. package/speechflow-cli/dst/speechflow-utils.js +11 -5
  121. package/speechflow-cli/dst/speechflow-utils.js.map +1 -1
  122. package/speechflow-cli/dst/speechflow.js +13 -7
  123. package/speechflow-cli/dst/speechflow.js.map +1 -1
  124. package/speechflow-cli/package.json +11 -11
  125. package/speechflow-cli/src/speechflow-node-a2a-compressor.ts +2 -2
  126. package/speechflow-cli/src/speechflow-node-a2a-expander.ts +2 -2
  127. package/speechflow-cli/src/speechflow-node-a2a-ffmpeg.ts +2 -2
  128. package/speechflow-cli/src/speechflow-node-a2a-filler.ts +2 -2
  129. package/speechflow-cli/src/speechflow-node-a2a-gain.ts +2 -2
  130. package/speechflow-cli/src/speechflow-node-a2a-gender.ts +2 -2
  131. package/speechflow-cli/src/speechflow-node-a2a-meter.ts +2 -2
  132. package/speechflow-cli/src/speechflow-node-a2a-mute.ts +2 -2
  133. package/speechflow-cli/src/speechflow-node-a2a-rnnoise.ts +2 -2
  134. package/speechflow-cli/src/speechflow-node-a2a-speex.ts +2 -2
  135. package/speechflow-cli/src/speechflow-node-a2a-vad.ts +2 -2
  136. package/speechflow-cli/src/speechflow-node-a2a-wav.ts +2 -2
  137. package/speechflow-cli/src/{speechflow-node-a2t-awstranscribe.ts → speechflow-node-a2t-amazon.ts} +3 -3
  138. package/speechflow-cli/src/speechflow-node-a2t-deepgram.ts +2 -2
  139. package/speechflow-cli/src/{speechflow-node-a2t-openaitranscribe.ts → speechflow-node-a2t-openai.ts} +5 -5
  140. package/speechflow-cli/src/{speechflow-node-t2a-awspolly.ts → speechflow-node-t2a-amazon.ts} +5 -5
  141. package/speechflow-cli/src/speechflow-node-t2a-elevenlabs.ts +4 -4
  142. package/speechflow-cli/src/speechflow-node-t2a-kokoro.ts +4 -4
  143. package/speechflow-cli/src/{speechflow-node-t2t-awstranslate.ts → speechflow-node-t2t-amazon.ts} +3 -3
  144. package/speechflow-cli/src/speechflow-node-t2t-deepl.ts +2 -2
  145. package/speechflow-cli/src/speechflow-node-t2t-format.ts +2 -2
  146. package/speechflow-cli/src/speechflow-node-t2t-google.ts +2 -2
  147. package/speechflow-cli/src/speechflow-node-t2t-modify.ts +84 -0
  148. package/speechflow-cli/src/speechflow-node-t2t-ollama.ts +2 -2
  149. package/speechflow-cli/src/speechflow-node-t2t-openai.ts +2 -2
  150. package/speechflow-cli/src/speechflow-node-t2t-sentence.ts +2 -2
  151. package/speechflow-cli/src/speechflow-node-t2t-subtitle.ts +7 -7
  152. package/speechflow-cli/src/speechflow-node-t2t-transformers.ts +2 -2
  153. package/speechflow-cli/src/speechflow-node-x2x-filter.ts +2 -2
  154. package/speechflow-cli/src/speechflow-node-x2x-trace.ts +2 -2
  155. package/speechflow-cli/src/speechflow-node-xio-device.ts +2 -2
  156. package/speechflow-cli/src/speechflow-node-xio-file.ts +43 -21
  157. package/speechflow-cli/src/speechflow-node-xio-mqtt.ts +2 -2
  158. package/speechflow-cli/src/speechflow-node-xio-websocket.ts +2 -2
  159. package/speechflow-cli/src/speechflow-utils.ts +11 -5
  160. package/speechflow-cli/src/speechflow.ts +13 -7
  161. package/speechflow-ui-db/package.json +3 -3
  162. package/speechflow-ui-st/dst/app-font-fa-brands-400.woff2 +0 -0
  163. package/speechflow-ui-st/dst/app-font-fa-regular-400.woff2 +0 -0
  164. package/speechflow-ui-st/dst/app-font-fa-solid-900.woff2 +0 -0
  165. package/speechflow-ui-st/dst/app-font-fa-v4compatibility.woff2 +0 -0
  166. package/speechflow-ui-st/dst/index.css +2 -2
  167. package/speechflow-ui-st/dst/index.js +32 -33
  168. package/speechflow-ui-st/package.json +4 -4
@@ -19,10 +19,10 @@ import {
19
19
  import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
20
20
  import * as utils from "./speechflow-utils"
21
21
 
22
- /* SpeechFlow node for AWS Polly text-to-speech conversion */
23
- export default class SpeechFlowNodeAWSPolly extends SpeechFlowNode {
22
+ /* SpeechFlow node for Amazon Polly text-to-speech conversion */
23
+ export default class SpeechFlowNodeT2AAmazon extends SpeechFlowNode {
24
24
  /* declare official node name */
25
- public static name = "awspolly"
25
+ public static name = "t2a-amazon"
26
26
 
27
27
  /* internal state */
28
28
  private client: PollyClient | null = null
@@ -114,10 +114,10 @@ export default class SpeechFlowNodeAWSPolly extends SpeechFlowNode {
114
114
 
115
115
  /* establish resampler from AWS Polly's maximum 16Khz output
116
116
  (for PCM output) to our standard audio sample rate (48KHz) */
117
- if (!SpeechFlowNodeAWSPolly.speexInitialized) {
117
+ if (!SpeechFlowNodeT2AAmazon.speexInitialized) {
118
118
  /* at least once initialize resampler */
119
119
  await SpeexResampler.initPromise
120
- SpeechFlowNodeAWSPolly.speexInitialized = true
120
+ SpeechFlowNodeT2AAmazon.speexInitialized = true
121
121
  }
122
122
  this.resampler = new SpeexResampler(1, 16000, this.config.audioSampleRate, 7)
123
123
 
@@ -16,9 +16,9 @@ import SpeexResampler from "speex-resampler"
16
16
  import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
17
17
 
18
18
  /* SpeechFlow node for Elevenlabs text-to-speech conversion */
19
- export default class SpeechFlowNodeElevenlabs extends SpeechFlowNode {
19
+ export default class SpeechFlowNodeT2AElevenlabs extends SpeechFlowNode {
20
20
  /* declare official node name */
21
- public static name = "elevenlabs"
21
+ public static name = "t2a-elevenlabs"
22
22
 
23
23
  /* internal state */
24
24
  private elevenlabs: ElevenLabs.ElevenLabsClient | null = null
@@ -131,10 +131,10 @@ export default class SpeechFlowNodeElevenlabs extends SpeechFlowNode {
131
131
 
132
132
  /* establish resampler from ElevenLabs's maximum 24Khz
133
133
  output to our standard audio sample rate (48KHz) */
134
- if (!SpeechFlowNodeElevenlabs.speexInitialized) {
134
+ if (!SpeechFlowNodeT2AElevenlabs.speexInitialized) {
135
135
  /* at least once initialize resampler */
136
136
  await SpeexResampler.initPromise
137
- SpeechFlowNodeElevenlabs.speexInitialized = true
137
+ SpeechFlowNodeT2AElevenlabs.speexInitialized = true
138
138
  }
139
139
  this.resampler = new SpeexResampler(1, maxSampleRate, this.config.audioSampleRate, 7)
140
140
 
@@ -16,9 +16,9 @@ import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
16
16
  import * as utils from "./speechflow-utils"
17
17
 
18
18
  /* SpeechFlow node for Kokoro text-to-speech conversion */
19
- export default class SpeechFlowNodeKokoro extends SpeechFlowNode {
19
+ export default class SpeechFlowNodeT2AKokoro extends SpeechFlowNode {
20
20
  /* declare official node name */
21
- public static name = "kokoro"
21
+ public static name = "t2a-kokoro"
22
22
 
23
23
  /* internal state */
24
24
  private kokoro: KokoroTTS | null = null
@@ -82,9 +82,9 @@ export default class SpeechFlowNodeKokoro extends SpeechFlowNode {
82
82
 
83
83
  /* establish resampler from Kokoro's maximum 24Khz
84
84
  output to our standard audio sample rate (48KHz) */
85
- if (!SpeechFlowNodeKokoro.speexInitialized) {
85
+ if (!SpeechFlowNodeT2AKokoro.speexInitialized) {
86
86
  /* at least once initialize resampler */
87
- SpeechFlowNodeKokoro.speexInitialized = true
87
+ SpeechFlowNodeT2AKokoro.speexInitialized = true
88
88
  await SpeexResampler.initPromise
89
89
  }
90
90
  this.resampler = new SpeexResampler(1, 24000, this.config.audioSampleRate, 7)
@@ -14,10 +14,10 @@ import { TranslateClient, TranslateTextCommand } from "@aws-sdk/client-translate
14
14
  import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
15
15
  import * as utils from "./speechflow-utils"
16
16
 
17
- /* SpeechFlow node for AWS Translate text-to-text translations */
18
- export default class SpeechFlowNodeAWSTranslate extends SpeechFlowNode {
17
+ /* SpeechFlow node for Amazon Translate text-to-text translations */
18
+ export default class SpeechFlowNodeT2TAmazon extends SpeechFlowNode {
19
19
  /* declare official node name */
20
- public static name = "awstranslate"
20
+ public static name = "t2t-amazon"
21
21
 
22
22
  /* internal state */
23
23
  private client: TranslateClient | null = null
@@ -15,9 +15,9 @@ import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
15
15
  import * as utils from "./speechflow-utils"
16
16
 
17
17
  /* SpeechFlow node for DeepL text-to-text translations */
18
- export default class SpeechFlowNodeDeepL extends SpeechFlowNode {
18
+ export default class SpeechFlowNodeT2TDeepL extends SpeechFlowNode {
19
19
  /* declare official node name */
20
- public static name = "deepl"
20
+ public static name = "t2t-deepl"
21
21
 
22
22
  /* internal state */
23
23
  private deepl: DeepL.Translator | null = null
@@ -14,9 +14,9 @@ import wrapText from "wrap-text"
14
14
  import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
15
15
 
16
16
  /* SpeechFlow node for text-to-text formatting */
17
- export default class SpeechFlowNodeFormat extends SpeechFlowNode {
17
+ export default class SpeechFlowNodeT2TFormat extends SpeechFlowNode {
18
18
  /* declare official node name */
19
- public static name = "format"
19
+ public static name = "t2t-format"
20
20
 
21
21
  /* construct node */
22
22
  constructor (id: string, cfg: { [ id: string ]: any }, opts: { [ id: string ]: any }, args: any[]) {
@@ -16,9 +16,9 @@ import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
16
16
  import * as utils from "./speechflow-utils"
17
17
 
18
18
  /* SpeechFlow node for Google Translate text-to-text translations */
19
- export default class SpeechFlowNodeGoogle extends SpeechFlowNode {
19
+ export default class SpeechFlowNodeT2TGoogle extends SpeechFlowNode {
20
20
  /* declare official node name */
21
- public static name = "google"
21
+ public static name = "t2t-google"
22
22
 
23
23
  /* internal state */
24
24
  private client: TranslationServiceClient | null = null
@@ -0,0 +1,84 @@
1
+ /*
2
+ ** SpeechFlow - Speech Processing Flow Graph
3
+ ** Copyright (c) 2024-2025 Dr. Ralf S. Engelschall <rse@engelschall.com>
4
+ ** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
5
+ */
6
+
7
+ /* standard dependencies */
8
+ import Stream from "node:stream"
9
+
10
+ /* internal dependencies */
11
+ import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
12
+ import * as utils from "./speechflow-utils"
13
+
14
+ /* SpeechFlow node for text-to-text modification via regex */
15
+ export default class SpeechFlowNodeT2TModify extends SpeechFlowNode {
16
+ /* declare official node name */
17
+ public static name = "t2t-modify"
18
+
19
+ /* construct node */
20
+ constructor (id: string, cfg: { [ id: string ]: any }, opts: { [ id: string ]: any }, args: any[]) {
21
+ super(id, cfg, opts, args)
22
+
23
+ /* declare node configuration parameters */
24
+ this.configure({
25
+ match: { type: "string", val: "" },
26
+ replace: { type: "string", val: "" }
27
+ })
28
+
29
+ /* declare node input/output format */
30
+ this.input = "text"
31
+ this.output = "text"
32
+ }
33
+
34
+ /* open node */
35
+ async open () {
36
+ /* validate parameters */
37
+ if (this.params.match === "")
38
+ throw new Error("match parameter cannot be empty")
39
+
40
+ /* compile regex pattern */
41
+ const regex = utils.run("compiling regex pattern",
42
+ () => new RegExp(this.params.match, "g"))
43
+
44
+ /* apply regex-based modification */
45
+ const modify = (text: string): string =>
46
+ text.replace(regex, this.params.replace)
47
+
48
+ /* establish a duplex stream and connect it to text modification */
49
+ this.stream = new Stream.Transform({
50
+ readableObjectMode: true,
51
+ writableObjectMode: true,
52
+ decodeStrings: false,
53
+ highWaterMark: 1,
54
+ transform (chunk: SpeechFlowChunk, encoding, callback) {
55
+ if (Buffer.isBuffer(chunk.payload))
56
+ callback(new Error("invalid chunk payload type"))
57
+ else if (chunk.payload === "") {
58
+ this.push(chunk)
59
+ callback()
60
+ }
61
+ else {
62
+ const payload = modify(chunk.payload)
63
+ const chunkNew = chunk.clone()
64
+ chunkNew.payload = payload
65
+ this.push(chunkNew)
66
+ callback()
67
+ }
68
+ },
69
+ final (callback) {
70
+ this.push(null)
71
+ callback()
72
+ }
73
+ })
74
+ }
75
+
76
+ /* close node */
77
+ async close () {
78
+ /* close stream */
79
+ if (this.stream !== null) {
80
+ this.stream.destroy()
81
+ this.stream = null
82
+ }
83
+ }
84
+ }
@@ -19,9 +19,9 @@ type ConfigEntry = { systemPrompt: string, chat: Array<{ role: string, content:
19
19
  type Config = { [ key: string ]: ConfigEntry }
20
20
 
21
21
  /* SpeechFlow node for Ollama text-to-text translation */
22
- export default class SpeechFlowNodeOllama extends SpeechFlowNode {
22
+ export default class SpeechFlowNodeT2TOllama extends SpeechFlowNode {
23
23
  /* declare official node name */
24
- public static name = "ollama"
24
+ public static name = "t2t-ollama"
25
25
 
26
26
  /* internal state */
27
27
  private ollama: Ollama | null = null
@@ -19,9 +19,9 @@ type ConfigEntry = { systemPrompt: string, chat: OpenAI.ChatCompletionMessagePar
19
19
  type Config = { [ key: string ]: ConfigEntry }
20
20
 
21
21
  /* SpeechFlow node for OpenAI/GPT text-to-text translation */
22
- export default class SpeechFlowNodeOpenAI extends SpeechFlowNode {
22
+ export default class SpeechFlowNodeT2TOpenAI extends SpeechFlowNode {
23
23
  /* declare official node name */
24
- public static name = "openai"
24
+ public static name = "t2t-openai"
25
25
 
26
26
  /* internal state */
27
27
  private openai: OpenAI | null = null
@@ -24,9 +24,9 @@ type TextQueueElement = {
24
24
  }
25
25
 
26
26
  /* SpeechFlow node for sentence splitting */
27
- export default class SpeechFlowNodeSentence extends SpeechFlowNode {
27
+ export default class SpeechFlowNodeT2TSentence extends SpeechFlowNode {
28
28
  /* declare official node name */
29
- public static name = "sentence"
29
+ public static name = "t2t-sentence"
30
30
 
31
31
  /* internal state */
32
32
  private queue = new utils.Queue<TextQueueElement>()
@@ -30,9 +30,9 @@ type wsPeerInfo = {
30
30
  }
31
31
 
32
32
  /* SpeechFlow node for subtitle (text-to-text) "translations" */
33
- export default class SpeechFlowNodeSubtitle extends SpeechFlowNode {
33
+ export default class SpeechFlowNodeT2TSubtitle extends SpeechFlowNode {
34
34
  /* declare official node name */
35
- public static name = "subtitle"
35
+ public static name = "t2t-subtitle"
36
36
 
37
37
  /* internal state */
38
38
  private sequenceNo = 1
@@ -44,11 +44,11 @@ export default class SpeechFlowNodeSubtitle extends SpeechFlowNode {
44
44
 
45
45
  /* declare node configuration parameters */
46
46
  this.configure({
47
- format: { type: "string", pos: 0, val: "srt", match: /^(?:srt|vtt)$/ },
48
- words: { type: "boolean", val: false },
49
- mode: { type: "string", val: "export", match: /^(?:export|render)$/ },
50
- addr: { type: "string", val: "127.0.0.1" },
51
- port: { type: "number", val: 8585 }
47
+ format: { type: "string", pos: 0, val: "srt", match: /^(?:srt|vtt)$/ },
48
+ words: { type: "boolean", val: false },
49
+ mode: { type: "string", val: "export", match: /^(?:export|render)$/ },
50
+ addr: { type: "string", val: "127.0.0.1" },
51
+ port: { type: "number", val: 8585 }
52
52
  })
53
53
 
54
54
  /* declare node input/output format */
@@ -20,9 +20,9 @@ type ConfigEntry = { systemPrompt: string, chat: Array<{ role: string, content:
20
20
  type Config = { [ key: string ]: ConfigEntry }
21
21
 
22
22
  /* SpeechFlow node for Transformers text-to-text translation */
23
- export default class SpeechFlowNodeTransformers extends SpeechFlowNode {
23
+ export default class SpeechFlowNodeT2TTransformers extends SpeechFlowNode {
24
24
  /* declare official node name */
25
- public static name = "transformers"
25
+ public static name = "t2t-transformers"
26
26
 
27
27
  /* internal state */
28
28
  private translator: Transformers.TranslationPipeline | null = null
@@ -12,9 +12,9 @@ import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
12
12
  import * as utils from "./speechflow-utils"
13
13
 
14
14
  /* SpeechFlow node for data flow filtering (based on meta information) */
15
- export default class SpeechFlowNodeFilter extends SpeechFlowNode {
15
+ export default class SpeechFlowNodeX2XFilter extends SpeechFlowNode {
16
16
  /* declare official node name */
17
- public static name = "filter"
17
+ public static name = "x2x-filter"
18
18
 
19
19
  /* cached regular expression instance */
20
20
  private cachedRegExp = new utils.CachedRegExp()
@@ -14,9 +14,9 @@ import { Duration } from "luxon"
14
14
  import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
15
15
 
16
16
  /* SpeechFlow node for data flow tracing */
17
- export default class SpeechFlowNodeTrace extends SpeechFlowNode {
17
+ export default class SpeechFlowNodeX2XTrace extends SpeechFlowNode {
18
18
  /* declare official node name */
19
- public static name = "trace"
19
+ public static name = "x2x-trace"
20
20
 
21
21
  /* construct node */
22
22
  constructor (id: string, cfg: { [ id: string ]: any }, opts: { [ id: string ]: any }, args: any[]) {
@@ -15,9 +15,9 @@ import SpeechFlowNode from "./speechflow-node"
15
15
  import * as utils from "./speechflow-utils"
16
16
 
17
17
  /* SpeechFlow node for device access */
18
- export default class SpeechFlowNodeDevice extends SpeechFlowNode {
18
+ export default class SpeechFlowNodeXIODevice extends SpeechFlowNode {
19
19
  /* declare official node name */
20
- public static name = "device"
20
+ public static name = "xio-device"
21
21
 
22
22
  /* internal state */
23
23
  private io: PortAudio.IoStreamRead
@@ -13,9 +13,9 @@ import SpeechFlowNode from "./speechflow-node"
13
13
  import * as utils from "./speechflow-utils"
14
14
 
15
15
  /* SpeechFlow node for file access */
16
- export default class SpeechFlowNodeFile extends SpeechFlowNode {
16
+ export default class SpeechFlowNodeXIOFile extends SpeechFlowNode {
17
17
  /* declare official node name */
18
- public static name = "file"
18
+ public static name = "xio-file"
19
19
 
20
20
  /* construct node */
21
21
  constructor (id: string, cfg: { [ id: string ]: any }, opts: { [ id: string ]: any }, args: any[]) {
@@ -59,6 +59,28 @@ export default class SpeechFlowNodeFile extends SpeechFlowNode {
59
59
  if (this.params.path === "")
60
60
  throw new Error("required parameter \"path\" has to be given")
61
61
 
62
+ /* utility function: create a writable stream as chunker that
63
+ writes to process.stdout but properly handles finish events.
64
+ This ensures the writable side of the composed stream below
65
+ properly signals completion while keeping process.stdout open
66
+ (as it's a global stream that shouldn't be closed by individual nodes). */
67
+ const createStdoutChunker = () => {
68
+ return new Stream.Writable({
69
+ highWaterMark: this.params.type === "audio" ?
70
+ highWaterMarkAudio : highWaterMarkText,
71
+ write (chunk: Buffer | string, encoding, callback) {
72
+ const canContinue = process.stdout.write(chunk, encoding)
73
+ if (canContinue)
74
+ callback()
75
+ else
76
+ process.stdout.once("drain", callback)
77
+ },
78
+ final (callback) {
79
+ callback()
80
+ }
81
+ })
82
+ }
83
+
62
84
  /* dispatch according to mode and path */
63
85
  if (this.params.mode === "rw") {
64
86
  if (this.params.path === "-") {
@@ -145,17 +167,13 @@ export default class SpeechFlowNodeFile extends SpeechFlowNode {
145
167
  else if (this.params.mode === "w") {
146
168
  if (this.params.path === "-") {
147
169
  /* standard I/O */
148
- let chunker: Stream.PassThrough
149
- if (this.params.type === "audio") {
170
+ if (this.params.type === "audio")
150
171
  process.stdout.setEncoding()
151
- chunker = new Stream.PassThrough({ highWaterMark: highWaterMarkAudio })
152
- }
153
- else {
172
+ else
154
173
  process.stdout.setEncoding(this.config.textEncoding)
155
- chunker = new Stream.PassThrough({ highWaterMark: highWaterMarkText })
156
- }
174
+ const chunker = createStdoutChunker()
157
175
  const wrapper = utils.createTransformStreamForWritableSide()
158
- this.stream = Stream.compose(wrapper, chunker, process.stdout)
176
+ this.stream = Stream.compose(wrapper, chunker)
159
177
  }
160
178
  else {
161
179
  /* file I/O */
@@ -178,18 +196,22 @@ export default class SpeechFlowNodeFile extends SpeechFlowNode {
178
196
  async close () {
179
197
  /* shutdown stream */
180
198
  if (this.stream !== null) {
181
- await new Promise<void>((resolve, reject) => {
182
- if (this.stream instanceof Stream.Writable || this.stream instanceof Stream.Duplex) {
183
- this.stream.end((err?: Error) => {
184
- if (err)
185
- reject(err)
186
- else
199
+ await Promise.race([
200
+ new Promise<void>((resolve, reject) => {
201
+ if (this.stream instanceof Stream.Writable || this.stream instanceof Stream.Duplex) {
202
+ if (this.stream.writableEnded || this.stream.destroyed)
187
203
  resolve()
188
- })
189
- }
190
- else
191
- resolve()
192
- })
204
+ else
205
+ this.stream.end((err?: Error) => {
206
+ if (err) reject(err)
207
+ else resolve()
208
+ })
209
+ }
210
+ else
211
+ resolve()
212
+ }),
213
+ new Promise<void>((resolve) => setTimeout(() => resolve(), 5000))
214
+ ])
193
215
  if (this.params.path !== "-")
194
216
  this.stream.destroy()
195
217
  this.stream = null
@@ -16,9 +16,9 @@ import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
16
16
  import * as utils from "./speechflow-utils"
17
17
 
18
18
  /* SpeechFlow node for MQTT networking */
19
- export default class SpeechFlowNodeMQTT extends SpeechFlowNode {
19
+ export default class SpeechFlowNodeXIOMQTT extends SpeechFlowNode {
20
20
  /* declare official node name */
21
- public static name = "mqtt"
21
+ public static name = "xio-mqtt"
22
22
 
23
23
  /* internal state */
24
24
  private broker: MQTT.MqttClient | null = null
@@ -16,9 +16,9 @@ import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
16
16
  import * as utils from "./speechflow-utils"
17
17
 
18
18
  /* SpeechFlow node for Websocket networking */
19
- export default class SpeechFlowNodeWebsocket extends SpeechFlowNode {
19
+ export default class SpeechFlowNodeXIOWebSocket extends SpeechFlowNode {
20
20
  /* declare official node name */
21
- public static name = "websocket"
21
+ public static name = "xio-websocket"
22
22
 
23
23
  /* internal state */
24
24
  private server: ws.WebSocketServer | null = null
@@ -40,7 +40,7 @@ function runFinally (onfinally?: () => void) {
40
40
  if (!onfinally)
41
41
  return
42
42
  try { onfinally() }
43
- catch (_arg: unknown) { /* ignored */ }
43
+ catch (_error: unknown) { /* ignored */ }
44
44
  }
45
45
 
46
46
  /* helper type for ensuring T contains no Promise */
@@ -120,14 +120,14 @@ export function run<T> (
120
120
  let error = ensureError(arg, description)
121
121
  if (oncatch) {
122
122
  try {
123
- return ensurePromise(oncatch(error))
123
+ return oncatch(error)
124
124
  }
125
125
  catch (arg: unknown) {
126
126
  error = ensureError(arg, description)
127
- return Promise.reject(error)
127
+ throw error
128
128
  }
129
129
  }
130
- return Promise.reject(error)
130
+ throw error
131
131
  }).finally(() => {
132
132
  /* asynchronous case (result and error branch) */
133
133
  runFinally(onfinally)
@@ -328,6 +328,11 @@ export function createTransformStreamForReadableSide (type: "text" | "audio", ge
328
328
  decodeStrings: false,
329
329
  highWaterMark: (type === "audio" ? 19200 : 65536),
330
330
  transform (chunk: Buffer | string, encoding, callback) {
331
+ if (chunk === null) {
332
+ this.push(null)
333
+ callback()
334
+ return
335
+ }
331
336
  const timeZero = getTimeZero()
332
337
  const start = DateTime.now().diff(timeZero)
333
338
  let end = start
@@ -335,7 +340,8 @@ export function createTransformStreamForReadableSide (type: "text" | "audio", ge
335
340
  const duration = audioBufferDuration(chunk as Buffer)
336
341
  end = start.plus(duration * 1000)
337
342
  }
338
- const obj = new SpeechFlowChunk(start, end, "final", type, chunk)
343
+ const payload = ensureStreamChunk(type, chunk) as Buffer | string
344
+ const obj = new SpeechFlowChunk(start, end, "final", type, payload)
339
345
  this.push(obj)
340
346
  callback()
341
347
  },
@@ -282,16 +282,17 @@ let debug = false
282
282
  "./speechflow-node-a2a-speex.js",
283
283
  "./speechflow-node-a2a-vad.js",
284
284
  "./speechflow-node-a2a-wav.js",
285
- "./speechflow-node-a2t-awstranscribe.js",
285
+ "./speechflow-node-a2t-amazon.js",
286
286
  "./speechflow-node-a2t-deepgram.js",
287
- "./speechflow-node-a2t-openaitranscribe.js",
288
- "./speechflow-node-t2a-awspolly.js",
287
+ "./speechflow-node-a2t-openai.js",
288
+ "./speechflow-node-t2a-amazon.js",
289
289
  "./speechflow-node-t2a-elevenlabs.js",
290
290
  "./speechflow-node-t2a-kokoro.js",
291
- "./speechflow-node-t2t-awstranslate.js",
291
+ "./speechflow-node-t2t-amazon.js",
292
292
  "./speechflow-node-t2t-deepl.js",
293
293
  "./speechflow-node-t2t-format.js",
294
294
  "./speechflow-node-t2t-google.js",
295
+ "./speechflow-node-t2t-modify.js",
295
296
  "./speechflow-node-t2t-ollama.js",
296
297
  "./speechflow-node-t2t-openai.js",
297
298
  "./speechflow-node-t2t-sentence.js",
@@ -441,9 +442,12 @@ let debug = false
441
442
  cli!.log("error", `creation of node <${id}> failed: ${err}`)
442
443
  process.exit(1)
443
444
  }
444
- const params = Object.keys(node!.params)
445
- .filter((key) => !key.match(/key/))
446
- .map((key) => `${key}: ${JSON.stringify(node.params[key])}`).join(", ")
445
+ const params = Object.keys(node!.params).map((key) => {
446
+ if (key.match(/key/))
447
+ return `${key}: [...]`
448
+ else
449
+ return `${key}: ${JSON.stringify(node.params[key])}`
450
+ }).join(", ")
447
451
  cli!.log("info", `create node <${node!.id}> (${params})`)
448
452
  graphNodes.add(node!)
449
453
  return node!
@@ -706,6 +710,8 @@ let debug = false
706
710
  const peer = ctx.peer
707
711
  wsPeers.delete(peer)
708
712
  ws.removeAllListeners()
713
+ if (ws.readyState === WebSocket.OPEN)
714
+ ws.close()
709
715
  cli!.log("info", `HAPI: WebSocket: disconnect: peer ${peer}`)
710
716
  }
711
717
  }
@@ -37,8 +37,8 @@
37
37
 
38
38
  "@vue/eslint-config-typescript": "14.6.0",
39
39
  "vue-eslint-parser": "10.2.0",
40
- "eslint": "9.34.0",
41
- "@eslint/js": "9.34.0",
40
+ "eslint": "9.35.0",
41
+ "@eslint/js": "9.35.0",
42
42
  "neostandard": "0.12.2",
43
43
  "eslint-plugin-import": "2.32.0",
44
44
  "eslint-plugin-vue": "10.4.0",
@@ -60,7 +60,7 @@
60
60
  "vue-tsc": "3.0.6",
61
61
  "delay-cli": "2.0.0",
62
62
  "cross-env": "10.0.0",
63
- "serve": "14.2.4"
63
+ "serve": "14.2.5"
64
64
  },
65
65
  "overrides": {
66
66
  "@liuli-util/vite-plugin-node": { "vite": ">=6.0.0" }