speechflow 0.9.4 → 0.9.7
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 +19 -0
- package/README.md +227 -54
- package/dst/speechflow-node-a2a-ffmpeg.d.ts +13 -0
- package/dst/speechflow-node-a2a-ffmpeg.js +152 -0
- package/dst/speechflow-node-a2a-wav.d.ts +11 -0
- package/dst/speechflow-node-a2a-wav.js +170 -0
- package/dst/speechflow-node-a2t-deepgram.d.ts +12 -0
- package/dst/speechflow-node-a2t-deepgram.js +220 -0
- package/dst/speechflow-node-deepgram.d.ts +3 -1
- package/dst/speechflow-node-deepgram.js +86 -22
- package/dst/speechflow-node-deepl.d.ts +3 -1
- package/dst/speechflow-node-deepl.js +25 -20
- package/dst/speechflow-node-device.d.ts +3 -1
- package/dst/speechflow-node-device.js +53 -2
- package/dst/speechflow-node-elevenlabs.d.ts +4 -1
- package/dst/speechflow-node-elevenlabs.js +88 -49
- package/dst/speechflow-node-ffmpeg.d.ts +3 -1
- package/dst/speechflow-node-ffmpeg.js +42 -4
- package/dst/speechflow-node-file.d.ts +3 -1
- package/dst/speechflow-node-file.js +84 -13
- package/dst/speechflow-node-format.d.ts +11 -0
- package/dst/speechflow-node-format.js +80 -0
- package/dst/speechflow-node-gemma.d.ts +3 -1
- package/dst/speechflow-node-gemma.js +84 -23
- package/dst/speechflow-node-mqtt.d.ts +13 -0
- package/dst/speechflow-node-mqtt.js +181 -0
- package/dst/speechflow-node-opus.d.ts +12 -0
- package/dst/speechflow-node-opus.js +135 -0
- package/dst/speechflow-node-subtitle.d.ts +12 -0
- package/dst/speechflow-node-subtitle.js +96 -0
- package/dst/speechflow-node-t2a-elevenlabs.d.ts +13 -0
- package/dst/speechflow-node-t2a-elevenlabs.js +182 -0
- package/dst/speechflow-node-t2t-deepl.d.ts +12 -0
- package/dst/speechflow-node-t2t-deepl.js +133 -0
- package/dst/speechflow-node-t2t-format.d.ts +11 -0
- package/dst/speechflow-node-t2t-format.js +80 -0
- package/dst/speechflow-node-t2t-gemma.d.ts +13 -0
- package/dst/speechflow-node-t2t-gemma.js +213 -0
- package/dst/speechflow-node-t2t-opus.d.ts +12 -0
- package/dst/speechflow-node-t2t-opus.js +135 -0
- package/dst/speechflow-node-t2t-subtitle.d.ts +12 -0
- package/dst/speechflow-node-t2t-subtitle.js +96 -0
- package/dst/speechflow-node-trace.d.ts +11 -0
- package/dst/speechflow-node-trace.js +88 -0
- package/dst/speechflow-node-wav.d.ts +11 -0
- package/dst/speechflow-node-wav.js +170 -0
- package/dst/speechflow-node-websocket.d.ts +3 -1
- package/dst/speechflow-node-websocket.js +149 -49
- package/dst/speechflow-node-whisper-common.d.ts +34 -0
- package/dst/speechflow-node-whisper-common.js +7 -0
- package/dst/speechflow-node-whisper-ggml.d.ts +1 -0
- package/dst/speechflow-node-whisper-ggml.js +97 -0
- package/dst/speechflow-node-whisper-onnx.d.ts +1 -0
- package/dst/speechflow-node-whisper-onnx.js +131 -0
- package/dst/speechflow-node-whisper-worker-ggml.d.ts +1 -0
- package/dst/speechflow-node-whisper-worker-ggml.js +97 -0
- package/dst/speechflow-node-whisper-worker-onnx.d.ts +1 -0
- package/dst/speechflow-node-whisper-worker-onnx.js +131 -0
- package/dst/speechflow-node-whisper-worker.d.ts +1 -0
- package/dst/speechflow-node-whisper-worker.js +116 -0
- package/dst/speechflow-node-whisper-worker2.d.ts +1 -0
- package/dst/speechflow-node-whisper-worker2.js +82 -0
- package/dst/speechflow-node-whisper.d.ts +19 -0
- package/dst/speechflow-node-whisper.js +604 -0
- package/dst/speechflow-node-x2x-trace.d.ts +11 -0
- package/dst/speechflow-node-x2x-trace.js +88 -0
- package/dst/speechflow-node-xio-device.d.ts +13 -0
- package/dst/speechflow-node-xio-device.js +205 -0
- package/dst/speechflow-node-xio-file.d.ts +11 -0
- package/dst/speechflow-node-xio-file.js +176 -0
- package/dst/speechflow-node-xio-mqtt.d.ts +13 -0
- package/dst/speechflow-node-xio-mqtt.js +181 -0
- package/dst/speechflow-node-xio-websocket.d.ts +13 -0
- package/dst/speechflow-node-xio-websocket.js +275 -0
- package/dst/speechflow-node.d.ts +25 -7
- package/dst/speechflow-node.js +74 -9
- package/dst/speechflow-utils.d.ts +23 -0
- package/dst/speechflow-utils.js +194 -0
- package/dst/speechflow.js +146 -43
- package/etc/biome.jsonc +12 -4
- package/etc/stx.conf +65 -0
- package/package.d/@ericedouard+vad-node-realtime+0.2.0.patch +18 -0
- package/package.json +49 -31
- package/sample.yaml +61 -23
- package/src/lib.d.ts +6 -1
- package/src/{speechflow-node-ffmpeg.ts → speechflow-node-a2a-ffmpeg.ts} +10 -4
- package/src/speechflow-node-a2a-wav.ts +143 -0
- package/src/speechflow-node-a2t-deepgram.ts +199 -0
- package/src/speechflow-node-t2a-elevenlabs.ts +160 -0
- package/src/{speechflow-node-deepl.ts → speechflow-node-t2t-deepl.ts} +36 -25
- package/src/speechflow-node-t2t-format.ts +85 -0
- package/src/{speechflow-node-gemma.ts → speechflow-node-t2t-gemma.ts} +89 -25
- package/src/speechflow-node-t2t-opus.ts +111 -0
- package/src/speechflow-node-t2t-subtitle.ts +101 -0
- package/src/speechflow-node-x2x-trace.ts +92 -0
- package/src/{speechflow-node-device.ts → speechflow-node-xio-device.ts} +25 -3
- package/src/speechflow-node-xio-file.ts +153 -0
- package/src/speechflow-node-xio-mqtt.ts +154 -0
- package/src/speechflow-node-xio-websocket.ts +248 -0
- package/src/speechflow-node.ts +78 -13
- package/src/speechflow-utils.ts +212 -0
- package/src/speechflow.ts +150 -43
- package/etc/nps.yaml +0 -40
- package/src/speechflow-node-deepgram.ts +0 -133
- package/src/speechflow-node-elevenlabs.ts +0 -116
- package/src/speechflow-node-file.ts +0 -108
- package/src/speechflow-node-websocket.ts +0 -179
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
** SpeechFlow - Speech Processing Flow Graph
|
|
4
|
+
** Copyright (c) 2024-2025 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
5
|
+
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
41
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
42
|
+
};
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
/* standard dependencies */
|
|
45
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
46
|
+
const node_stream_1 = __importDefault(require("node:stream"));
|
|
47
|
+
/* external dependencies */
|
|
48
|
+
const Transformers = __importStar(require("@huggingface/transformers"));
|
|
49
|
+
/* internal dependencies */
|
|
50
|
+
const speechflow_node_1 = __importDefault(require("./speechflow-node"));
|
|
51
|
+
/* SpeechFlow node for OPUS text-to-text translation */
|
|
52
|
+
class SpeechFlowNodeOPUS extends speechflow_node_1.default {
|
|
53
|
+
/* declare official node name */
|
|
54
|
+
static name = "opus";
|
|
55
|
+
/* internal state */
|
|
56
|
+
translator = null;
|
|
57
|
+
/* construct node */
|
|
58
|
+
constructor(id, cfg, opts, args) {
|
|
59
|
+
super(id, cfg, opts, args);
|
|
60
|
+
/* declare node configuration parameters */
|
|
61
|
+
this.configure({
|
|
62
|
+
src: { type: "string", pos: 0, val: "de", match: /^(?:de|en)$/ },
|
|
63
|
+
dst: { type: "string", pos: 1, val: "en", match: /^(?:de|en)$/ }
|
|
64
|
+
});
|
|
65
|
+
/* sanity check situation */
|
|
66
|
+
if (this.params.src === this.params.dst)
|
|
67
|
+
throw new Error("source and destination languages cannot be the same");
|
|
68
|
+
/* declare node input/output format */
|
|
69
|
+
this.input = "text";
|
|
70
|
+
this.output = "text";
|
|
71
|
+
}
|
|
72
|
+
/* open node */
|
|
73
|
+
async open() {
|
|
74
|
+
/* instantiate OPUS */
|
|
75
|
+
const model = `onnx-community/opus-mt-${this.params.src}-${this.params.dst}`;
|
|
76
|
+
this.translator = await Transformers.pipeline("translation", model, {
|
|
77
|
+
cache_dir: node_path_1.default.join(this.config.cacheDir, "opus"),
|
|
78
|
+
dtype: "q4",
|
|
79
|
+
device: "gpu"
|
|
80
|
+
});
|
|
81
|
+
if (this.translator === null)
|
|
82
|
+
throw new Error("failed to instantiate translator pipeline");
|
|
83
|
+
/* provide text-to-text translation */
|
|
84
|
+
const translate = async (text) => {
|
|
85
|
+
const result = await this.translator(text);
|
|
86
|
+
return Array.isArray(result) ?
|
|
87
|
+
result[0].translation_text :
|
|
88
|
+
result.translation_text;
|
|
89
|
+
};
|
|
90
|
+
/* establish a duplex stream and connect it to Ollama */
|
|
91
|
+
this.stream = new node_stream_1.default.Transform({
|
|
92
|
+
readableObjectMode: true,
|
|
93
|
+
writableObjectMode: true,
|
|
94
|
+
decodeStrings: false,
|
|
95
|
+
transform(chunk, encoding, callback) {
|
|
96
|
+
if (Buffer.isBuffer(chunk.payload))
|
|
97
|
+
callback(new Error("invalid chunk payload type"));
|
|
98
|
+
else {
|
|
99
|
+
if (chunk.payload === "") {
|
|
100
|
+
this.push(chunk);
|
|
101
|
+
callback();
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
translate(chunk.payload).then((payload) => {
|
|
105
|
+
const chunkNew = chunk.clone();
|
|
106
|
+
chunkNew.payload = payload;
|
|
107
|
+
this.push(chunkNew);
|
|
108
|
+
callback();
|
|
109
|
+
}).catch((err) => {
|
|
110
|
+
callback(err);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
final(callback) {
|
|
116
|
+
this.push(null);
|
|
117
|
+
callback();
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/* close node */
|
|
122
|
+
async close() {
|
|
123
|
+
/* close stream */
|
|
124
|
+
if (this.stream !== null) {
|
|
125
|
+
this.stream.destroy();
|
|
126
|
+
this.stream = null;
|
|
127
|
+
}
|
|
128
|
+
/* shutdown OPUS */
|
|
129
|
+
if (this.translator !== null) {
|
|
130
|
+
this.translator.dispose();
|
|
131
|
+
this.translator = null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
exports.default = SpeechFlowNodeOPUS;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import SpeechFlowNode from "./speechflow-node";
|
|
2
|
+
export default class SpeechFlowNodeSubtitle extends SpeechFlowNode {
|
|
3
|
+
static name: string;
|
|
4
|
+
private sequenceNo;
|
|
5
|
+
constructor(id: string, cfg: {
|
|
6
|
+
[id: string]: any;
|
|
7
|
+
}, opts: {
|
|
8
|
+
[id: string]: any;
|
|
9
|
+
}, args: any[]);
|
|
10
|
+
open(): Promise<void>;
|
|
11
|
+
close(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
** SpeechFlow - Speech Processing Flow Graph
|
|
4
|
+
** Copyright (c) 2024-2025 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
5
|
+
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
6
|
+
*/
|
|
7
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
|
+
};
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
/* standard dependencies */
|
|
12
|
+
const node_stream_1 = __importDefault(require("node:stream"));
|
|
13
|
+
/* internal dependencies */
|
|
14
|
+
const speechflow_node_1 = __importDefault(require("./speechflow-node"));
|
|
15
|
+
/* SpeechFlow node for subtitle (text-to-text) "translations" */
|
|
16
|
+
class SpeechFlowNodeSubtitle extends speechflow_node_1.default {
|
|
17
|
+
/* declare official node name */
|
|
18
|
+
static name = "subtitle";
|
|
19
|
+
/* internal state */
|
|
20
|
+
sequenceNo = 1;
|
|
21
|
+
/* construct node */
|
|
22
|
+
constructor(id, cfg, opts, args) {
|
|
23
|
+
super(id, cfg, opts, args);
|
|
24
|
+
/* declare node configuration parameters */
|
|
25
|
+
this.configure({
|
|
26
|
+
format: { type: "string", pos: 0, val: "srt", match: /^(?:srt|vtt)$/ }
|
|
27
|
+
});
|
|
28
|
+
/* declare node input/output format */
|
|
29
|
+
this.input = "text";
|
|
30
|
+
this.output = "text";
|
|
31
|
+
}
|
|
32
|
+
/* open node */
|
|
33
|
+
async open() {
|
|
34
|
+
this.sequenceNo = 1;
|
|
35
|
+
/* provide text-to-subtitle conversion */
|
|
36
|
+
const convert = async (chunk) => {
|
|
37
|
+
if (typeof chunk.payload !== "string")
|
|
38
|
+
throw new Error("chunk payload type must be string");
|
|
39
|
+
let text = chunk.payload;
|
|
40
|
+
if (this.params.format === "srt") {
|
|
41
|
+
const start = chunk.timestampStart.toFormat("hh:mm:ss,SSS");
|
|
42
|
+
const end = chunk.timestampEnd.toFormat("hh:mm:ss,SSS");
|
|
43
|
+
text = `${this.sequenceNo++}\n` +
|
|
44
|
+
`${start} --> ${end}\n` +
|
|
45
|
+
`${text}\n\n`;
|
|
46
|
+
}
|
|
47
|
+
else if (this.params.format === "vtt") {
|
|
48
|
+
const start = chunk.timestampStart.toFormat("hh:mm:ss.SSS");
|
|
49
|
+
const end = chunk.timestampEnd.toFormat("hh:mm:ss.SSS");
|
|
50
|
+
text = `${this.sequenceNo++}\n` +
|
|
51
|
+
`${start} --> ${end}\n` +
|
|
52
|
+
`${text}\n\n`;
|
|
53
|
+
}
|
|
54
|
+
return text;
|
|
55
|
+
};
|
|
56
|
+
/* establish a duplex stream */
|
|
57
|
+
this.stream = new node_stream_1.default.Transform({
|
|
58
|
+
readableObjectMode: true,
|
|
59
|
+
writableObjectMode: true,
|
|
60
|
+
decodeStrings: false,
|
|
61
|
+
transform(chunk, encoding, callback) {
|
|
62
|
+
if (Buffer.isBuffer(chunk.payload))
|
|
63
|
+
callback(new Error("invalid chunk payload type"));
|
|
64
|
+
else {
|
|
65
|
+
if (chunk.payload === "") {
|
|
66
|
+
this.push(chunk);
|
|
67
|
+
callback();
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
convert(chunk).then((payload) => {
|
|
71
|
+
const chunkNew = chunk.clone();
|
|
72
|
+
chunkNew.payload = payload;
|
|
73
|
+
this.push(chunkNew);
|
|
74
|
+
callback();
|
|
75
|
+
}).catch((err) => {
|
|
76
|
+
callback(err);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
final(callback) {
|
|
82
|
+
this.push(null);
|
|
83
|
+
callback();
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
/* open node */
|
|
88
|
+
async close() {
|
|
89
|
+
/* close stream */
|
|
90
|
+
if (this.stream !== null) {
|
|
91
|
+
this.stream.destroy();
|
|
92
|
+
this.stream = null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
exports.default = SpeechFlowNodeSubtitle;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import SpeechFlowNode from "./speechflow-node";
|
|
2
|
+
export default class SpeechFlowNodeTrace extends SpeechFlowNode {
|
|
3
|
+
static name: string;
|
|
4
|
+
constructor(id: string, cfg: {
|
|
5
|
+
[id: string]: any;
|
|
6
|
+
}, opts: {
|
|
7
|
+
[id: string]: any;
|
|
8
|
+
}, args: any[]);
|
|
9
|
+
open(): Promise<void>;
|
|
10
|
+
close(): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
** SpeechFlow - Speech Processing Flow Graph
|
|
4
|
+
** Copyright (c) 2024-2025 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
5
|
+
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
6
|
+
*/
|
|
7
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
|
+
};
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
/* standard dependencies */
|
|
12
|
+
const node_stream_1 = __importDefault(require("node:stream"));
|
|
13
|
+
/* internal dependencies */
|
|
14
|
+
const speechflow_node_1 = __importDefault(require("./speechflow-node"));
|
|
15
|
+
/* SpeechFlow node for data flow tracing */
|
|
16
|
+
class SpeechFlowNodeTrace extends speechflow_node_1.default {
|
|
17
|
+
/* declare official node name */
|
|
18
|
+
static name = "trace";
|
|
19
|
+
/* construct node */
|
|
20
|
+
constructor(id, cfg, opts, args) {
|
|
21
|
+
super(id, cfg, opts, args);
|
|
22
|
+
/* declare node configuration parameters */
|
|
23
|
+
this.configure({
|
|
24
|
+
type: { type: "string", pos: 0, val: "audio", match: /^(?:audio|text)$/ },
|
|
25
|
+
name: { type: "string", pos: 1 }
|
|
26
|
+
});
|
|
27
|
+
/* declare node input/output format */
|
|
28
|
+
this.input = this.params.type;
|
|
29
|
+
this.output = this.params.type;
|
|
30
|
+
}
|
|
31
|
+
/* open node */
|
|
32
|
+
async open() {
|
|
33
|
+
/* wrapper for local logging */
|
|
34
|
+
const log = (level, msg) => {
|
|
35
|
+
if (this.params.name !== undefined)
|
|
36
|
+
this.log(level, `[${this.params.name}]: ${msg}`);
|
|
37
|
+
else
|
|
38
|
+
this.log(level, msg);
|
|
39
|
+
};
|
|
40
|
+
/* provide Duplex stream and internally attach to Deepgram API */
|
|
41
|
+
const type = this.params.type;
|
|
42
|
+
this.stream = new node_stream_1.default.Transform({
|
|
43
|
+
writableObjectMode: true,
|
|
44
|
+
readableObjectMode: true,
|
|
45
|
+
decodeStrings: false,
|
|
46
|
+
transform(chunk, encoding, callback) {
|
|
47
|
+
let error;
|
|
48
|
+
const fmt = (t) => t.toFormat("hh:mm:ss.SSS");
|
|
49
|
+
if (Buffer.isBuffer(chunk.payload)) {
|
|
50
|
+
if (type === "audio")
|
|
51
|
+
log("info", `writing ${type} chunk: start=${fmt(chunk.timestampStart)} ` +
|
|
52
|
+
`end=${fmt(chunk.timestampEnd)} kind=${chunk.kind} type=${chunk.type} ` +
|
|
53
|
+
`payload-type=Buffer payload-bytes=${chunk.payload.byteLength}`);
|
|
54
|
+
else
|
|
55
|
+
error = new Error(`writing ${type} chunk: seen Buffer instead of String chunk type`);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
if (type === "text")
|
|
59
|
+
log("info", `writing ${type} chunk: start=${fmt(chunk.timestampStart)} ` +
|
|
60
|
+
`end=${fmt(chunk.timestampEnd)} kind=${chunk.kind} type=${chunk.type}` +
|
|
61
|
+
`payload-type=String payload-length=${chunk.payload.length} ` +
|
|
62
|
+
`payload-encoding=${encoding} payload-content="${chunk.payload.toString()}"`);
|
|
63
|
+
else
|
|
64
|
+
error = new Error(`writing ${type} chunk: seen String instead of Buffer chunk type`);
|
|
65
|
+
}
|
|
66
|
+
if (error !== undefined)
|
|
67
|
+
callback(error);
|
|
68
|
+
else {
|
|
69
|
+
this.push(chunk, encoding);
|
|
70
|
+
callback();
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
final(callback) {
|
|
74
|
+
this.push(null);
|
|
75
|
+
callback();
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/* close node */
|
|
80
|
+
async close() {
|
|
81
|
+
/* close stream */
|
|
82
|
+
if (this.stream !== null) {
|
|
83
|
+
this.stream.destroy();
|
|
84
|
+
this.stream = null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
exports.default = SpeechFlowNodeTrace;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import SpeechFlowNode from "./speechflow-node";
|
|
2
|
+
export default class SpeechFlowNodeWAV extends SpeechFlowNode {
|
|
3
|
+
static name: string;
|
|
4
|
+
constructor(id: string, cfg: {
|
|
5
|
+
[id: string]: any;
|
|
6
|
+
}, opts: {
|
|
7
|
+
[id: string]: any;
|
|
8
|
+
}, args: any[]);
|
|
9
|
+
open(): Promise<void>;
|
|
10
|
+
close(): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
** SpeechFlow - Speech Processing Flow Graph
|
|
4
|
+
** Copyright (c) 2024-2025 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
5
|
+
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
41
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
42
|
+
};
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
/* standard dependencies */
|
|
45
|
+
const node_stream_1 = __importDefault(require("node:stream"));
|
|
46
|
+
/* external dependencies */
|
|
47
|
+
const wav_1 = __importDefault(require("wav"));
|
|
48
|
+
/* internal dependencies */
|
|
49
|
+
const speechflow_node_1 = __importDefault(require("./speechflow-node"));
|
|
50
|
+
const utils = __importStar(require("./speechflow-utils"));
|
|
51
|
+
/* utility class for wrapping a custom stream into a regular Transform stream */
|
|
52
|
+
class StreamWrapper extends node_stream_1.default.Transform {
|
|
53
|
+
foreignStream;
|
|
54
|
+
constructor(foreignStream, options = {}) {
|
|
55
|
+
options.readableObjectMode = true;
|
|
56
|
+
options.writableObjectMode = true;
|
|
57
|
+
super(options);
|
|
58
|
+
this.foreignStream = foreignStream;
|
|
59
|
+
this.foreignStream.on("data", (chunk) => {
|
|
60
|
+
this.push(chunk);
|
|
61
|
+
});
|
|
62
|
+
this.foreignStream.on("error", (err) => {
|
|
63
|
+
this.emit("error", err);
|
|
64
|
+
});
|
|
65
|
+
this.foreignStream.on("end", () => {
|
|
66
|
+
this.push(null);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
_transform(chunk, encoding, callback) {
|
|
70
|
+
try {
|
|
71
|
+
const canContinue = this.foreignStream.write(chunk);
|
|
72
|
+
if (canContinue)
|
|
73
|
+
callback();
|
|
74
|
+
else
|
|
75
|
+
this.foreignStream.once("drain", callback);
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
callback(err);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
_flush(callback) {
|
|
82
|
+
try {
|
|
83
|
+
if (typeof this.foreignStream.end === "function")
|
|
84
|
+
this.foreignStream.end();
|
|
85
|
+
callback();
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
callback(err);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/* SpeechFlow node for WAV format conversion */
|
|
93
|
+
class SpeechFlowNodeWAV extends speechflow_node_1.default {
|
|
94
|
+
/* declare official node name */
|
|
95
|
+
static name = "wav";
|
|
96
|
+
/* construct node */
|
|
97
|
+
constructor(id, cfg, opts, args) {
|
|
98
|
+
super(id, cfg, opts, args);
|
|
99
|
+
/* declare node configuration parameters */
|
|
100
|
+
this.configure({
|
|
101
|
+
mode: { type: "string", pos: 1, val: "encode", match: /^(?:encode|decode)$/ }
|
|
102
|
+
});
|
|
103
|
+
/* declare node input/output format */
|
|
104
|
+
this.input = "audio";
|
|
105
|
+
this.output = "audio";
|
|
106
|
+
}
|
|
107
|
+
/* open node */
|
|
108
|
+
async open() {
|
|
109
|
+
if (this.params.mode === "encode") {
|
|
110
|
+
/* convert raw/PCM to WAV/PCM */
|
|
111
|
+
/* NOTICE: as this is a continuous stream, the resulting WAV header is not 100%
|
|
112
|
+
conforming to the WAV standard, as it has to use a zero duration information.
|
|
113
|
+
This cannot be changed in a stream-based processing. */
|
|
114
|
+
const writer = new wav_1.default.Writer({
|
|
115
|
+
format: 0x0001 /* PCM */,
|
|
116
|
+
channels: this.config.audioChannels,
|
|
117
|
+
sampleRate: this.config.audioSampleRate,
|
|
118
|
+
bitDepth: this.config.audioBitDepth
|
|
119
|
+
});
|
|
120
|
+
this.stream = new StreamWrapper(writer);
|
|
121
|
+
}
|
|
122
|
+
else if (this.params.mode === "decode") {
|
|
123
|
+
/* convert WAV/PCM to raw/PCM */
|
|
124
|
+
const reader = new wav_1.default.Reader();
|
|
125
|
+
reader.on("format", (format) => {
|
|
126
|
+
this.log("info", `WAV audio stream: format=${format.audioFormat === 0x0001 ? "PCM" :
|
|
127
|
+
"0x" + format.audioFormat.toString(16).padStart(4, "0")} ` +
|
|
128
|
+
`bitDepth=${format.bitDepth} ` +
|
|
129
|
+
`signed=${format.signed ? "yes" : "no"} ` +
|
|
130
|
+
`endian=${format.endianness} ` +
|
|
131
|
+
`sampleRate=${format.sampleRate} ` +
|
|
132
|
+
`channels=${format.channels}`);
|
|
133
|
+
if (format.audioFormat !== 0x0001 /* PCM */)
|
|
134
|
+
throw new Error("WAV not based on PCM format");
|
|
135
|
+
if (format.bitDepth !== 16)
|
|
136
|
+
throw new Error("WAV not based on 16 bit samples");
|
|
137
|
+
if (!format.signed)
|
|
138
|
+
throw new Error("WAV not based on signed integers");
|
|
139
|
+
if (format.endianness !== "LE")
|
|
140
|
+
throw new Error("WAV not based on little endianness");
|
|
141
|
+
if (format.sampleRate !== 48000)
|
|
142
|
+
throw new Error("WAV not based on 48Khz sample rate");
|
|
143
|
+
if (format.channels !== 1)
|
|
144
|
+
throw new Error("WAV not based on mono channel");
|
|
145
|
+
});
|
|
146
|
+
this.stream = new StreamWrapper(reader);
|
|
147
|
+
}
|
|
148
|
+
else
|
|
149
|
+
throw new Error(`invalid operation mode "${this.params.mode}"`);
|
|
150
|
+
/* convert regular stream into object-mode stream */
|
|
151
|
+
const wrapper1 = utils.createTransformStreamForWritableSide();
|
|
152
|
+
const wrapper2 = utils.createTransformStreamForReadableSide("audio", () => this.timeZero);
|
|
153
|
+
this.stream = node_stream_1.default.compose(wrapper1, this.stream, wrapper2);
|
|
154
|
+
}
|
|
155
|
+
/* close node */
|
|
156
|
+
async close() {
|
|
157
|
+
/* shutdown stream */
|
|
158
|
+
if (this.stream !== null) {
|
|
159
|
+
await new Promise((resolve) => {
|
|
160
|
+
if (this.stream instanceof node_stream_1.default.Duplex)
|
|
161
|
+
this.stream.end(() => { resolve(); });
|
|
162
|
+
else
|
|
163
|
+
resolve();
|
|
164
|
+
});
|
|
165
|
+
this.stream.destroy();
|
|
166
|
+
this.stream = null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
exports.default = SpeechFlowNodeWAV;
|
|
@@ -3,7 +3,9 @@ export default class SpeechFlowNodeWebsocket extends SpeechFlowNode {
|
|
|
3
3
|
static name: string;
|
|
4
4
|
private server;
|
|
5
5
|
private client;
|
|
6
|
-
constructor(id: string,
|
|
6
|
+
constructor(id: string, cfg: {
|
|
7
|
+
[id: string]: any;
|
|
8
|
+
}, opts: {
|
|
7
9
|
[id: string]: any;
|
|
8
10
|
}, args: any[]);
|
|
9
11
|
open(): Promise<void>;
|