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.
- package/CHANGELOG.md +28 -0
- package/README.md +220 -7
- package/etc/claude.md +70 -0
- package/etc/speechflow.yaml +5 -3
- package/etc/stx.conf +7 -0
- package/package.json +7 -6
- package/speechflow-cli/dst/speechflow-node-a2a-compressor-wt.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-compressor-wt.js +155 -0
- package/speechflow-cli/dst/speechflow-node-a2a-compressor-wt.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.d.ts +15 -0
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.js +287 -0
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-dynamics-wt.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-dynamics-wt.js +208 -0
- package/speechflow-cli/dst/speechflow-node-a2a-dynamics-wt.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-dynamics.d.ts +15 -0
- package/speechflow-cli/dst/speechflow-node-a2a-dynamics.js +312 -0
- package/speechflow-cli/dst/speechflow-node-a2a-dynamics.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.js +161 -0
- package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-expander.d.ts +13 -0
- package/speechflow-cli/dst/speechflow-node-a2a-expander.js +208 -0
- package/speechflow-cli/dst/speechflow-node-a2a-expander.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js +13 -3
- package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-filler.d.ts +14 -0
- package/speechflow-cli/dst/speechflow-node-a2a-filler.js +233 -0
- package/speechflow-cli/dst/speechflow-node-a2a-filler.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-gain.d.ts +12 -0
- package/speechflow-cli/dst/speechflow-node-a2a-gain.js +125 -0
- package/speechflow-cli/dst/speechflow-node-a2a-gain.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-gender.d.ts +0 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gender.js +28 -12
- package/speechflow-cli/dst/speechflow-node-a2a-gender.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-meter.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-meter.js +12 -8
- package/speechflow-cli/dst/speechflow-node-a2a-meter.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-mute.js +2 -1
- package/speechflow-cli/dst/speechflow-node-a2a-mute.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise-wt.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise-wt.js +55 -0
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise-wt.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.d.ts +14 -0
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js +184 -0
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-speex.d.ts +14 -0
- package/speechflow-cli/dst/speechflow-node-a2a-speex.js +156 -0
- package/speechflow-cli/dst/speechflow-node-a2a-speex.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-vad.js +3 -3
- package/speechflow-cli/dst/speechflow-node-a2a-vad.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-wav.js +22 -17
- package/speechflow-cli/dst/speechflow-node-a2a-wav.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-awstranscribe.d.ts +18 -0
- package/speechflow-cli/dst/speechflow-node-a2t-awstranscribe.js +317 -0
- package/speechflow-cli/dst/speechflow-node-a2t-awstranscribe.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js +15 -13
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-openaitranscribe.d.ts +19 -0
- package/speechflow-cli/dst/speechflow-node-a2t-openaitranscribe.js +351 -0
- package/speechflow-cli/dst/speechflow-node-a2t-openaitranscribe.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-t2a-awspolly.d.ts +16 -0
- package/speechflow-cli/dst/speechflow-node-t2a-awspolly.js +171 -0
- package/speechflow-cli/dst/speechflow-node-t2a-awspolly.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js +19 -14
- package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js +11 -6
- package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-awstranslate.d.ts +13 -0
- package/speechflow-cli/dst/speechflow-node-t2t-awstranslate.js +141 -0
- package/speechflow-cli/dst/speechflow-node-t2t-awstranslate.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-t2t-deepl.js +13 -15
- package/speechflow-cli/dst/speechflow-node-t2t-deepl.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-format.js +10 -15
- package/speechflow-cli/dst/speechflow-node-t2t-format.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-ollama.js +44 -31
- package/speechflow-cli/dst/speechflow-node-t2t-ollama.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-openai.js +44 -45
- package/speechflow-cli/dst/speechflow-node-t2t-openai.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-sentence.js +8 -8
- package/speechflow-cli/dst/speechflow-node-t2t-sentence.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js +10 -12
- package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-transformers.js +22 -27
- package/speechflow-cli/dst/speechflow-node-t2t-transformers.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-x2x-filter.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-node-x2x-filter.js +50 -15
- package/speechflow-cli/dst/speechflow-node-x2x-filter.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-x2x-trace.js +17 -18
- package/speechflow-cli/dst/speechflow-node-x2x-trace.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-device.js +13 -21
- package/speechflow-cli/dst/speechflow-node-xio-device.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-mqtt.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-node-xio-mqtt.js +22 -16
- package/speechflow-cli/dst/speechflow-node-xio-mqtt.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-websocket.js +19 -19
- package/speechflow-cli/dst/speechflow-node-xio-websocket.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node.d.ts +6 -3
- package/speechflow-cli/dst/speechflow-node.js +13 -2
- package/speechflow-cli/dst/speechflow-node.js.map +1 -1
- package/speechflow-cli/dst/speechflow-utils-audio-wt.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-utils-audio-wt.js +124 -0
- package/speechflow-cli/dst/speechflow-utils-audio-wt.js.map +1 -0
- package/speechflow-cli/dst/speechflow-utils-audio.d.ts +13 -0
- package/speechflow-cli/dst/speechflow-utils-audio.js +137 -0
- package/speechflow-cli/dst/speechflow-utils-audio.js.map +1 -0
- package/speechflow-cli/dst/speechflow-utils.d.ts +18 -0
- package/speechflow-cli/dst/speechflow-utils.js +123 -35
- package/speechflow-cli/dst/speechflow-utils.js.map +1 -1
- package/speechflow-cli/dst/speechflow.js +69 -14
- package/speechflow-cli/dst/speechflow.js.map +1 -1
- package/speechflow-cli/etc/oxlint.jsonc +112 -11
- package/speechflow-cli/etc/stx.conf +2 -2
- package/speechflow-cli/etc/tsconfig.json +1 -1
- package/speechflow-cli/package.d/@shiguredo+rnnoise-wasm+2025.1.5.patch +25 -0
- package/speechflow-cli/package.json +102 -94
- package/speechflow-cli/src/lib.d.ts +24 -0
- package/speechflow-cli/src/speechflow-node-a2a-compressor-wt.ts +151 -0
- package/speechflow-cli/src/speechflow-node-a2a-compressor.ts +303 -0
- package/speechflow-cli/src/speechflow-node-a2a-expander-wt.ts +158 -0
- package/speechflow-cli/src/speechflow-node-a2a-expander.ts +212 -0
- package/speechflow-cli/src/speechflow-node-a2a-ffmpeg.ts +13 -3
- package/speechflow-cli/src/speechflow-node-a2a-filler.ts +223 -0
- package/speechflow-cli/src/speechflow-node-a2a-gain.ts +98 -0
- package/speechflow-cli/src/speechflow-node-a2a-gender.ts +31 -17
- package/speechflow-cli/src/speechflow-node-a2a-meter.ts +13 -9
- package/speechflow-cli/src/speechflow-node-a2a-mute.ts +3 -2
- package/speechflow-cli/src/speechflow-node-a2a-rnnoise-wt.ts +62 -0
- package/speechflow-cli/src/speechflow-node-a2a-rnnoise.ts +164 -0
- package/speechflow-cli/src/speechflow-node-a2a-speex.ts +137 -0
- package/speechflow-cli/src/speechflow-node-a2a-vad.ts +3 -3
- package/speechflow-cli/src/speechflow-node-a2a-wav.ts +20 -13
- package/speechflow-cli/src/speechflow-node-a2t-awstranscribe.ts +308 -0
- package/speechflow-cli/src/speechflow-node-a2t-deepgram.ts +15 -13
- package/speechflow-cli/src/speechflow-node-a2t-openaitranscribe.ts +337 -0
- package/speechflow-cli/src/speechflow-node-t2a-awspolly.ts +187 -0
- package/speechflow-cli/src/speechflow-node-t2a-elevenlabs.ts +19 -14
- package/speechflow-cli/src/speechflow-node-t2a-kokoro.ts +12 -7
- package/speechflow-cli/src/speechflow-node-t2t-awstranslate.ts +152 -0
- package/speechflow-cli/src/speechflow-node-t2t-deepl.ts +13 -15
- package/speechflow-cli/src/speechflow-node-t2t-format.ts +10 -15
- package/speechflow-cli/src/speechflow-node-t2t-ollama.ts +55 -42
- package/speechflow-cli/src/speechflow-node-t2t-openai.ts +58 -58
- package/speechflow-cli/src/speechflow-node-t2t-sentence.ts +10 -10
- package/speechflow-cli/src/speechflow-node-t2t-subtitle.ts +15 -16
- package/speechflow-cli/src/speechflow-node-t2t-transformers.ts +27 -32
- package/speechflow-cli/src/speechflow-node-x2x-filter.ts +20 -16
- package/speechflow-cli/src/speechflow-node-x2x-trace.ts +20 -19
- package/speechflow-cli/src/speechflow-node-xio-device.ts +15 -23
- package/speechflow-cli/src/speechflow-node-xio-mqtt.ts +23 -16
- package/speechflow-cli/src/speechflow-node-xio-websocket.ts +19 -19
- package/speechflow-cli/src/speechflow-node.ts +21 -8
- package/speechflow-cli/src/speechflow-utils-audio-wt.ts +172 -0
- package/speechflow-cli/src/speechflow-utils-audio.ts +147 -0
- package/speechflow-cli/src/speechflow-utils.ts +125 -32
- package/speechflow-cli/src/speechflow.ts +74 -17
- package/speechflow-ui-db/dst/index.js +31 -31
- package/speechflow-ui-db/etc/eslint.mjs +0 -1
- package/speechflow-ui-db/etc/tsc-client.json +3 -3
- package/speechflow-ui-db/package.json +11 -10
- package/speechflow-ui-db/src/app.vue +20 -6
- package/speechflow-ui-st/dst/index.js +26 -26
- package/speechflow-ui-st/etc/eslint.mjs +0 -1
- package/speechflow-ui-st/etc/tsc-client.json +3 -3
- package/speechflow-ui-st/package.json +11 -10
- 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
|
|
58
|
-
if (
|
|
59
|
-
balance +=
|
|
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.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
166
|
-
|
|
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
|
|
233
|
+
this.push(chunk)
|
|
232
234
|
}
|
|
233
235
|
}).catch((error) => {
|
|
234
236
|
if (!self.destroyed)
|