speechflow 1.5.0 → 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 (177) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +210 -167
  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 +4 -4
  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 +4 -4
  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 +5 -15
  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 +4 -4
  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 +7 -12
  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 +4 -4
  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 +6 -6
  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 +40 -7
  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 +41 -7
  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 +39 -5
  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 +6 -5
  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 +13 -0
  82. package/speechflow-cli/dst/speechflow-node-t2t-google.js +153 -0
  83. package/speechflow-cli/dst/speechflow-node-t2t-google.js.map +1 -0
  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 +39 -5
  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 +39 -5
  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 +6 -5
  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 +6 -5
  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.d.ts +16 -0
  121. package/speechflow-cli/dst/speechflow-utils.js +140 -1
  122. package/speechflow-cli/dst/speechflow-utils.js.map +1 -1
  123. package/speechflow-cli/dst/speechflow.js +19 -19
  124. package/speechflow-cli/dst/speechflow.js.map +1 -1
  125. package/speechflow-cli/etc/biome.jsonc +2 -1
  126. package/speechflow-cli/etc/oxlint.jsonc +2 -1
  127. package/speechflow-cli/package.json +16 -15
  128. package/speechflow-cli/src/speechflow-node-a2a-compressor.ts +4 -4
  129. package/speechflow-cli/src/speechflow-node-a2a-expander.ts +4 -4
  130. package/speechflow-cli/src/speechflow-node-a2a-ffmpeg.ts +4 -14
  131. package/speechflow-cli/src/speechflow-node-a2a-filler.ts +4 -4
  132. package/speechflow-cli/src/speechflow-node-a2a-gain.ts +2 -2
  133. package/speechflow-cli/src/speechflow-node-a2a-gender.ts +2 -2
  134. package/speechflow-cli/src/speechflow-node-a2a-meter.ts +2 -2
  135. package/speechflow-cli/src/speechflow-node-a2a-mute.ts +2 -2
  136. package/speechflow-cli/src/speechflow-node-a2a-rnnoise.ts +2 -2
  137. package/speechflow-cli/src/speechflow-node-a2a-speex.ts +2 -2
  138. package/speechflow-cli/src/speechflow-node-a2a-vad.ts +2 -2
  139. package/speechflow-cli/src/speechflow-node-a2a-wav.ts +2 -2
  140. package/speechflow-cli/src/{speechflow-node-a2t-awstranscribe.ts → speechflow-node-a2t-amazon.ts} +11 -13
  141. package/speechflow-cli/src/speechflow-node-a2t-deepgram.ts +4 -4
  142. package/speechflow-cli/src/{speechflow-node-a2t-openaitranscribe.ts → speechflow-node-a2t-openai.ts} +7 -7
  143. package/speechflow-cli/src/{speechflow-node-t2a-awspolly.ts → speechflow-node-t2a-amazon.ts} +8 -8
  144. package/speechflow-cli/src/speechflow-node-t2a-elevenlabs.ts +4 -4
  145. package/speechflow-cli/src/speechflow-node-t2a-kokoro.ts +7 -6
  146. package/speechflow-cli/src/{speechflow-node-t2t-awstranslate.ts → speechflow-node-t2t-amazon.ts} +6 -5
  147. package/speechflow-cli/src/speechflow-node-t2t-deepl.ts +5 -4
  148. package/speechflow-cli/src/speechflow-node-t2t-format.ts +2 -2
  149. package/speechflow-cli/src/speechflow-node-t2t-google.ts +133 -0
  150. package/speechflow-cli/src/speechflow-node-t2t-modify.ts +84 -0
  151. package/speechflow-cli/src/speechflow-node-t2t-ollama.ts +5 -4
  152. package/speechflow-cli/src/speechflow-node-t2t-openai.ts +5 -4
  153. package/speechflow-cli/src/speechflow-node-t2t-sentence.ts +2 -2
  154. package/speechflow-cli/src/speechflow-node-t2t-subtitle.ts +10 -9
  155. package/speechflow-cli/src/speechflow-node-t2t-transformers.ts +5 -4
  156. package/speechflow-cli/src/speechflow-node-x2x-filter.ts +2 -2
  157. package/speechflow-cli/src/speechflow-node-x2x-trace.ts +2 -2
  158. package/speechflow-cli/src/speechflow-node-xio-device.ts +2 -2
  159. package/speechflow-cli/src/speechflow-node-xio-file.ts +43 -21
  160. package/speechflow-cli/src/speechflow-node-xio-mqtt.ts +2 -2
  161. package/speechflow-cli/src/speechflow-node-xio-websocket.ts +2 -2
  162. package/speechflow-cli/src/speechflow-utils.ts +196 -1
  163. package/speechflow-cli/src/speechflow.ts +22 -22
  164. package/speechflow-ui-db/dst/app-font-fa-brands-400.woff2 +0 -0
  165. package/speechflow-ui-db/dst/app-font-fa-regular-400.woff2 +0 -0
  166. package/speechflow-ui-db/dst/app-font-fa-solid-900.woff2 +0 -0
  167. package/speechflow-ui-db/dst/app-font-fa-v4compatibility.woff2 +0 -0
  168. package/speechflow-ui-db/dst/index.css +2 -2
  169. package/speechflow-ui-db/dst/index.js +37 -38
  170. package/speechflow-ui-db/package.json +10 -10
  171. package/speechflow-ui-st/dst/app-font-fa-brands-400.woff2 +0 -0
  172. package/speechflow-ui-st/dst/app-font-fa-regular-400.woff2 +0 -0
  173. package/speechflow-ui-st/dst/app-font-fa-solid-900.woff2 +0 -0
  174. package/speechflow-ui-st/dst/app-font-fa-v4compatibility.woff2 +0 -0
  175. package/speechflow-ui-st/dst/index.css +2 -2
  176. package/speechflow-ui-st/dst/index.js +32 -33
  177. package/speechflow-ui-st/package.json +11 -11
