speechflow 1.4.5 → 1.5.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 (166) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +220 -7
  3. package/etc/claude.md +70 -0
  4. package/etc/speechflow.yaml +5 -3
  5. package/etc/stx.conf +7 -0
  6. package/package.json +7 -6
  7. package/speechflow-cli/dst/speechflow-node-a2a-compressor-wt.d.ts +1 -0
  8. package/speechflow-cli/dst/speechflow-node-a2a-compressor-wt.js +155 -0
  9. package/speechflow-cli/dst/speechflow-node-a2a-compressor-wt.js.map +1 -0
  10. package/speechflow-cli/dst/speechflow-node-a2a-compressor.d.ts +15 -0
  11. package/speechflow-cli/dst/speechflow-node-a2a-compressor.js +287 -0
  12. package/speechflow-cli/dst/speechflow-node-a2a-compressor.js.map +1 -0
  13. package/speechflow-cli/dst/speechflow-node-a2a-dynamics-wt.d.ts +1 -0
  14. package/speechflow-cli/dst/speechflow-node-a2a-dynamics-wt.js +208 -0
  15. package/speechflow-cli/dst/speechflow-node-a2a-dynamics-wt.js.map +1 -0
  16. package/speechflow-cli/dst/speechflow-node-a2a-dynamics.d.ts +15 -0
  17. package/speechflow-cli/dst/speechflow-node-a2a-dynamics.js +312 -0
  18. package/speechflow-cli/dst/speechflow-node-a2a-dynamics.js.map +1 -0
  19. package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.d.ts +1 -0
  20. package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.js +161 -0
  21. package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.js.map +1 -0
  22. package/speechflow-cli/dst/speechflow-node-a2a-expander.d.ts +13 -0
  23. package/speechflow-cli/dst/speechflow-node-a2a-expander.js +208 -0
  24. package/speechflow-cli/dst/speechflow-node-a2a-expander.js.map +1 -0
  25. package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js +13 -3
  26. package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js.map +1 -1
  27. package/speechflow-cli/dst/speechflow-node-a2a-filler.d.ts +14 -0
  28. package/speechflow-cli/dst/speechflow-node-a2a-filler.js +233 -0
  29. package/speechflow-cli/dst/speechflow-node-a2a-filler.js.map +1 -0
  30. package/speechflow-cli/dst/speechflow-node-a2a-gain.d.ts +12 -0
  31. package/speechflow-cli/dst/speechflow-node-a2a-gain.js +125 -0
  32. package/speechflow-cli/dst/speechflow-node-a2a-gain.js.map +1 -0
  33. package/speechflow-cli/dst/speechflow-node-a2a-gender.d.ts +0 -1
  34. package/speechflow-cli/dst/speechflow-node-a2a-gender.js +28 -12
  35. package/speechflow-cli/dst/speechflow-node-a2a-gender.js.map +1 -1
  36. package/speechflow-cli/dst/speechflow-node-a2a-meter.d.ts +1 -0
  37. package/speechflow-cli/dst/speechflow-node-a2a-meter.js +12 -8
  38. package/speechflow-cli/dst/speechflow-node-a2a-meter.js.map +1 -1
  39. package/speechflow-cli/dst/speechflow-node-a2a-mute.js +2 -1
  40. package/speechflow-cli/dst/speechflow-node-a2a-mute.js.map +1 -1
  41. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise-wt.d.ts +1 -0
  42. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise-wt.js +55 -0
  43. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise-wt.js.map +1 -0
  44. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.d.ts +14 -0
  45. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js +184 -0
  46. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js.map +1 -0
  47. package/speechflow-cli/dst/speechflow-node-a2a-speex.d.ts +14 -0
  48. package/speechflow-cli/dst/speechflow-node-a2a-speex.js +156 -0
  49. package/speechflow-cli/dst/speechflow-node-a2a-speex.js.map +1 -0
  50. package/speechflow-cli/dst/speechflow-node-a2a-vad.js +3 -3
  51. package/speechflow-cli/dst/speechflow-node-a2a-vad.js.map +1 -1
  52. package/speechflow-cli/dst/speechflow-node-a2a-wav.js +22 -17
  53. package/speechflow-cli/dst/speechflow-node-a2a-wav.js.map +1 -1
  54. package/speechflow-cli/dst/speechflow-node-a2t-awstranscribe.d.ts +18 -0
  55. package/speechflow-cli/dst/speechflow-node-a2t-awstranscribe.js +317 -0
  56. package/speechflow-cli/dst/speechflow-node-a2t-awstranscribe.js.map +1 -0
  57. package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js +15 -13
  58. package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js.map +1 -1
  59. package/speechflow-cli/dst/speechflow-node-a2t-openaitranscribe.d.ts +19 -0
  60. package/speechflow-cli/dst/speechflow-node-a2t-openaitranscribe.js +351 -0
  61. package/speechflow-cli/dst/speechflow-node-a2t-openaitranscribe.js.map +1 -0
  62. package/speechflow-cli/dst/speechflow-node-t2a-awspolly.d.ts +16 -0
  63. package/speechflow-cli/dst/speechflow-node-t2a-awspolly.js +171 -0
  64. package/speechflow-cli/dst/speechflow-node-t2a-awspolly.js.map +1 -0
  65. package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js +19 -14
  66. package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js.map +1 -1
  67. package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js +11 -6
  68. package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js.map +1 -1
  69. package/speechflow-cli/dst/speechflow-node-t2t-awstranslate.d.ts +13 -0
  70. package/speechflow-cli/dst/speechflow-node-t2t-awstranslate.js +141 -0
  71. package/speechflow-cli/dst/speechflow-node-t2t-awstranslate.js.map +1 -0
  72. package/speechflow-cli/dst/speechflow-node-t2t-deepl.js +13 -15
  73. package/speechflow-cli/dst/speechflow-node-t2t-deepl.js.map +1 -1
  74. package/speechflow-cli/dst/speechflow-node-t2t-format.js +10 -15
  75. package/speechflow-cli/dst/speechflow-node-t2t-format.js.map +1 -1
  76. package/speechflow-cli/dst/speechflow-node-t2t-ollama.js +44 -31
  77. package/speechflow-cli/dst/speechflow-node-t2t-ollama.js.map +1 -1
  78. package/speechflow-cli/dst/speechflow-node-t2t-openai.js +44 -45
  79. package/speechflow-cli/dst/speechflow-node-t2t-openai.js.map +1 -1
  80. package/speechflow-cli/dst/speechflow-node-t2t-sentence.js +8 -8
  81. package/speechflow-cli/dst/speechflow-node-t2t-sentence.js.map +1 -1
  82. package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js +10 -12
  83. package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js.map +1 -1
  84. package/speechflow-cli/dst/speechflow-node-t2t-transformers.js +22 -27
  85. package/speechflow-cli/dst/speechflow-node-t2t-transformers.js.map +1 -1
  86. package/speechflow-cli/dst/speechflow-node-x2x-filter.d.ts +1 -0
  87. package/speechflow-cli/dst/speechflow-node-x2x-filter.js +50 -15
  88. package/speechflow-cli/dst/speechflow-node-x2x-filter.js.map +1 -1
  89. package/speechflow-cli/dst/speechflow-node-x2x-trace.js +17 -18
  90. package/speechflow-cli/dst/speechflow-node-x2x-trace.js.map +1 -1
  91. package/speechflow-cli/dst/speechflow-node-xio-device.js +13 -21
  92. package/speechflow-cli/dst/speechflow-node-xio-device.js.map +1 -1
  93. package/speechflow-cli/dst/speechflow-node-xio-mqtt.d.ts +1 -0
  94. package/speechflow-cli/dst/speechflow-node-xio-mqtt.js +22 -16
  95. package/speechflow-cli/dst/speechflow-node-xio-mqtt.js.map +1 -1
  96. package/speechflow-cli/dst/speechflow-node-xio-websocket.js +19 -19
  97. package/speechflow-cli/dst/speechflow-node-xio-websocket.js.map +1 -1
  98. package/speechflow-cli/dst/speechflow-node.d.ts +6 -3
  99. package/speechflow-cli/dst/speechflow-node.js +13 -2
  100. package/speechflow-cli/dst/speechflow-node.js.map +1 -1
  101. package/speechflow-cli/dst/speechflow-utils-audio-wt.d.ts +1 -0
  102. package/speechflow-cli/dst/speechflow-utils-audio-wt.js +124 -0
  103. package/speechflow-cli/dst/speechflow-utils-audio-wt.js.map +1 -0
  104. package/speechflow-cli/dst/speechflow-utils-audio.d.ts +13 -0
  105. package/speechflow-cli/dst/speechflow-utils-audio.js +137 -0
  106. package/speechflow-cli/dst/speechflow-utils-audio.js.map +1 -0
  107. package/speechflow-cli/dst/speechflow-utils.d.ts +18 -0
  108. package/speechflow-cli/dst/speechflow-utils.js +123 -35
  109. package/speechflow-cli/dst/speechflow-utils.js.map +1 -1
  110. package/speechflow-cli/dst/speechflow.js +69 -14
  111. package/speechflow-cli/dst/speechflow.js.map +1 -1
  112. package/speechflow-cli/etc/oxlint.jsonc +112 -11
  113. package/speechflow-cli/etc/stx.conf +2 -2
  114. package/speechflow-cli/etc/tsconfig.json +1 -1
  115. package/speechflow-cli/package.d/@shiguredo+rnnoise-wasm+2025.1.5.patch +25 -0
  116. package/speechflow-cli/package.json +102 -94
  117. package/speechflow-cli/src/lib.d.ts +24 -0
  118. package/speechflow-cli/src/speechflow-node-a2a-compressor-wt.ts +151 -0
  119. package/speechflow-cli/src/speechflow-node-a2a-compressor.ts +303 -0
  120. package/speechflow-cli/src/speechflow-node-a2a-expander-wt.ts +158 -0
  121. package/speechflow-cli/src/speechflow-node-a2a-expander.ts +212 -0
  122. package/speechflow-cli/src/speechflow-node-a2a-ffmpeg.ts +13 -3
  123. package/speechflow-cli/src/speechflow-node-a2a-filler.ts +223 -0
  124. package/speechflow-cli/src/speechflow-node-a2a-gain.ts +98 -0
  125. package/speechflow-cli/src/speechflow-node-a2a-gender.ts +31 -17
  126. package/speechflow-cli/src/speechflow-node-a2a-meter.ts +13 -9
  127. package/speechflow-cli/src/speechflow-node-a2a-mute.ts +3 -2
  128. package/speechflow-cli/src/speechflow-node-a2a-rnnoise-wt.ts +62 -0
  129. package/speechflow-cli/src/speechflow-node-a2a-rnnoise.ts +164 -0
  130. package/speechflow-cli/src/speechflow-node-a2a-speex.ts +137 -0
  131. package/speechflow-cli/src/speechflow-node-a2a-vad.ts +3 -3
  132. package/speechflow-cli/src/speechflow-node-a2a-wav.ts +20 -13
  133. package/speechflow-cli/src/speechflow-node-a2t-awstranscribe.ts +308 -0
  134. package/speechflow-cli/src/speechflow-node-a2t-deepgram.ts +15 -13
  135. package/speechflow-cli/src/speechflow-node-a2t-openaitranscribe.ts +337 -0
  136. package/speechflow-cli/src/speechflow-node-t2a-awspolly.ts +187 -0
  137. package/speechflow-cli/src/speechflow-node-t2a-elevenlabs.ts +19 -14
  138. package/speechflow-cli/src/speechflow-node-t2a-kokoro.ts +12 -7
  139. package/speechflow-cli/src/speechflow-node-t2t-awstranslate.ts +152 -0
  140. package/speechflow-cli/src/speechflow-node-t2t-deepl.ts +13 -15
  141. package/speechflow-cli/src/speechflow-node-t2t-format.ts +10 -15
  142. package/speechflow-cli/src/speechflow-node-t2t-ollama.ts +55 -42
  143. package/speechflow-cli/src/speechflow-node-t2t-openai.ts +58 -58
  144. package/speechflow-cli/src/speechflow-node-t2t-sentence.ts +10 -10
  145. package/speechflow-cli/src/speechflow-node-t2t-subtitle.ts +15 -16
  146. package/speechflow-cli/src/speechflow-node-t2t-transformers.ts +27 -32
  147. package/speechflow-cli/src/speechflow-node-x2x-filter.ts +20 -16
  148. package/speechflow-cli/src/speechflow-node-x2x-trace.ts +20 -19
  149. package/speechflow-cli/src/speechflow-node-xio-device.ts +15 -23
  150. package/speechflow-cli/src/speechflow-node-xio-mqtt.ts +23 -16
  151. package/speechflow-cli/src/speechflow-node-xio-websocket.ts +19 -19
  152. package/speechflow-cli/src/speechflow-node.ts +21 -8
  153. package/speechflow-cli/src/speechflow-utils-audio-wt.ts +172 -0
  154. package/speechflow-cli/src/speechflow-utils-audio.ts +147 -0
  155. package/speechflow-cli/src/speechflow-utils.ts +125 -32
  156. package/speechflow-cli/src/speechflow.ts +74 -17
  157. package/speechflow-ui-db/dst/index.js +31 -31
  158. package/speechflow-ui-db/etc/eslint.mjs +0 -1
  159. package/speechflow-ui-db/etc/tsc-client.json +3 -3
  160. package/speechflow-ui-db/package.json +11 -10
  161. package/speechflow-ui-db/src/app.vue +20 -6
  162. package/speechflow-ui-st/dst/index.js +26 -26
  163. package/speechflow-ui-st/etc/eslint.mjs +0 -1
  164. package/speechflow-ui-st/etc/tsc-client.json +3 -3
  165. package/speechflow-ui-st/package.json +11 -10
  166. package/speechflow-ui-st/src/app.vue +5 -12
