speechflow 1.7.1 → 2.0.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 +18 -0
- package/README.md +387 -119
- package/etc/claude.md +5 -5
- package/etc/speechflow.yaml +2 -2
- package/package.json +3 -3
- package/speechflow-cli/dst/speechflow-main-graph.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-main-graph.js +28 -5
- package/speechflow-cli/dst/speechflow-main-graph.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-wav.js +24 -4
- package/speechflow-cli/dst/speechflow-node-a2a-wav.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-google.d.ts +17 -0
- package/speechflow-cli/dst/speechflow-node-a2t-google.js +320 -0
- package/speechflow-cli/dst/speechflow-node-a2t-google.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-t2a-google.d.ts +15 -0
- package/speechflow-cli/dst/speechflow-node-t2a-google.js +218 -0
- package/speechflow-cli/dst/speechflow-node-t2a-google.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-t2a-openai.d.ts +15 -0
- package/speechflow-cli/dst/speechflow-node-t2a-openai.js +195 -0
- package/speechflow-cli/dst/speechflow-node-t2a-openai.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-t2a-supertonic.d.ts +17 -0
- package/speechflow-cli/dst/speechflow-node-t2a-supertonic.js +608 -0
- package/speechflow-cli/dst/speechflow-node-t2a-supertonic.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-t2t-amazon.js.map +1 -1
- package/speechflow-cli/dst/{speechflow-node-t2t-transformers.d.ts → speechflow-node-t2t-opus.d.ts} +1 -3
- package/speechflow-cli/dst/speechflow-node-t2t-opus.js +159 -0
- package/speechflow-cli/dst/speechflow-node-t2t-opus.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-t2t-profanity.d.ts +11 -0
- package/speechflow-cli/dst/speechflow-node-t2t-profanity.js +118 -0
- package/speechflow-cli/dst/speechflow-node-t2t-profanity.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-t2t-punctuation.d.ts +13 -0
- package/speechflow-cli/dst/speechflow-node-t2t-punctuation.js +220 -0
- package/speechflow-cli/dst/speechflow-node-t2t-punctuation.js.map +1 -0
- package/speechflow-cli/dst/{speechflow-node-t2t-openai.d.ts → speechflow-node-t2t-spellcheck.d.ts} +2 -2
- package/speechflow-cli/dst/{speechflow-node-t2t-openai.js → speechflow-node-t2t-spellcheck.js} +47 -99
- package/speechflow-cli/dst/speechflow-node-t2t-spellcheck.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js +3 -6
- package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-summary.d.ts +16 -0
- package/speechflow-cli/dst/speechflow-node-t2t-summary.js +241 -0
- package/speechflow-cli/dst/speechflow-node-t2t-summary.js.map +1 -0
- package/speechflow-cli/dst/{speechflow-node-t2t-ollama.d.ts → speechflow-node-t2t-translate.d.ts} +2 -2
- package/speechflow-cli/dst/{speechflow-node-t2t-transformers.js → speechflow-node-t2t-translate.js} +53 -115
- package/speechflow-cli/dst/speechflow-node-t2t-translate.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-xio-exec.d.ts +12 -0
- package/speechflow-cli/dst/speechflow-node-xio-exec.js +223 -0
- package/speechflow-cli/dst/speechflow-node-xio-exec.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-xio-file.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-node-xio-file.js +79 -66
- package/speechflow-cli/dst/speechflow-node-xio-file.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-vban.d.ts +17 -0
- package/speechflow-cli/dst/speechflow-node-xio-vban.js +330 -0
- package/speechflow-cli/dst/speechflow-node-xio-vban.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-xio-webrtc.d.ts +39 -0
- package/speechflow-cli/dst/speechflow-node-xio-webrtc.js +500 -0
- package/speechflow-cli/dst/speechflow-node-xio-webrtc.js.map +1 -0
- package/speechflow-cli/dst/speechflow-util-audio.js +4 -5
- package/speechflow-cli/dst/speechflow-util-audio.js.map +1 -1
- package/speechflow-cli/dst/speechflow-util-error.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-util-error.js +5 -0
- package/speechflow-cli/dst/speechflow-util-error.js.map +1 -1
- package/speechflow-cli/dst/speechflow-util-llm.d.ts +35 -0
- package/speechflow-cli/dst/speechflow-util-llm.js +363 -0
- package/speechflow-cli/dst/speechflow-util-llm.js.map +1 -0
- package/speechflow-cli/dst/speechflow-util.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-util.js +1 -0
- package/speechflow-cli/dst/speechflow-util.js.map +1 -1
- package/speechflow-cli/etc/oxlint.jsonc +2 -1
- package/speechflow-cli/package.json +34 -17
- package/speechflow-cli/src/lib.d.ts +5 -0
- package/speechflow-cli/src/speechflow-main-graph.ts +31 -5
- package/speechflow-cli/src/speechflow-node-a2a-wav.ts +24 -4
- package/speechflow-cli/src/speechflow-node-a2t-google.ts +322 -0
- package/speechflow-cli/src/speechflow-node-t2a-google.ts +206 -0
- package/speechflow-cli/src/speechflow-node-t2a-openai.ts +179 -0
- package/speechflow-cli/src/speechflow-node-t2a-supertonic.ts +701 -0
- package/speechflow-cli/src/speechflow-node-t2t-amazon.ts +2 -1
- package/speechflow-cli/src/speechflow-node-t2t-opus.ts +136 -0
- package/speechflow-cli/src/speechflow-node-t2t-profanity.ts +93 -0
- package/speechflow-cli/src/speechflow-node-t2t-punctuation.ts +201 -0
- package/speechflow-cli/src/{speechflow-node-t2t-openai.ts → speechflow-node-t2t-spellcheck.ts} +48 -107
- package/speechflow-cli/src/speechflow-node-t2t-subtitle.ts +3 -6
- package/speechflow-cli/src/speechflow-node-t2t-summary.ts +229 -0
- package/speechflow-cli/src/speechflow-node-t2t-translate.ts +181 -0
- package/speechflow-cli/src/speechflow-node-xio-exec.ts +210 -0
- package/speechflow-cli/src/speechflow-node-xio-file.ts +92 -79
- package/speechflow-cli/src/speechflow-node-xio-vban.ts +325 -0
- package/speechflow-cli/src/speechflow-node-xio-webrtc.ts +533 -0
- package/speechflow-cli/src/speechflow-util-audio.ts +5 -5
- package/speechflow-cli/src/speechflow-util-error.ts +9 -0
- package/speechflow-cli/src/speechflow-util-llm.ts +367 -0
- package/speechflow-cli/src/speechflow-util.ts +1 -0
- package/speechflow-ui-db/package.json +9 -9
- package/speechflow-ui-st/package.json +9 -9
- package/speechflow-cli/dst/speechflow-node-t2t-ollama.js +0 -293
- package/speechflow-cli/dst/speechflow-node-t2t-ollama.js.map +0 -1
- package/speechflow-cli/dst/speechflow-node-t2t-openai.js.map +0 -1
- package/speechflow-cli/dst/speechflow-node-t2t-transformers.js.map +0 -1
- package/speechflow-cli/src/speechflow-node-t2t-ollama.ts +0 -281
- package/speechflow-cli/src/speechflow-node-t2t-transformers.ts +0 -247
|
@@ -0,0 +1,179 @@
|
|
|
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 OpenAI from "openai"
|
|
12
|
+
import { Duration } from "luxon"
|
|
13
|
+
import SpeexResampler from "speex-resampler"
|
|
14
|
+
|
|
15
|
+
/* internal dependencies */
|
|
16
|
+
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
17
|
+
import * as util from "./speechflow-util"
|
|
18
|
+
|
|
19
|
+
/* SpeechFlow node for OpenAI text-to-speech conversion */
|
|
20
|
+
export default class SpeechFlowNodeT2AOpenAI extends SpeechFlowNode {
|
|
21
|
+
/* declare official node name */
|
|
22
|
+
public static name = "t2a-openai"
|
|
23
|
+
|
|
24
|
+
/* internal state */
|
|
25
|
+
private openai: OpenAI | null = null
|
|
26
|
+
private resampler: SpeexResampler | null = null
|
|
27
|
+
private closing = false
|
|
28
|
+
|
|
29
|
+
/* construct node */
|
|
30
|
+
constructor (id: string, cfg: { [ id: string ]: any }, opts: { [ id: string ]: any }, args: any[]) {
|
|
31
|
+
super(id, cfg, opts, args)
|
|
32
|
+
|
|
33
|
+
/* declare node configuration parameters */
|
|
34
|
+
this.configure({
|
|
35
|
+
key: { type: "string", val: process.env.SPEECHFLOW_OPENAI_KEY },
|
|
36
|
+
api: { type: "string", val: "https://api.openai.com/v1", match: /^https?:\/\/.+/ },
|
|
37
|
+
voice: { type: "string", val: "alloy", pos: 0, match: /^(?:alloy|echo|fable|onyx|nova|shimmer)$/ },
|
|
38
|
+
model: { type: "string", val: "tts-1", pos: 1, match: /^(?:tts-1|tts-1-hd)$/ },
|
|
39
|
+
speed: { type: "number", val: 1.0, pos: 2, match: (n: number) => n >= 0.25 && n <= 4.0 }
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
/* sanity check parameters */
|
|
43
|
+
if (!this.params.key)
|
|
44
|
+
throw new Error("OpenAI API key not configured")
|
|
45
|
+
|
|
46
|
+
/* declare node input/output format */
|
|
47
|
+
this.input = "text"
|
|
48
|
+
this.output = "audio"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* one-time status of node */
|
|
52
|
+
async status () {
|
|
53
|
+
return {}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* open node */
|
|
57
|
+
async open () {
|
|
58
|
+
/* clear destruction flag */
|
|
59
|
+
this.closing = false
|
|
60
|
+
|
|
61
|
+
/* establish OpenAI API connection */
|
|
62
|
+
this.openai = new OpenAI({
|
|
63
|
+
baseURL: this.params.api,
|
|
64
|
+
apiKey: this.params.key,
|
|
65
|
+
timeout: 60000
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
/* establish resampler from OpenAI's 24Khz PCM output
|
|
69
|
+
to our standard audio sample rate (48KHz) */
|
|
70
|
+
this.resampler = new SpeexResampler(1, 24000, this.config.audioSampleRate, 7)
|
|
71
|
+
|
|
72
|
+
/* perform text-to-speech operation with OpenAI API */
|
|
73
|
+
const textToSpeech = async (text: string) => {
|
|
74
|
+
this.log("info", `OpenAI TTS: send text "${text}"`)
|
|
75
|
+
const response = await this.openai!.audio.speech.create({
|
|
76
|
+
model: this.params.model,
|
|
77
|
+
voice: this.params.voice,
|
|
78
|
+
input: text,
|
|
79
|
+
response_format: "pcm",
|
|
80
|
+
speed: this.params.speed
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
/* convert response to buffer (PCM 24kHz, 16-bit, little-endian) */
|
|
84
|
+
const arrayBuffer = await response.arrayBuffer()
|
|
85
|
+
const buffer = Buffer.from(arrayBuffer)
|
|
86
|
+
this.log("info", `OpenAI TTS: received audio (buffer length: ${buffer.byteLength})`)
|
|
87
|
+
|
|
88
|
+
/* resample from 24kHz to 48kHz */
|
|
89
|
+
const bufferResampled = this.resampler!.processChunk(buffer)
|
|
90
|
+
this.log("info", `OpenAI TTS: forwarding resampled audio (buffer length: ${bufferResampled.byteLength})`)
|
|
91
|
+
return bufferResampled
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* create transform stream and connect it to the OpenAI API */
|
|
95
|
+
const self = this
|
|
96
|
+
this.stream = new Stream.Transform({
|
|
97
|
+
writableObjectMode: true,
|
|
98
|
+
readableObjectMode: true,
|
|
99
|
+
decodeStrings: false,
|
|
100
|
+
highWaterMark: 1,
|
|
101
|
+
async transform (chunk: SpeechFlowChunk, encoding, callback) {
|
|
102
|
+
if (self.closing)
|
|
103
|
+
callback(new Error("stream already destroyed"))
|
|
104
|
+
else if (Buffer.isBuffer(chunk.payload))
|
|
105
|
+
callback(new Error("invalid chunk payload type"))
|
|
106
|
+
else if (chunk.payload === "") {
|
|
107
|
+
/* pass through empty chunks */
|
|
108
|
+
this.push(chunk)
|
|
109
|
+
callback()
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
let processTimeout: ReturnType<typeof setTimeout> | null = setTimeout(() => {
|
|
113
|
+
processTimeout = null
|
|
114
|
+
callback(new Error("OpenAI TTS API timeout"))
|
|
115
|
+
}, 60 * 1000)
|
|
116
|
+
const clearProcessTimeout = () => {
|
|
117
|
+
if (processTimeout !== null) {
|
|
118
|
+
clearTimeout(processTimeout)
|
|
119
|
+
processTimeout = null
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
if (self.closing) {
|
|
124
|
+
clearProcessTimeout()
|
|
125
|
+
callback(new Error("stream destroyed during processing"))
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
const buffer = await textToSpeech(chunk.payload as string)
|
|
129
|
+
if (self.closing) {
|
|
130
|
+
clearProcessTimeout()
|
|
131
|
+
callback(new Error("stream destroyed during processing"))
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* calculate actual audio duration from PCM buffer size */
|
|
136
|
+
const durationMs = util.audioBufferDuration(buffer,
|
|
137
|
+
self.config.audioSampleRate, self.config.audioBitDepth) * 1000
|
|
138
|
+
|
|
139
|
+
/* create new chunk with recalculated timestamps */
|
|
140
|
+
const chunkNew = chunk.clone()
|
|
141
|
+
chunkNew.type = "audio"
|
|
142
|
+
chunkNew.payload = buffer
|
|
143
|
+
chunkNew.timestampEnd = Duration.fromMillis(chunkNew.timestampStart.toMillis() + durationMs)
|
|
144
|
+
clearProcessTimeout()
|
|
145
|
+
this.push(chunkNew)
|
|
146
|
+
callback()
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
clearProcessTimeout()
|
|
150
|
+
callback(util.ensureError(error, "OpenAI TTS processing failed"))
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
final (callback) {
|
|
155
|
+
callback()
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* close node */
|
|
161
|
+
async close () {
|
|
162
|
+
/* indicate closing */
|
|
163
|
+
this.closing = true
|
|
164
|
+
|
|
165
|
+
/* shutdown stream */
|
|
166
|
+
if (this.stream !== null) {
|
|
167
|
+
await util.destroyStream(this.stream)
|
|
168
|
+
this.stream = null
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* destroy resampler */
|
|
172
|
+
if (this.resampler !== null)
|
|
173
|
+
this.resampler = null
|
|
174
|
+
|
|
175
|
+
/* destroy OpenAI API */
|
|
176
|
+
if (this.openai !== null)
|
|
177
|
+
this.openai = null
|
|
178
|
+
}
|
|
179
|
+
}
|