@@ -14,9 +14,9 @@ import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
14
14
  import * as utils from "./speechflow-utils"
15
15
 
16
16
  /* SpeechFlow node for RNNoise based noise suppression in audio-to-audio passing */
17
- export default class SpeechFlowNodeRNNoise extends SpeechFlowNode {
17
+ export default class SpeechFlowNodeA2ARNNoise extends SpeechFlowNode {
18
18
  /* declare official node name */
19
- public static name = "rnnoise"
19
+ public static name = "a2a-rnnoise"
20
20
 
21
21
  /* internal state */
22
22
  private destroyed = false
@@ -17,9 +17,9 @@ import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
17
17
  import * as utils from "./speechflow-utils"
18
18
 
19
19
  /* SpeechFlow node for Speex based noise suppression in audio-to-audio passing */
20
- export default class SpeechFlowNodeSpeex extends SpeechFlowNode {
20
+ export default class SpeechFlowNodeA2ASpeex extends SpeechFlowNode {
21
21
  /* declare official node name */
22
- public static name = "speex"
22
+ public static name = "a2a-speex"
23
23
 
24
24
  /* internal state */
25
25
  private destroyed = false
@@ -30,9 +30,9 @@ type AudioQueueElement = {
30
30
  }
31
31
 
32
32
  /* SpeechFlow node for VAD speech-to-speech processing */
33
- export default class SpeechFlowNodeVAD extends SpeechFlowNode {
33
+ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
34
34
  /* declare official node name */
35
- public static name = "vad"
35
+ public static name = "a2a-vad"
36
36
 
37
37
  /* internal state */
38
38
  private vad: RealTimeVAD | null = null
@@ -83,9 +83,9 @@ const readWavHeader = (buffer: Buffer) => {
83
83
  }
84
84
 
85
85
  /* SpeechFlow node for WAV format conversion */
86
- export default class SpeechFlowNodeWAV extends SpeechFlowNode {
86
+ export default class SpeechFlowNodeA2AWAV extends SpeechFlowNode {
87
87
  /* declare official node name */
88
- public static name = "wav"
88
+ public static name = "a2a-wav"
89
89
 
90
90
  /* construct node */
91
91
  constructor (id: string, cfg: { [ id: string ]: any }, opts: { [ id: string ]: any }, args: any[]) {
@@ -62,10 +62,10 @@ class AsyncQueue<T> {
62
62
  }
63
63
  }
64
64
 
65
- /* SpeechFlow node for AWS Transcribe speech-to-text conversion */
66
- export default class SpeechFlowNodeAWSTranscribe extends SpeechFlowNode {
65
+ /* SpeechFlow node for Amazon Transcribe speech-to-text conversion */
66
+ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
67
67
  /* declare official node name */
68
- public static name = "awstranscribe"
68
+ public static name = "a2t-amazon"
69
69
 
70
70
  /* internal state */
71
71
  private client: TranscribeStreamingClient | null = null
@@ -224,8 +224,8 @@ export default class SpeechFlowNodeAWSTranscribe extends SpeechFlowNode {
224
224
  if (chunk.meta.size > 0)
225
225
  metastore.store(chunk.timestampStart, chunk.timestampEnd, chunk.meta)
226
226
  audioQueue.push(new Uint8Array(chunk.payload)) /* intentionally discard all time information */
227
- ensureAudioStreamActive().catch((err) => {
228
- self.log("error", `failed to start audio stream: ${err}`)
227
+ ensureAudioStreamActive().catch((error: unknown) => {
228
+ self.log("error", `failed to start audio stream: ${utils.ensureError(error).message}`)
229
229
  })
230
230
  }
231
231
  callback()
@@ -249,9 +249,9 @@ export default class SpeechFlowNodeAWSTranscribe extends SpeechFlowNode {
249
249
  self.log("debug", `received data (${chunk.payload.length} bytes): "${chunk.payload}"`)
250
250
  this.push(chunk)
251
251
  }
252
- }).catch((error) => {
252
+ }).catch((error: unknown) => {
253
253
  if (!self.destroyed)
254
- self.log("error", `queue read error: ${error.message}`)
254
+ self.log("error", `queue read error: ${utils.ensureError(error).message}`)
255
255
  })
256
256
  },
257
257
  final (callback) {
@@ -259,12 +259,10 @@ export default class SpeechFlowNodeAWSTranscribe extends SpeechFlowNode {
259
259
  callback()
260
260
  return
261
261
  }
262
- try {
263
- self.client.destroy()
264
- }
265
- catch (error) {
266
- self.log("warning", `error closing Amazon Transcribe connection: ${error}`)
267
- }
262
+ utils.run(
263
+ () => self.client!.destroy(),
264
+ (error: Error) => self.log("warning", `error closing Amazon Transcribe connection: ${error}`)
265
+ )
268
266
  audioQueue.push(null) /* do not push null to stream, let Amazon Transcribe do it */
269
267
  audioQueue.destroy()
270
268
  callback()
@@ -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 Deepgram speech-to-text conversion */
19
- export default class SpeechFlowNodeDeepgram extends SpeechFlowNode {
19
+ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
20
20
  /* declare official node name */
21
- public static name = "deepgram"
21
+ public static name = "a2t-deepgram"
22
22
 
23
23
  /* internal state */
24
24
  private dg: Deepgram.LiveClient | null = null
@@ -232,9 +232,9 @@ export default class SpeechFlowNodeDeepgram extends SpeechFlowNode {
232
232
  self.log("debug", `received data (${chunk.payload.length} bytes)`)
233
233
  this.push(chunk)
234
234
  }
235
- }).catch((error) => {
235
+ }).catch((error: unknown) => {
236
236
  if (!self.destroyed)
237
- self.log("error", `queue read error: ${error.message}`)
237
+ self.log("error", `queue read error: ${utils.ensureError(error).message}`)
238
238
  })
239
239
  },
240
240
  final (callback) {
@@ -17,10 +17,10 @@ import ws from "ws"
17
17
  import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
18
18
  import * as utils from "./speechflow-utils"
19
19
 
20
- /* SpeechFlow node for OpenAI Transcribe speech-to-text conversion */
21
- export default class SpeechFlowNodeOpenAITranscribe extends SpeechFlowNode {
20
+ /* SpeechFlow node for OpenAI speech-to-text conversion */
21
+ export default class SpeechFlowNodeA2TOpenAI extends SpeechFlowNode {
22
22
  /* declare official node name */
23
- public static name = "openaitranscribe"
23
+ public static name = "a2t-openai"
24
24
 
25
25
  /* internal state */
26
26
  private static speexInitialized = false
@@ -71,10 +71,10 @@ export default class SpeechFlowNodeOpenAITranscribe extends SpeechFlowNode {
71
71
 
72
72
  /* establish resampler from our standard audio sample rate (48Khz)
73
73
  to OpenAI's maximum 24Khz input sample rate */
74
- if (!SpeechFlowNodeOpenAITranscribe.speexInitialized) {
74
+ if (!SpeechFlowNodeA2TOpenAI.speexInitialized) {
75
75
  /* at least once initialize resampler */
76
76
  await SpeexResampler.initPromise
77
- SpeechFlowNodeOpenAITranscribe.speexInitialized = true
77
+ SpeechFlowNodeA2TOpenAI.speexInitialized = true
78
78
  }
79
79
  this.resampler = new SpeexResampler(1, this.config.audioSampleRate, 24000, 7)
80
80
 
@@ -279,9 +279,9 @@ export default class SpeechFlowNodeOpenAITranscribe extends SpeechFlowNode {
279
279
  self.log("debug", `received data (${chunk.payload.length} bytes)`)
280
280
  this.push(chunk)
281
281
  }
282
- }).catch((error) => {
282
+ }).catch((error: unknown) => {
283
283
  if (!self.destroyed)
284
- self.log("error", `queue read error: ${error.message}`)
284
+ self.log("error", `queue read error: ${utils.ensureError(error).message}`)
285
285
  })
286
286
  },
287
287
  final (callback) {
@@ -17,11 +17,12 @@ import {
17
17
 
18
18
  /* internal dependencies */
19
19
  import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
20
+ import * as utils from "./speechflow-utils"
20
21
 
21
- /* SpeechFlow node for AWS Polly text-to-speech conversion */
22
- export default class SpeechFlowNodeAWSPolly extends SpeechFlowNode {
22
+ /* SpeechFlow node for Amazon Polly text-to-speech conversion */
23
+ export default class SpeechFlowNodeT2AAmazon extends SpeechFlowNode {
23
24
  /* declare official node name */
24
- public static name = "awspolly"
25
+ public static name = "t2a-amazon"
25
26
 
26
27
  /* internal state */
27
28
  private client: PollyClient | null = null
@@ -113,10 +114,10 @@ export default class SpeechFlowNodeAWSPolly extends SpeechFlowNode {
113
114
 
114
115
  /* establish resampler from AWS Polly's maximum 16Khz output
115
116
  (for PCM output) to our standard audio sample rate (48KHz) */
116
- if (!SpeechFlowNodeAWSPolly.speexInitialized) {
117
+ if (!SpeechFlowNodeT2AAmazon.speexInitialized) {
117
118
  /* at least once initialize resampler */
118
119
  await SpeexResampler.initPromise
119
- SpeechFlowNodeAWSPolly.speexInitialized = true
120
+ SpeechFlowNodeT2AAmazon.speexInitialized = true
120
121
  }
121
122
  this.resampler = new SpeexResampler(1, 16000, this.config.audioSampleRate, 7)
122
123
 
@@ -144,9 +145,8 @@ export default class SpeechFlowNodeAWSPolly extends SpeechFlowNode {
144
145
  chunkNew.payload = buffer
145
146
  this.push(chunkNew)
146
147
  callback()
147
- }).catch((error) => {
148
- callback(error instanceof Error ?
149
- error : new Error(`failed to send to AWS Polly: ${String(error)}`))
148
+ }).catch((error: unknown) => {
149
+ callback(utils.ensureError(error, "failed to send to AWS Polly"))
150
150
  })
151
151
  }
152
152
  else
@@ -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
 
@@ -13,11 +13,12 @@ import SpeexResampler from "speex-resampler"
13
13
 
14
14
  /* internal dependencies */
15
15
  import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
16
+ import * as utils from "./speechflow-utils"
16
17
 
17
18
  /* SpeechFlow node for Kokoro text-to-speech conversion */
18
- export default class SpeechFlowNodeKokoro extends SpeechFlowNode {
19
+ export default class SpeechFlowNodeT2AKokoro extends SpeechFlowNode {
19
20
  /* declare official node name */
20
- public static name = "kokoro"
21
+ public static name = "t2a-kokoro"
21
22
 
22
23
  /* internal state */
23
24
  private kokoro: KokoroTTS | null = null
@@ -81,9 +82,9 @@ export default class SpeechFlowNodeKokoro extends SpeechFlowNode {
81
82
 
82
83
  /* establish resampler from Kokoro's maximum 24Khz
83
84
  output to our standard audio sample rate (48KHz) */
84
- if (!SpeechFlowNodeKokoro.speexInitialized) {
85
+ if (!SpeechFlowNodeT2AKokoro.speexInitialized) {
85
86
  /* at least once initialize resampler */
86
- SpeechFlowNodeKokoro.speexInitialized = true
87
+ SpeechFlowNodeT2AKokoro.speexInitialized = true
87
88
  await SpeexResampler.initPromise
88
89
  }
89
90
  this.resampler = new SpeexResampler(1, 24000, this.config.audioSampleRate, 7)
@@ -141,8 +142,8 @@ export default class SpeechFlowNodeKokoro extends SpeechFlowNode {
141
142
  chunk.payload = buffer
142
143
  this.push(chunk)
143
144
  callback()
144
- }).catch((err) => {
145
- callback(err)
145
+ }).catch((error: unknown) => {
146
+ callback(utils.ensureError(error))
146
147
  })
147
148
  }
148
149
  },
@@ -12,11 +12,12 @@ import { TranslateClient, TranslateTextCommand } from "@aws-sdk/client-translate
12
12
 
13
13
  /* internal dependencies */
14
14
  import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
15
+ import * as utils from "./speechflow-utils"
15
16
 
16
- /* SpeechFlow node for AWS Translate text-to-text translations */
17
- export default class SpeechFlowNodeAWSTranslate extends SpeechFlowNode {
17
+ /* SpeechFlow node for Amazon Translate text-to-text translations */
18
+ export default class SpeechFlowNodeT2TAmazon extends SpeechFlowNode {
18
19
  /* declare official node name */
19
- public static name = "awstranslate"
20
+ public static name = "t2t-amazon"
20
21
 
21
22
  /* internal state */
22
23
  private client: TranslateClient | null = null
@@ -122,8 +123,8 @@ export default class SpeechFlowNodeAWSTranslate extends SpeechFlowNode {
122
123
  chunkNew.payload = payload
123
124
  this.push(chunkNew)
124
125
  callback()
125
- }).catch((err) => {
126
- callback(err)
126
+ }).catch((error: unknown) => {
127
+ callback(utils.ensureError(error))
127
128
  })
128
129
  }
129
130
  },
@@ -12,11 +12,12 @@ import * as DeepL from "deepl-node"
12
12
 
13
13
  /* internal dependencies */
14
14
  import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
15
+ import * as utils from "./speechflow-utils"
15
16
 
16
17
  /* SpeechFlow node for DeepL text-to-text translations */
17
- export default class SpeechFlowNodeDeepL extends SpeechFlowNode {
18
+ export default class SpeechFlowNodeT2TDeepL extends SpeechFlowNode {
18
19
  /* declare official node name */
19
- public static name = "deepl"
20
+ public static name = "t2t-deepl"
20
21
 
21
22
  /* internal state */
22
23
  private deepl: DeepL.Translator | null = null
@@ -93,8 +94,8 @@ export default class SpeechFlowNodeDeepL extends SpeechFlowNode {
93
94
  chunkNew.payload = payload
94
95
  this.push(chunkNew)
95
96
  callback()
96
- }).catch((err) => {
97
- callback(err)
97
+ }).catch((error: unknown) => {
98
+ callback(utils.ensureError(error))
98
99
  })
99
100
  }
100
101
  },
@@ -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[]) {
@@ -0,0 +1,133 @@
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
+ /* external dependencies */
11
+ import { TranslationServiceClient } from "@google-cloud/translate"
12
+ import * as arktype from "arktype"
13
+
14
+ /* internal dependencies */
15
+ import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
16
+ import * as utils from "./speechflow-utils"
17
+
18
+ /* SpeechFlow node for Google Translate text-to-text translations */
19
+ export default class SpeechFlowNodeT2TGoogle extends SpeechFlowNode {
20
+ /* declare official node name */
21
+ public static name = "t2t-google"
22
+
23
+ /* internal state */
24
+ private client: TranslationServiceClient | null = null
25
+
26
+ /* construct node */
27
+ constructor (id: string, cfg: { [ id: string ]: any }, opts: { [ id: string ]: any }, args: any[]) {
28
+ super(id, cfg, opts, args)
29
+
30
+ /* declare node configuration parameters */
31
+ this.configure({
32
+ key: { type: "string", val: process.env.SPEECHFLOW_GOOGLE_KEY ?? "" },
33
+ src: { type: "string", pos: 0, val: "de", match: /^(?:de|en|fr|it)$/ },
34
+ dst: { type: "string", pos: 1, val: "en", match: /^(?:de|en|fr|it)$/ }
35
+ })
36
+
37
+ /* validate API key and project */
38
+ if (this.params.key === "")
39
+ throw new Error("Google Cloud API credentials JSON key is required")
40
+
41
+ /* sanity check situation */
42
+ if (this.params.src === this.params.dst)
43
+ throw new Error("source and destination languages cannot be the same")
44
+
45
+ /* declare node input/output format */
46
+ this.input = "text"
47
+ this.output = "text"
48
+ }
49
+
50
+ /* one-time status of node */
51
+ async status () {
52
+ return {}
53
+ }
54
+
55
+ /* open node */
56
+ async open () {
57
+ /* instantiate Google Translate client */
58
+ const data = utils.run("Google Cloud API credentials key", () =>
59
+ JSON.parse(this.params.key))
60
+ const credentials = utils.importObject("Google Cloud API credentials key",
61
+ data,
62
+ arktype.type({
63
+ project_id: "string",
64
+ private_key: "string",
65
+ client_email: "string",
66
+ })
67
+ )
68
+ this.client = new TranslationServiceClient({
69
+ credentials: {
70
+ private_key: credentials.private_key,
71
+ client_email: credentials.client_email
72
+ },
73
+ projectId: credentials.project_id
74
+ })
75
+
76
+ /* provide text-to-text translation */
77
+ const translate = utils.runner("Google Translate API", async (text: string) => {
78
+ const [ response ] = await this.client!.translateText({
79
+ parent: `projects/${credentials.project_id}/locations/global`,
80
+ contents: [ text ],
81
+ mimeType: "text/plain",
82
+ sourceLanguageCode: this.params.src,
83
+ targetLanguageCode: this.params.dst
84
+ })
85
+ return response.translations?.[0]?.translatedText ?? text
86
+ })
87
+
88
+ /* establish a duplex stream and connect it to Google Translate */
89
+ this.stream = new Stream.Transform({
90
+ readableObjectMode: true,
91
+ writableObjectMode: true,
92
+ decodeStrings: false,
93
+ highWaterMark: 1,
94
+ transform (chunk: SpeechFlowChunk, encoding, callback) {
95
+ if (Buffer.isBuffer(chunk.payload))
96
+ callback(new Error("invalid chunk payload type"))
97
+ else if (chunk.payload === "") {
98
+ this.push(chunk)
99
+ callback()
100
+ }
101
+ else {
102
+ translate(chunk.payload).then((payload) => {
103
+ const chunkNew = chunk.clone()
104
+ chunkNew.payload = payload
105
+ this.push(chunkNew)
106
+ callback()
107
+ }).catch((error: unknown) => {
108
+ callback(utils.ensureError(error))
109
+ })
110
+ }
111
+ },
112
+ final (callback) {
113
+ this.push(null)
114
+ callback()
115
+ }
116
+ })
117
+ }
118
+
119
+ /* close node */
120
+ async close () {
121
+ /* close stream */
122
+ if (this.stream !== null) {
123
+ this.stream.destroy()
124
+ this.stream = null
125
+ }
126
+
127
+ /* shutdown Google Translate client */
128
+ if (this.client !== null) {
129
+ this.client.close()
130
+ this.client = null
131
+ }
132
+ }
133
+ }
@@ -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
+ }
@@ -12,15 +12,16 @@ import { Ollama, type ListResponse } from "ollama"
12
12
 
13
13
  /* internal dependencies */
14
14
  import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
15
+ import * as utils from "./speechflow-utils"
15
16
 
16
17
  /* internal utility types */
17
18
  type ConfigEntry = { systemPrompt: string, chat: Array<{ role: string, content: string }> }
18
19
  type Config = { [ key: string ]: ConfigEntry }
19
20
 
20
21
  /* SpeechFlow node for Ollama text-to-text translation */
21
- export default class SpeechFlowNodeOllama extends SpeechFlowNode {
22
+ export default class SpeechFlowNodeT2TOllama extends SpeechFlowNode {
22
23
  /* declare official node name */
23
- public static name = "ollama"
24
+ public static name = "t2t-ollama"
24
25
 
25
26
  /* internal state */
26
27
  private ollama: Ollama | null = null
@@ -250,8 +251,8 @@ export default class SpeechFlowNodeOllama extends SpeechFlowNode {
250
251
  chunkNew.payload = payload
251
252
  this.push(chunkNew)
252
253
  callback()
253
- }).catch((err) => {
254
- callback(err)
254
+ }).catch((error: unknown) => {
255
+ callback(utils.ensureError(error))
255
256
  })
256
257
  }
257
258
  }
@@ -12,15 +12,16 @@ import OpenAI from "openai"
12
12
 
13
13
  /* internal dependencies */
14
14
  import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
15
+ import * as utils from "./speechflow-utils"
15
16
 
16
17
  /* internal utility types */
17
18
  type ConfigEntry = { systemPrompt: string, chat: OpenAI.ChatCompletionMessageParam[] }
18
19
  type Config = { [ key: string ]: ConfigEntry }
19
20
 
20
21
  /* SpeechFlow node for OpenAI/GPT text-to-text translation */
21
- export default class SpeechFlowNodeOpenAI extends SpeechFlowNode {
22
+ export default class SpeechFlowNodeT2TOpenAI extends SpeechFlowNode {
22
23
  /* declare official node name */
23
- public static name = "openai"
24
+ public static name = "t2t-openai"
24
25
 
25
26
  /* internal state */
26
27
  private openai: OpenAI | null = null
@@ -219,8 +220,8 @@ export default class SpeechFlowNodeOpenAI extends SpeechFlowNode {
219
220
  chunkNew.payload = payload
220
221
  this.push(chunkNew)
221
222
  callback()
222
- }).catch((err) => {
223
- callback(err)
223
+ }).catch((error: unknown) => {
224
+ callback(utils.ensureError(error))
224
225
  })
225
226
  }
226
227
  },
@@ -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>()