@@ -0,0 +1,308 @@
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 {
12
+ TranscribeStreamingClient,
13
+ TranscriptResultStream,
14
+ StartStreamTranscriptionCommand,
15
+ type AudioStream,
16
+ type LanguageCode
17
+ } from "@aws-sdk/client-transcribe-streaming"
18
+ import { DateTime, Duration } from "luxon"
19
+
20
+ /* internal dependencies */
21
+ import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
22
+ import * as utils from "./speechflow-utils"
23
+
24
+ /* helper class for an asynchronous queue */
25
+ class AsyncQueue<T> {
26
+ private queue: Array<T | null> = []
27
+ private resolvers: ((it: IteratorResult<T>) => void)[] = []
28
+ push (v: T | null) {
29
+ const resolve = this.resolvers.shift()
30
+ if (resolve) {
31
+ if (v !== null)
32
+ resolve({ value: v })
33
+ else
34
+ resolve({ value: null, done: true })
35
+ }
36
+ else
37
+ this.queue.push(v)
38
+ }
39
+ destroy () {
40
+ while (this.resolvers.length > 0) {
41
+ const resolve = this.resolvers.shift()
42
+ resolve?.({ value: null, done: true })
43
+ }
44
+ this.queue.length = 0
45
+ }
46
+ async *[Symbol.asyncIterator](): AsyncIterator<T> {
47
+ while (true) {
48
+ if (this.queue.length > 0) {
49
+ const v = this.queue.shift()
50
+ if (v === undefined || v === null)
51
+ return
52
+ yield v
53
+ continue
54
+ }
55
+ else {
56
+ const it = await new Promise<IteratorResult<T>>((resolve) => this.resolvers.push(resolve))
57
+ if (it.done)
58
+ return
59
+ yield it.value
60
+ }
61
+ }
62
+ }
63
+ }
64
+
65
+ /* SpeechFlow node for AWS Transcribe speech-to-text conversion */
66
+ export default class SpeechFlowNodeAWSTranscribe extends SpeechFlowNode {
67
+ /* declare official node name */
68
+ public static name = "awstranscribe"
69
+
70
+ /* internal state */
71
+ private client: TranscribeStreamingClient | null = null
72
+ private clientStream: AsyncIterable<TranscriptResultStream> | null = null
73
+ private destroyed = false
74
+ private initTimeout: ReturnType<typeof setTimeout> | null = null
75
+ private connectionTimeout: ReturnType<typeof setTimeout> | null = null
76
+ private queue: utils.SingleQueue<SpeechFlowChunk | null> | null = null
77
+
78
+ /* construct node */
79
+ constructor (id: string, cfg: { [ id: string ]: any }, opts: { [ id: string ]: any }, args: any[]) {
80
+ super(id, cfg, opts, args)
81
+
82
+ /* declare node configuration parameters */
83
+ this.configure({
84
+ key: { type: "string", val: process.env.SPEECHFLOW_AMAZON_KEY },
85
+ secKey: { type: "string", val: process.env.SPEECHFLOW_AMAZON_KEY_SEC },
86
+ region: { type: "string", val: "eu-central-1" },
87
+ language: { type: "string", val: "en", match: /^(?:de|en)$/ },
88
+ interim: { type: "boolean", val: false }
89
+ })
90
+
91
+ /* sanity check parameters */
92
+ if (!this.params.key)
93
+ throw new Error("AWS Access Key not configured")
94
+ if (!this.params.secKey)
95
+ throw new Error("AWS Secret Access Key not configured")
96
+
97
+ /* declare node input/output format */
98
+ this.input = "audio"
99
+ this.output = "text"
100
+ }
101
+
102
+ /* one-time status of node */
103
+ async status () {
104
+ return {}
105
+ }
106
+
107
+ /* open node */
108
+ async open () {
109
+ /* sanity check situation */
110
+ if (this.config.audioBitDepth !== 16 || !this.config.audioLittleEndian)
111
+ throw new Error("Amazon Transcribe node currently supports PCM-S16LE audio only")
112
+
113
+ /* clear destruction flag */
114
+ this.destroyed = false
115
+
116
+ /* create queue for results */
117
+ this.queue = new utils.SingleQueue<SpeechFlowChunk | null>()
118
+
119
+ /* create a store for the meta information */
120
+ const metastore = new utils.TimeStore<Map<string, any>>()
121
+
122
+ /* connect to Amazon Transcribe API */
123
+ this.client = new TranscribeStreamingClient({
124
+ region: this.params.region,
125
+ credentials: {
126
+ accessKeyId: this.params.key,
127
+ secretAccessKey: this.params.secKey
128
+ }
129
+ })
130
+ if (this.client === null)
131
+ throw new Error("failed to establish Amazon Transcribe client")
132
+
133
+ /* create an AudioStream for Amazon Transcribe */
134
+ const audioQueue = new AsyncQueue<Uint8Array>()
135
+ const audioStream = (async function *(q: AsyncQueue<Uint8Array>): AsyncIterable<AudioStream> {
136
+ for await (const chunk of q) {
137
+ yield { AudioEvent: { AudioChunk: chunk } }
138
+ }
139
+ })(audioQueue)
140
+
141
+ /* start streaming */
142
+ const ensureAudioStreamActive = async () => {
143
+ if (this.clientStream !== null || this.destroyed)
144
+ return
145
+ const language: LanguageCode = this.params.language === "de" ? "de-DE" : "en-US"
146
+ const command = new StartStreamTranscriptionCommand({
147
+ LanguageCode: language,
148
+ EnablePartialResultsStabilization: this.params.interim,
149
+ ...(this.params.interim ? { PartialResultsStability: "low" } : {}),
150
+ MediaEncoding: "pcm",
151
+ MediaSampleRateHertz: this.config.audioSampleRate,
152
+ AudioStream: audioStream,
153
+ })
154
+ const response = await this.client!.send(command)
155
+ const stream = response.TranscriptResultStream
156
+ if (!stream)
157
+ throw new Error("no TranscriptResultStream returned")
158
+ this.clientStream = stream
159
+ ;(async () => {
160
+ for await (const event of stream) {
161
+ const te = event.TranscriptEvent
162
+ if (!te?.Transcript?.Results)
163
+ continue
164
+ for (const result of te.Transcript.Results) {
165
+ const alt = result.Alternatives?.[0]
166
+ if (!alt?.Transcript)
167
+ continue
168
+ if (result.IsPartial && !this.params.interim)
169
+ continue
170
+ const text = alt.Transcript ?? ""
171
+ const kind = result.IsPartial ? "intermediate" : "final"
172
+ const tsStart = Duration.fromMillis((result.StartTime ?? 0) * 1000).plus(this.timeZeroOffset)
173
+ const tsEnd = Duration.fromMillis((result.EndTime ?? 0) * 1000).plus(this.timeZeroOffset)
174
+ const metas = metastore.fetch(tsStart, tsEnd)
175
+ const meta = metas.reduce((prev: Map<string, any>, curr: Map<string, any>) => {
176
+ curr.forEach((val, key) => { prev.set(key, val) })
177
+ return prev
178
+ }, new Map<string, any>())
179
+ if (this.params.interim) {
180
+ const words = []
181
+ for (const item of alt.Items ?? []) {
182
+ if (item.Type === "pronunciation") {
183
+ words.push({
184
+ word: item.Content,
185
+ start: Duration.fromMillis((item.StartTime ?? 0) * 1000).plus(this.timeZeroOffset),
186
+ end: Duration.fromMillis((item.EndTime ?? 0) * 1000).plus(this.timeZeroOffset)
187
+ })
188
+ }
189
+ }
190
+ meta.set("words", words)
191
+ }
192
+ metastore.prune(tsStart)
193
+ const chunk = new SpeechFlowChunk(tsStart, tsEnd, kind, "text", text, meta)
194
+ this.queue?.write(chunk)
195
+ }
196
+ }
197
+ })().catch((err: Error) => {
198
+ this.log("warning", `failed to establish connectivity to Amazon Transcribe: ${err}`)
199
+ })
200
+ }
201
+
202
+ /* remember opening time to receive time zero offset */
203
+ this.timeOpen = DateTime.now()
204
+
205
+ /* provide Duplex stream and internally attach to Deepgram API */
206
+ const self = this
207
+ this.stream = new Stream.Duplex({
208
+ writableObjectMode: true,
209
+ readableObjectMode: true,
210
+ decodeStrings: false,
211
+ highWaterMark: 1,
212
+ write (chunk: SpeechFlowChunk, encoding, callback) {
213
+ if (self.destroyed || self.client === null) {
214
+ callback(new Error("stream already destroyed"))
215
+ return
216
+ }
217
+ if (chunk.type !== "audio")
218
+ callback(new Error("expected audio input chunk"))
219
+ else if (!Buffer.isBuffer(chunk.payload))
220
+ callback(new Error("expected Buffer input chunk"))
221
+ else {
222
+ if (chunk.payload.byteLength > 0) {
223
+ self.log("debug", `send data (${chunk.payload.byteLength} bytes)`)
224
+ if (chunk.meta.size > 0)
225
+ metastore.store(chunk.timestampStart, chunk.timestampEnd, chunk.meta)
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}`)
229
+ })
230
+ }
231
+ callback()
232
+ }
233
+ },
234
+ read (size) {
235
+ if (self.destroyed || self.queue === null) {
236
+ this.push(null)
237
+ return
238
+ }
239
+ self.queue.read().then((chunk) => {
240
+ if (self.destroyed) {
241
+ this.push(null)
242
+ return
243
+ }
244
+ if (chunk === null) {
245
+ self.log("info", "received EOF signal")
246
+ this.push(null)
247
+ }
248
+ else {
249
+ self.log("debug", `received data (${chunk.payload.length} bytes): "${chunk.payload}"`)
250
+ this.push(chunk)
251
+ }
252
+ }).catch((error) => {
253
+ if (!self.destroyed)
254
+ self.log("error", `queue read error: ${error.message}`)
255
+ })
256
+ },
257
+ final (callback) {
258
+ if (self.destroyed || self.client === null) {
259
+ callback()
260
+ return
261
+ }
262
+ try {
263
+ self.client.destroy()
264
+ }
265
+ catch (error) {
266
+ self.log("warning", `error closing Amazon Transcribe connection: ${error}`)
267
+ }
268
+ audioQueue.push(null) /* do not push null to stream, let Amazon Transcribe do it */
269
+ audioQueue.destroy()
270
+ callback()
271
+ }
272
+ })
273
+ }
274
+
275
+ /* close node */
276
+ async close () {
277
+ /* indicate destruction first to stop all async operations */
278
+ this.destroyed = true
279
+
280
+ /* cleanup all timers */
281
+ if (this.initTimeout !== null) {
282
+ clearTimeout(this.initTimeout)
283
+ this.initTimeout = null
284
+ }
285
+ if (this.connectionTimeout !== null) {
286
+ clearTimeout(this.connectionTimeout)
287
+ this.connectionTimeout = null
288
+ }
289
+
290
+ /* close queue */
291
+ if (this.queue !== null) {
292
+ this.queue.write(null)
293
+ this.queue = null
294
+ }
295
+
296
+ /* close Amazon Transcribe connection */
297
+ if (this.client !== null) {
298
+ this.client.destroy()
299
+ this.client = null
300
+ }
301
+
302
+ /* close stream */
303
+ if (this.stream !== null) {
304
+ this.stream.destroy()
305
+ this.stream = null
306
+ }
307
+ }
308
+ }
@@ -52,13 +52,15 @@ export default class SpeechFlowNodeDeepgram extends SpeechFlowNode {
52
52
  try {
53
53
  const deepgram = Deepgram.createClient(this.params.keyAdm)
54
54
  const response = await deepgram.manage.getProjects()
55
- if (response !== null && response.error === null) {
55
+ if (response !== null && response.error === null && response.result?.projects) {
56
56
  for (const project of response.result.projects) {
57
- const response = await deepgram.manage.getProjectBalances(project.project_id)
58
- if (response !== null && response.error === null)
59
- balance += response.result.balances[0]?.amount ?? 0
57
+ const balanceResponse = await deepgram.manage.getProjectBalances(project.project_id)
58
+ if (balanceResponse !== null && balanceResponse.error === null && balanceResponse.result?.balances)
59
+ balance += balanceResponse.result.balances[0]?.amount ?? 0
60
60
  }
61
61
  }
62
+ else if (response?.error !== null)
63
+ this.log("warning", `API error fetching projects: ${response.error}`)
62
64
  }
63
65
  catch (error) {
64
66
  this.log("warning", `failed to fetch balance: ${error}`)
@@ -84,10 +86,12 @@ export default class SpeechFlowNodeDeepgram extends SpeechFlowNode {
84
86
  /* connect to Deepgram API */
85
87
  const deepgram = Deepgram.createClient(this.params.key)
86
88
  let language = "en"
87
- if (this.params.model.match(/^nova-2/) && this.params.language !== "en")
88
- language = this.params.language
89
- else if (this.params.model.match(/^nova-3/) && this.params.language !== "en")
90
- language = "multi"
89
+ if (this.params.language !== "en") {
90
+ if (this.params.model.match(/^nova-2/))
91
+ language = this.params.language
92
+ else if (this.params.model.match(/^nova-3/))
93
+ language = "multi"
94
+ }
91
95
  this.dg = deepgram.listen.live({
92
96
  mip_opt_out: true,
93
97
  model: this.params.model,
@@ -162,10 +166,8 @@ export default class SpeechFlowNodeDeepgram extends SpeechFlowNode {
162
166
  /* wait for Deepgram API to be available */
163
167
  await new Promise((resolve, reject) => {
164
168
  this.connectionTimeout = setTimeout(() => {
165
- if (this.connectionTimeout !== null) {
166
- this.connectionTimeout = null
167
- reject(new Error("Deepgram: timeout waiting for connection open"))
168
- }
169
+ this.connectionTimeout = null
170
+ reject(new Error("Deepgram: timeout waiting for connection open"))
169
171
  }, 8000)
170
172
  this.dg!.once(Deepgram.LiveTranscriptionEvents.Open, () => {
171
173
  this.log("info", "connection open")
@@ -228,7 +230,7 @@ export default class SpeechFlowNodeDeepgram extends SpeechFlowNode {
228
230
  }
229
231
  else {
230
232
  self.log("debug", `received data (${chunk.payload.length} bytes)`)
231
- this.push(chunk, self.config.textEncoding)
233
+ this.push(chunk)
232
234
  }
233
235
  }).catch((error) => {
234
236
  if (!self.destroyed)