@nodetool-ai/whisper-cpp 0.0.0-stage → 0.8.3
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/README.md +29 -2
- package/dist/.nodetool-build-stamp +1 -0
- package/dist/audio.d.ts +4 -0
- package/dist/audio.d.ts.map +1 -0
- package/dist/audio.js +38 -0
- package/dist/audio.js.map +1 -0
- package/dist/binding.d.ts +70 -0
- package/dist/binding.d.ts.map +1 -0
- package/dist/binding.js +48 -0
- package/dist/binding.js.map +1 -0
- package/dist/context-cache.d.ts +15 -0
- package/dist/context-cache.d.ts.map +1 -0
- package/dist/context-cache.js +103 -0
- package/dist/context-cache.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/model-discovery.d.ts +7 -0
- package/dist/model-discovery.d.ts.map +1 -0
- package/dist/model-discovery.js +106 -0
- package/dist/model-discovery.js.map +1 -0
- package/dist/nodes/live-transcription.d.ts +36 -0
- package/dist/nodes/live-transcription.d.ts.map +1 -0
- package/dist/nodes/live-transcription.js +212 -0
- package/dist/nodes/live-transcription.js.map +1 -0
- package/dist/whisper-cpp-provider.d.ts +23 -0
- package/dist/whisper-cpp-provider.d.ts.map +1 -0
- package/dist/whisper-cpp-provider.js +101 -0
- package/dist/whisper-cpp-provider.js.map +1 -0
- package/dist/whisper-server-provider.d.ts +19 -0
- package/dist/whisper-server-provider.d.ts.map +1 -0
- package/dist/whisper-server-provider.js +121 -0
- package/dist/whisper-server-provider.js.map +1 -0
- package/package.json +64 -4
package/README.md
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
-
#
|
|
1
|
+
# whisper.cpp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Local speech recognition providers and streaming transcription for NodeTool.
|
|
4
|
+
|
|
5
|
+
Install `@fugood/whisper.node@1.1.3` through the desktop Package Manager.
|
|
6
|
+
Download GGML ASR models from `ggerganov/whisper.cpp` through Models.
|
|
7
|
+
The providers are excluded from the cloud profile.
|
|
8
|
+
|
|
9
|
+
- `whisper_cpp` runs ASR inside the backend. Model ids are absolute paths from
|
|
10
|
+
discovery. `WHISPER_CPP_MODELS_DIR` adds a directory to the Hugging Face hub
|
|
11
|
+
cache scan. `WHISPER_CPP_GPU_BACKEND` accepts `auto`, `metal`, `cuda`,
|
|
12
|
+
`vulkan`, or `cpu`. Restart after changing the backend because the binding
|
|
13
|
+
caches the first native variant loaded.
|
|
14
|
+
- `whisper_cpp_server` posts to `/inference` on `WHISPER_CPP_SERVER_URL` and
|
|
15
|
+
serves the model id `default`.
|
|
16
|
+
- Enable the `whisper-cpp` node pack for `whisper_cpp.LiveTranscription`.
|
|
17
|
+
Supply mono PCM16 audio as base64 chunks. `content_metadata.sample_rate`
|
|
18
|
+
defaults to 16000. A `done: true` chunk ends the stream. Install
|
|
19
|
+
`ggml-org/whisper-vad/ggml-silero-v5.1.2.bin` for speech boundaries, otherwise
|
|
20
|
+
the node uses fixed windows.
|
|
21
|
+
|
|
22
|
+
The binding returns transcription timestamps in milliseconds and VAD times
|
|
23
|
+
in centiseconds. The provider returns ASR timestamps in seconds.
|
|
24
|
+
|
|
25
|
+
Run tests with `npm run test --workspace=packages/whisper-cpp`.
|
|
26
|
+
Set `WHISPER_CPP_TEST_MODEL` to a real model path to enable the native test.
|
|
27
|
+
Set `WHISPER_CPP_TEST_SERVER_URL` to test a running whisper-server serving an
|
|
28
|
+
English-capable model.
|
|
29
|
+
The speech fixture is the public-domain JFK inaugural-address excerpt from
|
|
30
|
+
[whisper.cpp samples](https://github.com/ggml-org/whisper.cpp/blob/master/samples/jfk.wav).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2026-10-12T12:30:13.366Z
|
package/dist/audio.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function samplesToPcm16(samples: Float32Array): ArrayBuffer;
|
|
2
|
+
export declare function decodeToPcm16(bytes: Uint8Array): Promise<ArrayBuffer>;
|
|
3
|
+
export declare function pcm16Base64ToSamples(b64: string, sampleRate: number): Float32Array;
|
|
4
|
+
//# sourceMappingURL=audio.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio.d.ts","sourceRoot":"","sources":["../src/audio.ts"],"names":[],"mappings":"AAKA,wBAAgB,cAAc,CAAC,OAAO,EAAE,YAAY,GAAG,WAAW,CAcjE;AACD,wBAAsB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAK3E;AACD,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,GACjB,YAAY,CAkBd"}
|
package/dist/audio.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { decodeAudioBytesToSamples, resampleLinear } from "@nodetool-ai/transformers-js-nodes";
|
|
2
|
+
export function samplesToPcm16(samples) {
|
|
3
|
+
if (samples.length === 0) {
|
|
4
|
+
throw new Error("Audio input is empty");
|
|
5
|
+
}
|
|
6
|
+
const buffer = new ArrayBuffer(samples.length * 2);
|
|
7
|
+
const view = new DataView(buffer);
|
|
8
|
+
for (let i = 0; i < samples.length; i++) {
|
|
9
|
+
view.setInt16(i * 2, Math.round(Math.max(-1, Math.min(1, samples[i])) * 0x7fff), true);
|
|
10
|
+
}
|
|
11
|
+
return buffer;
|
|
12
|
+
}
|
|
13
|
+
export async function decodeToPcm16(bytes) {
|
|
14
|
+
if (bytes.length === 0) {
|
|
15
|
+
throw new Error("Audio input is empty");
|
|
16
|
+
}
|
|
17
|
+
return samplesToPcm16(await decodeAudioBytesToSamples(bytes, 16000));
|
|
18
|
+
}
|
|
19
|
+
export function pcm16Base64ToSamples(b64, sampleRate) {
|
|
20
|
+
const bytes = Buffer.from(b64, "base64");
|
|
21
|
+
if (bytes.length === 0) {
|
|
22
|
+
throw new Error("Audio input is empty");
|
|
23
|
+
}
|
|
24
|
+
if (bytes.length % 2 !== 0) {
|
|
25
|
+
throw new Error("PCM16 audio must contain complete samples");
|
|
26
|
+
}
|
|
27
|
+
if (!Number.isFinite(sampleRate) || sampleRate <= 0) {
|
|
28
|
+
throw new Error("Audio sample rate must be positive");
|
|
29
|
+
}
|
|
30
|
+
const samples = new Float32Array(bytes.length / 2);
|
|
31
|
+
for (let i = 0; i < samples.length; i++) {
|
|
32
|
+
samples[i] = bytes.readInt16LE(i * 2) / 32768;
|
|
33
|
+
}
|
|
34
|
+
return sampleRate === 16000
|
|
35
|
+
? samples
|
|
36
|
+
: resampleLinear(samples, sampleRate, 16000);
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=audio.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio.js","sourceRoot":"","sources":["../src/audio.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,yBAAyB,EACzB,cAAc,EACf,MAAM,oCAAoC,CAAC;AAE5C,MAAM,UAAU,cAAc,CAAC,OAAqB;IAClD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;IAC1C,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,WAAW,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACnD,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC;IAClC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACxC,IAAI,CAAC,QAAQ,CACX,CAAC,GAAG,CAAC,EACL,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,EAC1D,IAAI,CACL,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AACD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,KAAiB;IACnD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,cAAc,CAAC,MAAM,yBAAyB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;AACvE,CAAC;AACD,MAAM,UAAU,oBAAoB,CAClC,GAAW,EACX,UAAkB;IAElB,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IACzC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;IAC1C,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;IAC/D,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,UAAU,IAAI,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACxD,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,YAAY,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACnD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACxC,OAAO,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC;IAChD,CAAC;IACD,OAAO,UAAU,KAAK,KAAK;QACzB,CAAC,CAAC,OAAO;QACT,CAAC,CAAC,cAAc,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC;AACjD,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export type LibVariant = "default" | "vulkan" | "cuda";
|
|
2
|
+
export interface Segment {
|
|
3
|
+
text: string;
|
|
4
|
+
t0: number;
|
|
5
|
+
t1: number;
|
|
6
|
+
}
|
|
7
|
+
export interface TranscribeOptions {
|
|
8
|
+
language?: string;
|
|
9
|
+
translate?: boolean;
|
|
10
|
+
maxThreads?: number;
|
|
11
|
+
tokenTimestamps?: boolean;
|
|
12
|
+
temperature?: number;
|
|
13
|
+
beamSize?: number;
|
|
14
|
+
prompt?: string;
|
|
15
|
+
onProgress?: (progress: number) => void;
|
|
16
|
+
onNewSegments?: (result: {
|
|
17
|
+
result: string;
|
|
18
|
+
segments: Segment[];
|
|
19
|
+
}) => void;
|
|
20
|
+
}
|
|
21
|
+
export interface TranscribeResult {
|
|
22
|
+
language?: string;
|
|
23
|
+
result: string;
|
|
24
|
+
segments: Segment[];
|
|
25
|
+
isAborted: boolean;
|
|
26
|
+
}
|
|
27
|
+
export interface WhisperContext {
|
|
28
|
+
transcribeData(audio: ArrayBuffer, options?: TranscribeOptions): {
|
|
29
|
+
stop: () => Promise<void>;
|
|
30
|
+
promise: Promise<TranscribeResult>;
|
|
31
|
+
};
|
|
32
|
+
release(): Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
export interface WhisperVadContext {
|
|
35
|
+
detectSpeechData(audio: ArrayBuffer, options?: {
|
|
36
|
+
threshold?: number;
|
|
37
|
+
minSpeechDurationMs?: number;
|
|
38
|
+
minSilenceDurationMs?: number;
|
|
39
|
+
}): Promise<{
|
|
40
|
+
t0: number;
|
|
41
|
+
t1: number;
|
|
42
|
+
}[]>;
|
|
43
|
+
release(): Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
export interface WhisperNodeModule {
|
|
46
|
+
initWhisper(options: {
|
|
47
|
+
filePath: string;
|
|
48
|
+
useGpu?: boolean;
|
|
49
|
+
useFlashAttn?: boolean;
|
|
50
|
+
}, variant?: LibVariant): Promise<WhisperContext>;
|
|
51
|
+
initWhisperVad(options: {
|
|
52
|
+
filePath: string;
|
|
53
|
+
useGpu?: boolean;
|
|
54
|
+
nThreads?: number;
|
|
55
|
+
}, variant?: LibVariant): Promise<WhisperVadContext>;
|
|
56
|
+
}
|
|
57
|
+
export declare const INSTALL_MESSAGE = "The local whisper.cpp provider requires the optional '@fugood/whisper.node' package, which is not installed. Install it from the Package Manager.";
|
|
58
|
+
export declare function loadWhisperNode(): Promise<WhisperNodeModule>;
|
|
59
|
+
/**
|
|
60
|
+
* The whisper.node build to load for a WHISPER_CPP_GPU_BACKEND setting.
|
|
61
|
+
*
|
|
62
|
+
* `cuda` and `vulkan` select that build. `metal` and `cpu` use the default
|
|
63
|
+
* build (Metal on macOS). `auto` (or unset) on Windows and Linux probes the
|
|
64
|
+
* CUDA build, then the Vulkan build, like node-llama-cpp's `auto`: a build
|
|
65
|
+
* whose native library cannot load (no NVIDIA driver, no Vulkan loader) is
|
|
66
|
+
* skipped, and the default CPU build is the last resort. On macOS `auto` is
|
|
67
|
+
* the default build.
|
|
68
|
+
*/
|
|
69
|
+
export declare function resolveVariant(setting: string | undefined): Promise<LibVariant>;
|
|
70
|
+
//# sourceMappingURL=binding.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binding.d.ts","sourceRoot":"","sources":["../src/binding.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAC;AACvD,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;CACZ;AACD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,EAAE,CAAA;KAAE,KAAK,IAAI,CAAC;CAC3E;AACD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;CACpB;AACD,MAAM,WAAW,cAAc;IAC7B,cAAc,CACZ,KAAK,EAAE,WAAW,EAClB,OAAO,CAAC,EAAE,iBAAiB,GAC1B;QACD,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;QAC1B,OAAO,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;KACpC,CAAC;IACF,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AACD,MAAM,WAAW,iBAAiB;IAChC,gBAAgB,CACd,KAAK,EAAE,WAAW,EAClB,OAAO,CAAC,EAAE;QACR,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,mBAAmB,CAAC,EAAE,MAAM,CAAC;QAC7B,oBAAoB,CAAC,EAAE,MAAM,CAAC;KAC/B,GACA,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC,CAAC;IACzC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AACD,MAAM,WAAW,iBAAiB;IAChC,WAAW,CACT,OAAO,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,EACvE,OAAO,CAAC,EAAE,UAAU,GACnB,OAAO,CAAC,cAAc,CAAC,CAAC;IAC3B,cAAc,CACZ,OAAO,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,EAClE,OAAO,CAAC,EAAE,UAAU,GACnB,OAAO,CAAC,iBAAiB,CAAC,CAAC;CAC/B;AACD,eAAO,MAAM,eAAe,sJACyH,CAAC;AAEtJ,wBAAgB,eAAe,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAW5D;AACD;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,MAAM,GAAG,SAAS,GAC1B,OAAO,CAAC,UAAU,CAAC,CASrB"}
|
package/dist/binding.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { importOptionalModule } from "@nodetool-ai/config";
|
|
2
|
+
import { MissingRuntimePackageError } from "@nodetool-ai/protocol";
|
|
3
|
+
export const INSTALL_MESSAGE = "The local whisper.cpp provider requires the optional '@fugood/whisper.node' package, which is not installed. Install it from the Package Manager.";
|
|
4
|
+
let modulePromise;
|
|
5
|
+
export function loadWhisperNode() {
|
|
6
|
+
modulePromise ??= importOptionalModule("@fugood/whisper.node", { commonJs: true }).catch((cause) => {
|
|
7
|
+
modulePromise = undefined;
|
|
8
|
+
throw new MissingRuntimePackageError(INSTALL_MESSAGE, "whisper-cpp", {
|
|
9
|
+
cause
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
return modulePromise;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The whisper.node build to load for a WHISPER_CPP_GPU_BACKEND setting.
|
|
16
|
+
*
|
|
17
|
+
* `cuda` and `vulkan` select that build. `metal` and `cpu` use the default
|
|
18
|
+
* build (Metal on macOS). `auto` (or unset) on Windows and Linux probes the
|
|
19
|
+
* CUDA build, then the Vulkan build, like node-llama-cpp's `auto`: a build
|
|
20
|
+
* whose native library cannot load (no NVIDIA driver, no Vulkan loader) is
|
|
21
|
+
* skipped, and the default CPU build is the last resort. On macOS `auto` is
|
|
22
|
+
* the default build.
|
|
23
|
+
*/
|
|
24
|
+
export async function resolveVariant(setting) {
|
|
25
|
+
if (setting === "cuda" || setting === "vulkan") {
|
|
26
|
+
return setting;
|
|
27
|
+
}
|
|
28
|
+
if ((setting === undefined || setting === "" || setting === "auto") && process.platform !== "darwin") {
|
|
29
|
+
autoVariant ??= probeGpuVariant();
|
|
30
|
+
return autoVariant;
|
|
31
|
+
}
|
|
32
|
+
return "default";
|
|
33
|
+
}
|
|
34
|
+
let autoVariant;
|
|
35
|
+
/** First GPU build of whisper.node that loads on this machine, else `default`. */
|
|
36
|
+
async function probeGpuVariant() {
|
|
37
|
+
for (const variant of ["cuda", "vulkan"]) {
|
|
38
|
+
try {
|
|
39
|
+
await importOptionalModule(`@fugood/node-whisper-${process.platform}-${process.arch}-${variant}`, { commonJs: true });
|
|
40
|
+
return variant;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Not installed for this platform, or its native library cannot load.
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return "default";
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=binding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binding.js","sourceRoot":"","sources":["../src/binding.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AAwDnE,MAAM,CAAC,MAAM,eAAe,GAC1B,mJAAmJ,CAAC;AACtJ,IAAI,aAAqD,CAAC;AAC1D,MAAM,UAAU,eAAe;IAC7B,aAAa,KAAK,oBAAoB,CACpC,sBAAsB,EACtB,EAAE,QAAQ,EAAE,IAAI,EAAE,CACnB,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;QACzB,aAAa,GAAG,SAAS,CAAC;QAC1B,MAAM,IAAI,0BAA0B,CAAC,eAAe,EAAE,aAAa,EAAE;YACnE,KAAK;SACN,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IACH,OAAO,aAAa,CAAC;AACvB,CAAC;AACD;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,OAA2B;IAE3B,IAAI,OAAO,KAAK,MAAM,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC/C,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,IAAI,CAAC,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,EAAE,IAAI,OAAO,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACrG,WAAW,KAAK,eAAe,EAAE,CAAC;QAClC,OAAO,WAAW,CAAC;IACrB,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,IAAI,WAA4C,CAAC;AAEjD,kFAAkF;AAClF,KAAK,UAAU,eAAe;IAC5B,KAAK,MAAM,OAAO,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAU,EAAE,CAAC;QAClD,IAAI,CAAC;YACH,MAAM,oBAAoB,CACxB,wBAAwB,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,IAAI,IAAI,OAAO,EAAE,EACrE,EAAE,QAAQ,EAAE,IAAI,EAAE,CACnB,CAAC;YACF,OAAO,OAAO,CAAC;QACjB,CAAC;QAAC,MAAM,CAAC;YACP,sEAAsE;QACxE,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type LibVariant, type WhisperContext } from "./binding.js";
|
|
2
|
+
export declare class ContextCache {
|
|
3
|
+
private readonly maxModels;
|
|
4
|
+
private readonly idleMs;
|
|
5
|
+
private readonly entries;
|
|
6
|
+
private admission;
|
|
7
|
+
private readonly timer;
|
|
8
|
+
constructor(maxModels?: number, idleMs?: number);
|
|
9
|
+
private release;
|
|
10
|
+
private evictIdle;
|
|
11
|
+
withContext<T>(modelPath: string, variant: LibVariant, useGpu: boolean, run: (context: WhisperContext) => Promise<T>): Promise<T>;
|
|
12
|
+
dispose(): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
export declare const contextCache: ContextCache;
|
|
15
|
+
//# sourceMappingURL=context-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-cache.d.ts","sourceRoot":"","sources":["../src/context-cache.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,cAAc,EACpB,MAAM,cAAc,CAAC;AAStB,qBAAa,YAAY;IAKrB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM;IALzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;IACpD,OAAO,CAAC,SAAS,CAAoC;IACrD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiC;IACvD,YACmB,SAAS,SAAI,EACb,MAAM,SAAiB,EAgBzC;YACa,OAAO;YAIP,SAAS;IAWjB,WAAW,CAAC,CAAC,EACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,UAAU,EACnB,MAAM,EAAE,OAAO,EACf,GAAG,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,CAAC,CAAC,GAC3C,OAAO,CAAC,CAAC,CAAC,CA6DZ;IACK,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAM7B;CACF;AACD,eAAO,MAAM,YAAY,cAAqB,CAAC"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { createLogger } from "@nodetool-ai/config";
|
|
2
|
+
import { loadWhisperNode } from "./binding.js";
|
|
3
|
+
const log = createLogger("whisper-cpp.context-cache");
|
|
4
|
+
export class ContextCache {
|
|
5
|
+
maxModels;
|
|
6
|
+
idleMs;
|
|
7
|
+
entries = new Map();
|
|
8
|
+
admission = Promise.resolve();
|
|
9
|
+
timer;
|
|
10
|
+
constructor(maxModels = 2, idleMs = 10 * 60 * 1000) {
|
|
11
|
+
this.maxModels = maxModels;
|
|
12
|
+
this.idleMs = idleMs;
|
|
13
|
+
const sweep = async () => {
|
|
14
|
+
try {
|
|
15
|
+
await this.evictIdle();
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
log.warn("Context eviction failed: %s", String(error));
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
this.timer = setInterval(() => {
|
|
22
|
+
void sweep();
|
|
23
|
+
}, Math.min(idleMs, 60000));
|
|
24
|
+
this.timer.unref();
|
|
25
|
+
}
|
|
26
|
+
async release(key, entry) {
|
|
27
|
+
this.entries.delete(key);
|
|
28
|
+
await (await entry.context).release();
|
|
29
|
+
}
|
|
30
|
+
async evictIdle() {
|
|
31
|
+
const eviction = this.admission.then(async () => {
|
|
32
|
+
for (const [key, entry] of this.entries) {
|
|
33
|
+
if (entry.pending === 0 && Date.now() - entry.lastUsed >= this.idleMs) {
|
|
34
|
+
await this.release(key, entry);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
this.admission = eviction.catch(() => { });
|
|
39
|
+
await eviction;
|
|
40
|
+
}
|
|
41
|
+
async withContext(modelPath, variant, useGpu, run) {
|
|
42
|
+
const key = JSON.stringify([modelPath, variant, useGpu]);
|
|
43
|
+
const admitted = this.admission.then(async () => {
|
|
44
|
+
let entry = this.entries.get(key);
|
|
45
|
+
if (!entry) {
|
|
46
|
+
while (this.entries.size >= this.maxModels) {
|
|
47
|
+
// Evict the least recently used idle context. A busy context's
|
|
48
|
+
// lastUsed is from before its job started, so it would otherwise
|
|
49
|
+
// look oldest and make this admission wait for its whole job.
|
|
50
|
+
const oldest = [...this.entries].sort((a, b) => Number(a[1].pending > 0) - Number(b[1].pending > 0) ||
|
|
51
|
+
a[1].lastUsed - b[1].lastUsed)[0];
|
|
52
|
+
if (!oldest) {
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
await oldest[1].tail;
|
|
56
|
+
if (this.entries.get(oldest[0]) === oldest[1]) {
|
|
57
|
+
await this.release(oldest[0], oldest[1]);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const context = loadWhisperNode().then((binding) => binding.initWhisper({ filePath: modelPath, useGpu }, variant));
|
|
61
|
+
entry = {
|
|
62
|
+
context,
|
|
63
|
+
tail: Promise.resolve(),
|
|
64
|
+
pending: 0,
|
|
65
|
+
lastUsed: Date.now()
|
|
66
|
+
};
|
|
67
|
+
this.entries.set(key, entry);
|
|
68
|
+
}
|
|
69
|
+
const current = entry;
|
|
70
|
+
current.pending++;
|
|
71
|
+
const result = current.tail.then(async () => {
|
|
72
|
+
try {
|
|
73
|
+
return await run(await current.context);
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
current.pending--;
|
|
77
|
+
current.lastUsed = Date.now();
|
|
78
|
+
try {
|
|
79
|
+
await current.context;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
if (this.entries.get(key) === current) {
|
|
83
|
+
this.entries.delete(key);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
current.tail = result.then(() => { }, () => { });
|
|
89
|
+
return { result };
|
|
90
|
+
});
|
|
91
|
+
this.admission = admitted.then(() => { }, () => { });
|
|
92
|
+
return (await admitted).result;
|
|
93
|
+
}
|
|
94
|
+
async dispose() {
|
|
95
|
+
clearInterval(this.timer);
|
|
96
|
+
for (const [key, entry] of this.entries) {
|
|
97
|
+
await entry.tail;
|
|
98
|
+
await this.release(key, entry);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
export const contextCache = new ContextCache();
|
|
103
|
+
//# sourceMappingURL=context-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-cache.js","sourceRoot":"","sources":["../src/context-cache.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { NodeRegistry } from "@nodetool-ai/node-sdk";
|
|
2
|
+
import { WhisperCppProvider } from "./whisper-cpp-provider.js";
|
|
3
|
+
import { WhisperServerProvider } from "./whisper-server-provider.js";
|
|
4
|
+
import { LiveTranscriptionNode } from "./nodes/live-transcription.js";
|
|
5
|
+
export { WhisperCppProvider, WhisperServerProvider, LiveTranscriptionNode };
|
|
6
|
+
export { discoverASRModels, discoverVadModels } from "./model-discovery.js";
|
|
7
|
+
export declare const ALL_NODES: readonly [typeof LiveTranscriptionNode];
|
|
8
|
+
export declare function registerWhisperCppProviders(): void;
|
|
9
|
+
export declare function registerWhisperCppNodes(registry: NodeRegistry): void;
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAEtE,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,CAAC;AAC5E,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAC5E,eAAO,MAAM,SAAS,yCAAmC,CAAC;AAE1D,wBAAgB,2BAA2B,IAAI,IAAI,CAyBlD;AACD,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAIpE"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { registerProvider } from "@nodetool-ai/runtime";
|
|
2
|
+
import { isCloudProfileActive } from "@nodetool-ai/protocol";
|
|
3
|
+
import { WhisperCppProvider } from "./whisper-cpp-provider.js";
|
|
4
|
+
import { WhisperServerProvider } from "./whisper-server-provider.js";
|
|
5
|
+
import { LiveTranscriptionNode } from "./nodes/live-transcription.js";
|
|
6
|
+
export { WhisperCppProvider, WhisperServerProvider, LiveTranscriptionNode };
|
|
7
|
+
export { discoverASRModels, discoverVadModels } from "./model-discovery.js";
|
|
8
|
+
export const ALL_NODES = [LiveTranscriptionNode];
|
|
9
|
+
let registered = false;
|
|
10
|
+
export function registerWhisperCppProviders() {
|
|
11
|
+
if (registered ||
|
|
12
|
+
isCloudProfileActive(process.env.NODETOOL_NODE_PROFILE, process.env.NODETOOL_ENV)) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
registerProvider("whisper_cpp", WhisperCppProvider, {}, { WHISPER_CPP_MODELS_DIR: "", WHISPER_CPP_GPU_BACKEND: "" }, { access: "in_process", displayName: "whisper.cpp" });
|
|
16
|
+
registerProvider("whisper_cpp_server", WhisperServerProvider, { WHISPER_CPP_SERVER_URL: "" }, {}, { access: "local_service", displayName: "whisper.cpp server" });
|
|
17
|
+
registered = true;
|
|
18
|
+
}
|
|
19
|
+
export function registerWhisperCppNodes(registry) {
|
|
20
|
+
for (const node of ALL_NODES) {
|
|
21
|
+
registry.register(node);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAE7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAEtE,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,CAAC;AAC5E,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAC5E,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,qBAAqB,CAAU,CAAC;AAC1D,IAAI,UAAU,GAAG,KAAK,CAAC;AACvB,MAAM,UAAU,2BAA2B;IACzC,IACE,UAAU;QACV,oBAAoB,CAClB,OAAO,CAAC,GAAG,CAAC,qBAAqB,EACjC,OAAO,CAAC,GAAG,CAAC,YAAY,CACzB,EACD,CAAC;QACD,OAAO;IACT,CAAC;IACD,gBAAgB,CACd,aAAa,EACb,kBAAkB,EAClB,EAAE,EACF,EAAE,sBAAsB,EAAE,EAAE,EAAE,uBAAuB,EAAE,EAAE,EAAE,EAC3D,EAAE,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,CACrD,CAAC;IACF,gBAAgB,CACd,oBAAoB,EACpB,qBAAqB,EACrB,EAAE,sBAAsB,EAAE,EAAE,EAAE,EAC9B,EAAE,EACF,EAAE,MAAM,EAAE,eAAe,EAAE,WAAW,EAAE,oBAAoB,EAAE,CAC/D,CAAC;IACF,UAAU,GAAG,IAAI,CAAC;AACpB,CAAC;AACD,MAAM,UAAU,uBAAuB,CAAC,QAAsB;IAC5D,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC7B,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ASRModel } from "@nodetool-ai/runtime";
|
|
2
|
+
/** HuggingFace hub cache root, from the resolver every hub reader shares. */
|
|
3
|
+
export declare function getHfHubCacheDir(): string;
|
|
4
|
+
export declare function discoverASRModels(modelsDir?: string): Promise<ASRModel[]>;
|
|
5
|
+
export declare function discoverVadModels(modelsDir?: string): Promise<ASRModel[]>;
|
|
6
|
+
export declare function resolveModelPath(id: string, modelsDir?: string): Promise<string>;
|
|
7
|
+
//# sourceMappingURL=model-discovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-discovery.d.ts","sourceRoot":"","sources":["../src/model-discovery.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAOrD,6EAA6E;AAC7E,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAmFD,wBAAgB,iBAAiB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAEzE;AACD,wBAAgB,iBAAiB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAEzE;AACD,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,MAAM,EACV,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CAYjB"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { readdir, stat } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { getHfHubCacheDir as getConfigHfHubCacheDir } from "@nodetool-ai/config";
|
|
5
|
+
function expand(value) {
|
|
6
|
+
return path.resolve(value.startsWith("~/") ? path.join(homedir(), value.slice(2)) : value);
|
|
7
|
+
}
|
|
8
|
+
/** HuggingFace hub cache root, from the resolver every hub reader shares. */
|
|
9
|
+
export function getHfHubCacheDir() {
|
|
10
|
+
return path.resolve(getConfigHfHubCacheDir());
|
|
11
|
+
}
|
|
12
|
+
async function directories(dir) {
|
|
13
|
+
try {
|
|
14
|
+
return (await readdir(dir, { withFileTypes: true }))
|
|
15
|
+
.filter((entry) => entry.isDirectory())
|
|
16
|
+
.map((entry) => path.join(dir, entry.name));
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async function discover(vad, modelsDir) {
|
|
26
|
+
const hub = getHfHubCacheDir();
|
|
27
|
+
const dirs = [];
|
|
28
|
+
for (const repo of [
|
|
29
|
+
"models--ggerganov--whisper.cpp",
|
|
30
|
+
"models--ggml-org--whisper.cpp",
|
|
31
|
+
"models--ggml-org--whisper-vad"
|
|
32
|
+
]) {
|
|
33
|
+
dirs.push(...(await directories(path.join(hub, repo, "snapshots"))));
|
|
34
|
+
}
|
|
35
|
+
const extra = modelsDir || process.env.WHISPER_CPP_MODELS_DIR;
|
|
36
|
+
if (extra) {
|
|
37
|
+
dirs.push(expand(extra));
|
|
38
|
+
}
|
|
39
|
+
const files = new Map();
|
|
40
|
+
for (const dir of dirs) {
|
|
41
|
+
let entries;
|
|
42
|
+
try {
|
|
43
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
if (error instanceof Error &&
|
|
47
|
+
"code" in error &&
|
|
48
|
+
error.code === "ENOENT") {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
const mtime = (await stat(dir)).mtimeMs;
|
|
54
|
+
for (const entry of entries) {
|
|
55
|
+
if ((!entry.isFile() && !entry.isSymbolicLink()) ||
|
|
56
|
+
!/^ggml-.+\.bin$/i.test(entry.name) ||
|
|
57
|
+
/silero|vad/i.test(entry.name) !== vad) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const id = path.resolve(dir, entry.name);
|
|
61
|
+
try {
|
|
62
|
+
if (!(await stat(id)).isFile()) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (error instanceof Error &&
|
|
68
|
+
"code" in error &&
|
|
69
|
+
error.code === "ENOENT") {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
if ((files.get(entry.name)?.mtime ?? -Infinity) > mtime) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
files.set(entry.name, {
|
|
78
|
+
model: {
|
|
79
|
+
id,
|
|
80
|
+
name: entry.name.replace(/^ggml-/i, "").replace(/\.bin$/i, ""),
|
|
81
|
+
provider: "whisper_cpp"
|
|
82
|
+
},
|
|
83
|
+
mtime
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return [...files.values()]
|
|
88
|
+
.map((entry) => entry.model)
|
|
89
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
90
|
+
}
|
|
91
|
+
export function discoverASRModels(modelsDir) {
|
|
92
|
+
return discover(false, modelsDir);
|
|
93
|
+
}
|
|
94
|
+
export function discoverVadModels(modelsDir) {
|
|
95
|
+
return discover(true, modelsDir);
|
|
96
|
+
}
|
|
97
|
+
export async function resolveModelPath(id, modelsDir) {
|
|
98
|
+
// State saved before redaction stopped rewriting paths holds "~/..." ids.
|
|
99
|
+
const wanted = expand(id);
|
|
100
|
+
const model = (await discoverASRModels(modelsDir)).find((entry) => entry.id === wanted);
|
|
101
|
+
if (!model) {
|
|
102
|
+
throw new Error(`Unknown whisper.cpp model "${id}". Download one from the Models panel.`);
|
|
103
|
+
}
|
|
104
|
+
return model.id;
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=model-discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-discovery.js","sourceRoot":"","sources":["../src/model-discovery.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { BaseNode, type StreamingInputs, type StreamingOutputs } from "@nodetool-ai/node-sdk";
|
|
2
|
+
import type { ProcessingContext } from "@nodetool-ai/runtime";
|
|
3
|
+
import type { ASRModel } from "@nodetool-ai/runtime";
|
|
4
|
+
export declare class LiveTranscriptionNode extends BaseNode {
|
|
5
|
+
static readonly nodeType = "whisper_cpp.LiveTranscription";
|
|
6
|
+
static readonly title = "Live Transcription";
|
|
7
|
+
static readonly description = "Transcribe streaming PCM16 audio locally with whisper.cpp.\n speech, audio, transcription, streaming";
|
|
8
|
+
static readonly isStreamingInput = true;
|
|
9
|
+
static readonly inputFields: string[];
|
|
10
|
+
static readonly metadataOutputTypes: {
|
|
11
|
+
chunk: string;
|
|
12
|
+
text: string;
|
|
13
|
+
};
|
|
14
|
+
static readonly outputCorrelation: {
|
|
15
|
+
readonly chunk: {
|
|
16
|
+
readonly kind: "iteration";
|
|
17
|
+
readonly source: "__execution__";
|
|
18
|
+
readonly group: "stream";
|
|
19
|
+
};
|
|
20
|
+
readonly text: {
|
|
21
|
+
readonly kind: "single";
|
|
22
|
+
readonly source: "__execution__";
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
static readonly requiredSettings: string[];
|
|
26
|
+
chunk: unknown;
|
|
27
|
+
model: ASRModel;
|
|
28
|
+
language: string;
|
|
29
|
+
prompt: string;
|
|
30
|
+
vad_threshold: number;
|
|
31
|
+
min_silence_ms: number;
|
|
32
|
+
max_segment_s: number;
|
|
33
|
+
process(): Promise<Record<string, never>>;
|
|
34
|
+
run(inputs: StreamingInputs, outputs: StreamingOutputs, context?: ProcessingContext): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=live-transcription.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"live-transcription.d.ts","sourceRoot":"","sources":["../../src/nodes/live-transcription.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EAER,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACtB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAsBrD,qBAAa,qBAAsB,SAAQ,QAAQ;IACjD,MAAM,CAAC,QAAQ,CAAC,QAAQ,mCAAmC;IAC3D,MAAM,CAAC,QAAQ,CAAC,KAAK,wBAAwB;IAC7C,MAAM,CAAC,QAAQ,CAAC,WAAW,6GACiF;IAC5G,MAAM,CAAC,QAAQ,CAAC,gBAAgB,QAAQ;IACxC,MAAM,CAAC,QAAQ,CAAC,WAAW,WAAa;IACxC,MAAM,CAAC,QAAQ,CAAC,mBAAmB;QAAK,KAAK;QAAW,IAAI;MAAU;IACtE,MAAM,CAAC,QAAQ,CAAC,iBAAiB;iBAC/B,KAAK;qBAAI,IAAI,EAAE,WAAW;qBAAE,MAAM,EAAE,eAAe;qBAAE,KAAK,EAAE,QAAQ;;iBACpE,IAAI;qBAAI,IAAI,EAAE,QAAQ;qBAAE,MAAM,EAAE,eAAe;;MACtC;IACX,MAAM,CAAC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,EAAE,CAAM;IAEC,KAAK,EAAE,OAAO,CAAC;IAMxD,KAAK,EAAE,QAAQ,CAAC;IAEhB,QAAQ,EAAE,MAAM,CAAC;IACoC,MAAM,EAAE,MAAM,CAAC;IAEpE,aAAa,EAAE,MAAM,CAAC;IAEtB,cAAc,EAAE,MAAM,CAAC;IAEvB,aAAa,EAAE,MAAM,CAAC;IAEf,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAEvD;IACc,GAAG,CAChB,MAAM,EAAE,eAAe,EACvB,OAAO,EAAE,gBAAgB,EACzB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAiKf;CACF"}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { BaseNode, prop } from "@nodetool-ai/node-sdk";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import { createLogger } from "@nodetool-ai/config";
|
|
10
|
+
import { loadWhisperNode, resolveVariant } from "../binding.js";
|
|
11
|
+
import { pcm16Base64ToSamples, samplesToPcm16 } from "../audio.js";
|
|
12
|
+
import { discoverVadModels } from "../model-discovery.js";
|
|
13
|
+
import { WhisperCppProvider } from "../whisper-cpp-provider.js";
|
|
14
|
+
/** Each field degrades to absent when malformed, so one bad field never drops the chunk. */
|
|
15
|
+
const liveItemSchema = z.object({
|
|
16
|
+
done: z.boolean().optional().catch(undefined),
|
|
17
|
+
content_type: z.string().optional().catch(undefined),
|
|
18
|
+
content: z.string().optional().catch(undefined),
|
|
19
|
+
content_metadata: z
|
|
20
|
+
.object({ sample_rate: z.number().optional().catch(undefined) })
|
|
21
|
+
.optional()
|
|
22
|
+
.catch(undefined)
|
|
23
|
+
});
|
|
24
|
+
const log = createLogger("whisper-cpp.live-transcription");
|
|
25
|
+
export class LiveTranscriptionNode extends BaseNode {
|
|
26
|
+
static nodeType = "whisper_cpp.LiveTranscription";
|
|
27
|
+
static title = "Live Transcription";
|
|
28
|
+
static description = "Transcribe streaming PCM16 audio locally with whisper.cpp.\n speech, audio, transcription, streaming";
|
|
29
|
+
static isStreamingInput = true;
|
|
30
|
+
static inputFields = ["chunk"];
|
|
31
|
+
static metadataOutputTypes = { chunk: "chunk", text: "str" };
|
|
32
|
+
static outputCorrelation = {
|
|
33
|
+
chunk: { kind: "iteration", source: "__execution__", group: "stream" },
|
|
34
|
+
text: { kind: "single", source: "__execution__" }
|
|
35
|
+
};
|
|
36
|
+
static requiredSettings = [];
|
|
37
|
+
async process() {
|
|
38
|
+
return {};
|
|
39
|
+
}
|
|
40
|
+
async run(inputs, outputs, context) {
|
|
41
|
+
if (!Number.isFinite(this.max_segment_s) || this.max_segment_s < 1) {
|
|
42
|
+
throw new Error("Maximum segment duration must be at least one second");
|
|
43
|
+
}
|
|
44
|
+
if (this.model.provider !== "whisper_cpp") {
|
|
45
|
+
throw new Error("Live Transcription requires a whisper_cpp model");
|
|
46
|
+
}
|
|
47
|
+
const provider = new WhisperCppProvider({
|
|
48
|
+
WHISPER_CPP_MODELS_DIR: (await context?.getSecret("WHISPER_CPP_MODELS_DIR")) || undefined,
|
|
49
|
+
WHISPER_CPP_GPU_BACKEND: (await context?.getSecret("WHISPER_CPP_GPU_BACKEND")) || undefined
|
|
50
|
+
});
|
|
51
|
+
const modelPath = await provider.resolveModel(this.model.id);
|
|
52
|
+
const binding = await loadWhisperNode();
|
|
53
|
+
const signal = context
|
|
54
|
+
? AbortSignal.any([context.signal, inputs.signal])
|
|
55
|
+
: inputs.signal;
|
|
56
|
+
signal?.throwIfAborted();
|
|
57
|
+
let vad;
|
|
58
|
+
let worker = Promise.resolve();
|
|
59
|
+
let workerError;
|
|
60
|
+
const texts = [];
|
|
61
|
+
let buffer = new Float32Array();
|
|
62
|
+
const maxSamples = Math.floor(this.max_segment_s * 16000);
|
|
63
|
+
const enqueue = (samples) => {
|
|
64
|
+
worker = worker.then(async () => {
|
|
65
|
+
try {
|
|
66
|
+
signal?.throwIfAborted();
|
|
67
|
+
if (workerError) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const result = await provider.transcribePcm(modelPath, samplesToPcm16(samples), { language: this.language || "auto", prompt: this.prompt }, signal);
|
|
71
|
+
texts.push(result.text);
|
|
72
|
+
await outputs.emit("chunk", {
|
|
73
|
+
type: "chunk",
|
|
74
|
+
content: result.text,
|
|
75
|
+
content_type: "text",
|
|
76
|
+
done: false
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
workerError = error;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
try {
|
|
85
|
+
const vadModel = (await discoverVadModels(provider.modelsDir))[0];
|
|
86
|
+
if (vadModel) {
|
|
87
|
+
vad = await binding.initWhisperVad({ filePath: vadModel.id, useGpu: provider.gpuBackend !== "cpu" }, await resolveVariant(provider.gpuBackend));
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
log.warn("No whisper.cpp VAD model installed. Using fixed transcription windows.");
|
|
91
|
+
}
|
|
92
|
+
for await (const [handle, item] of inputs.any()) {
|
|
93
|
+
signal?.throwIfAborted();
|
|
94
|
+
if (workerError) {
|
|
95
|
+
throw workerError;
|
|
96
|
+
}
|
|
97
|
+
if (handle === "__control__") {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
let content = "";
|
|
101
|
+
let sampleRate = 16000;
|
|
102
|
+
let done = false;
|
|
103
|
+
const text = z.string().safeParse(item);
|
|
104
|
+
const parsed = liveItemSchema.safeParse(item);
|
|
105
|
+
if (text.success) {
|
|
106
|
+
content = text.data;
|
|
107
|
+
}
|
|
108
|
+
else if (parsed.success) {
|
|
109
|
+
const value = parsed.data;
|
|
110
|
+
done = value.done === true;
|
|
111
|
+
if (value.content_type === "audio") {
|
|
112
|
+
content = value.content ?? "";
|
|
113
|
+
sampleRate = value.content_metadata?.sample_rate ?? sampleRate;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (content) {
|
|
117
|
+
const samples = pcm16Base64ToSamples(content, sampleRate);
|
|
118
|
+
const combined = new Float32Array(buffer.length + samples.length);
|
|
119
|
+
combined.set(buffer);
|
|
120
|
+
combined.set(samples, buffer.length);
|
|
121
|
+
buffer = combined;
|
|
122
|
+
while (buffer.length >= 16000) {
|
|
123
|
+
let cut = 0;
|
|
124
|
+
let speech = true;
|
|
125
|
+
if (vad) {
|
|
126
|
+
const segments = await vad.detectSpeechData(samplesToPcm16(buffer), {
|
|
127
|
+
threshold: this.vad_threshold,
|
|
128
|
+
minSilenceDurationMs: this.min_silence_ms
|
|
129
|
+
});
|
|
130
|
+
// Whisper hallucinates text ("Thank you.") on silence, so a
|
|
131
|
+
// window VAD finds no speech in is dropped, not transcribed.
|
|
132
|
+
speech = segments.length > 0;
|
|
133
|
+
const last = segments.at(-1);
|
|
134
|
+
// VAD passes through whisper.cpp centiseconds (ASR uses milliseconds).
|
|
135
|
+
if (last &&
|
|
136
|
+
(buffer.length / 16000 - last.t1 / 100) * 1000 >=
|
|
137
|
+
this.min_silence_ms) {
|
|
138
|
+
cut = Math.min(buffer.length, Math.round((last.t1 / 100) * 16000));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (buffer.length >= maxSamples) {
|
|
142
|
+
cut = cut > 0 ? Math.min(cut, maxSamples) : maxSamples;
|
|
143
|
+
}
|
|
144
|
+
if (cut <= 0) {
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
if (speech) {
|
|
148
|
+
enqueue(buffer.slice(0, cut));
|
|
149
|
+
}
|
|
150
|
+
buffer = buffer.slice(cut);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (done) {
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (buffer.length) {
|
|
158
|
+
const speech = vad
|
|
159
|
+
? await vad.detectSpeechData(samplesToPcm16(buffer), {
|
|
160
|
+
threshold: this.vad_threshold,
|
|
161
|
+
minSilenceDurationMs: this.min_silence_ms
|
|
162
|
+
})
|
|
163
|
+
: undefined;
|
|
164
|
+
if (!speech || speech.length > 0) {
|
|
165
|
+
enqueue(buffer);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
await worker;
|
|
169
|
+
if (workerError) {
|
|
170
|
+
throw workerError;
|
|
171
|
+
}
|
|
172
|
+
signal?.throwIfAborted();
|
|
173
|
+
await outputs.emit("chunk", {
|
|
174
|
+
type: "chunk",
|
|
175
|
+
content: "",
|
|
176
|
+
content_type: "text",
|
|
177
|
+
done: true
|
|
178
|
+
});
|
|
179
|
+
await outputs.emit("text", texts.filter(Boolean).join(" "));
|
|
180
|
+
}
|
|
181
|
+
finally {
|
|
182
|
+
await worker;
|
|
183
|
+
await vad?.release();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
__decorate([
|
|
188
|
+
prop({ type: "chunk", title: "Chunk" })
|
|
189
|
+
], LiveTranscriptionNode.prototype, "chunk", void 0);
|
|
190
|
+
__decorate([
|
|
191
|
+
prop({
|
|
192
|
+
type: "asr_model",
|
|
193
|
+
default: { type: "asr_model", provider: "whisper_cpp", id: "" },
|
|
194
|
+
title: "Model"
|
|
195
|
+
})
|
|
196
|
+
], LiveTranscriptionNode.prototype, "model", void 0);
|
|
197
|
+
__decorate([
|
|
198
|
+
prop({ type: "str", default: "", title: "Language" })
|
|
199
|
+
], LiveTranscriptionNode.prototype, "language", void 0);
|
|
200
|
+
__decorate([
|
|
201
|
+
prop({ type: "str", default: "", title: "Prompt" })
|
|
202
|
+
], LiveTranscriptionNode.prototype, "prompt", void 0);
|
|
203
|
+
__decorate([
|
|
204
|
+
prop({ type: "float", default: 0.5, min: 0, max: 1, title: "VAD Threshold" })
|
|
205
|
+
], LiveTranscriptionNode.prototype, "vad_threshold", void 0);
|
|
206
|
+
__decorate([
|
|
207
|
+
prop({ type: "int", default: 500, min: 0, title: "Minimum Silence (ms)" })
|
|
208
|
+
], LiveTranscriptionNode.prototype, "min_silence_ms", void 0);
|
|
209
|
+
__decorate([
|
|
210
|
+
prop({ type: "float", default: 20, min: 1, title: "Maximum Segment (s)" })
|
|
211
|
+
], LiveTranscriptionNode.prototype, "max_segment_s", void 0);
|
|
212
|
+
//# sourceMappingURL=live-transcription.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"live-transcription.js","sourceRoot":"","sources":["../../src/nodes/live-transcription.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { BaseProvider, type ASRModel, type ASRResult, type Message, type ProviderStreamItem, type ProviderCapability } from "@nodetool-ai/runtime";
|
|
2
|
+
import { type TranscribeOptions } from "./binding.js";
|
|
3
|
+
export interface WhisperSettings {
|
|
4
|
+
WHISPER_CPP_MODELS_DIR?: string;
|
|
5
|
+
WHISPER_CPP_GPU_BACKEND?: string;
|
|
6
|
+
}
|
|
7
|
+
export type AsrArgs = Parameters<BaseProvider["automaticSpeechRecognition"]>[0];
|
|
8
|
+
export declare class WhisperCppProvider extends BaseProvider {
|
|
9
|
+
readonly modelsDir: string | undefined;
|
|
10
|
+
readonly gpuBackend: string | undefined;
|
|
11
|
+
constructor(secrets?: WhisperSettings);
|
|
12
|
+
static requiredSecrets(): string[];
|
|
13
|
+
protected declaredCapabilities(): readonly ProviderCapability[];
|
|
14
|
+
getCapabilities(): ProviderCapability[];
|
|
15
|
+
generateMessage(_args: Parameters<BaseProvider["generateMessage"]>[0]): Promise<Message>;
|
|
16
|
+
generateMessages(_args: Parameters<BaseProvider["generateMessages"]>[0]): AsyncGenerator<ProviderStreamItem>;
|
|
17
|
+
unavailableReason(): Promise<string | null>;
|
|
18
|
+
getAvailableASRModels(): Promise<ASRModel[]>;
|
|
19
|
+
resolveModel(id: string): Promise<string>;
|
|
20
|
+
transcribePcm(modelPath: string, audio: ArrayBuffer, options: TranscribeOptions, signal?: AbortSignal): Promise<ASRResult>;
|
|
21
|
+
automaticSpeechRecognition(args: AsrArgs): Promise<ASRResult>;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=whisper-cpp-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whisper-cpp-provider.d.ts","sourceRoot":"","sources":["../src/whisper-cpp-provider.ts"],"names":[],"mappings":"AACA,OAAO,EACL,YAAY,EACZ,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,OAAO,EACZ,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACxB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,cAAc,CAAC;AAKtB,MAAM,WAAW,eAAe;IAC9B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC;AACD,MAAM,MAAM,OAAO,GAAG,UAAU,CAAC,YAAY,CAAC,4BAA4B,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAChF,qBAAa,kBAAmB,SAAQ,YAAY;IAClD,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,YAAY,OAAO,GAAE,eAAoB,EAMxC;IACD,OAAgB,eAAe,IAAI,MAAM,EAAE,CAE1C;IACD,UAAmB,oBAAoB,IAAI,SAAS,kBAAkB,EAAE,CAEvE;IACQ,eAAe,IAAI,kBAAkB,EAAE,CAE/C;IACc,eAAe,CAC5B,KAAK,EAAE,UAAU,CAAC,YAAY,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC,GACpD,OAAO,CAAC,OAAO,CAAC,CAElB;IACQ,gBAAgB,CACvB,KAAK,EAAE,UAAU,CAAC,YAAY,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC,GACrD,cAAc,CAAC,kBAAkB,CAAC,CAEpC;IACc,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAOzD;IACc,qBAAqB,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC,CAK1D;IACD,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAExC;IACK,aAAa,CACjB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,iBAAiB,EAC1B,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,SAAS,CAAC,CAiCpB;IACc,0BAA0B,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC,CAmB3E;CACF"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { availableParallelism } from "node:os";
|
|
2
|
+
import { BaseProvider } from "@nodetool-ai/runtime";
|
|
3
|
+
import { loadWhisperNode, resolveVariant } from "./binding.js";
|
|
4
|
+
import { decodeToPcm16 } from "./audio.js";
|
|
5
|
+
import { contextCache } from "./context-cache.js";
|
|
6
|
+
import { discoverASRModels, resolveModelPath } from "./model-discovery.js";
|
|
7
|
+
export class WhisperCppProvider extends BaseProvider {
|
|
8
|
+
modelsDir;
|
|
9
|
+
gpuBackend;
|
|
10
|
+
constructor(secrets = {}) {
|
|
11
|
+
super("whisper_cpp");
|
|
12
|
+
this.modelsDir =
|
|
13
|
+
secrets.WHISPER_CPP_MODELS_DIR || process.env.WHISPER_CPP_MODELS_DIR;
|
|
14
|
+
this.gpuBackend =
|
|
15
|
+
secrets.WHISPER_CPP_GPU_BACKEND || process.env.WHISPER_CPP_GPU_BACKEND;
|
|
16
|
+
}
|
|
17
|
+
static requiredSecrets() {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
declaredCapabilities() {
|
|
21
|
+
return ["automatic_speech_recognition"];
|
|
22
|
+
}
|
|
23
|
+
getCapabilities() {
|
|
24
|
+
return ["automatic_speech_recognition"];
|
|
25
|
+
}
|
|
26
|
+
async generateMessage(_args) {
|
|
27
|
+
throw new Error("whisper_cpp does not support chat generation");
|
|
28
|
+
}
|
|
29
|
+
generateMessages(_args) {
|
|
30
|
+
throw new Error("whisper_cpp does not support chat generation");
|
|
31
|
+
}
|
|
32
|
+
async unavailableReason() {
|
|
33
|
+
try {
|
|
34
|
+
await loadWhisperNode();
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
return error instanceof Error ? error.message : String(error);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async getAvailableASRModels() {
|
|
42
|
+
if (await this.unavailableReason()) {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
return discoverASRModels(this.modelsDir);
|
|
46
|
+
}
|
|
47
|
+
resolveModel(id) {
|
|
48
|
+
return resolveModelPath(id, this.modelsDir);
|
|
49
|
+
}
|
|
50
|
+
async transcribePcm(modelPath, audio, options, signal) {
|
|
51
|
+
return contextCache.withContext(modelPath, await resolveVariant(this.gpuBackend), this.gpuBackend !== "cpu", async (context) => {
|
|
52
|
+
signal?.throwIfAborted();
|
|
53
|
+
const task = context.transcribeData(audio, {
|
|
54
|
+
...options,
|
|
55
|
+
maxThreads: Math.max(1, Math.min(8, availableParallelism() - 1))
|
|
56
|
+
});
|
|
57
|
+
const abort = () => {
|
|
58
|
+
void task.stop().catch(() => {
|
|
59
|
+
/* The task promise reports native failures. */
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
63
|
+
try {
|
|
64
|
+
const result = await task.promise;
|
|
65
|
+
signal?.throwIfAborted();
|
|
66
|
+
return {
|
|
67
|
+
text: result.result.trim(),
|
|
68
|
+
chunks: result.segments.map((segment) => ({
|
|
69
|
+
// whisper.node 1.1.3 converts whisper.cpp ticks to milliseconds.
|
|
70
|
+
timestamp: [segment.t0 / 1000, segment.t1 / 1000],
|
|
71
|
+
text: segment.text
|
|
72
|
+
}))
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
signal?.removeEventListener("abort", abort);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
async automaticSpeechRecognition(args) {
|
|
81
|
+
if (args.audio.length === 0) {
|
|
82
|
+
throw new Error("audio must not be empty");
|
|
83
|
+
}
|
|
84
|
+
await loadWhisperNode();
|
|
85
|
+
const modelPath = await this.resolveModel(args.model);
|
|
86
|
+
const audio = await decodeToPcm16(args.audio);
|
|
87
|
+
// BaseProvider ASR arguments do not expose cancellation yet.
|
|
88
|
+
const options = {
|
|
89
|
+
language: args.language || "auto",
|
|
90
|
+
tokenTimestamps: args.word_timestamps ?? false
|
|
91
|
+
};
|
|
92
|
+
if (args.prompt !== undefined) {
|
|
93
|
+
options.prompt = args.prompt;
|
|
94
|
+
}
|
|
95
|
+
if (args.temperature !== undefined) {
|
|
96
|
+
options.temperature = args.temperature;
|
|
97
|
+
}
|
|
98
|
+
return this.transcribePcm(modelPath, audio, options);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=whisper-cpp-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whisper-cpp-provider.js","sourceRoot":"","sources":["../src/whisper-cpp-provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { BaseProvider, type ASRModel, type ASRResult, type Message, type ProviderStreamItem, type ProviderCapability } from "@nodetool-ai/runtime";
|
|
2
|
+
import type { AsrArgs } from "./whisper-cpp-provider.js";
|
|
3
|
+
export declare class WhisperServerProvider extends BaseProvider {
|
|
4
|
+
readonly baseUrl: string;
|
|
5
|
+
private readonly fetchFn;
|
|
6
|
+
constructor(secrets?: {
|
|
7
|
+
WHISPER_CPP_SERVER_URL?: string;
|
|
8
|
+
}, options?: {
|
|
9
|
+
fetchFn?: typeof fetch;
|
|
10
|
+
});
|
|
11
|
+
static requiredSecrets(): string[];
|
|
12
|
+
protected declaredCapabilities(): readonly ProviderCapability[];
|
|
13
|
+
getCapabilities(): ProviderCapability[];
|
|
14
|
+
generateMessage(_args: Parameters<BaseProvider["generateMessage"]>[0]): Promise<Message>;
|
|
15
|
+
generateMessages(_args: Parameters<BaseProvider["generateMessages"]>[0]): AsyncGenerator<ProviderStreamItem>;
|
|
16
|
+
getAvailableASRModels(): Promise<ASRModel[]>;
|
|
17
|
+
automaticSpeechRecognition(args: AsrArgs): Promise<ASRResult>;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=whisper-server-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whisper-server-provider.d.ts","sourceRoot":"","sources":["../src/whisper-server-provider.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EAEZ,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,OAAO,EACZ,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACxB,MAAM,sBAAsB,CAAC;AAM9B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAoEzD,qBAAa,qBAAsB,SAAQ,YAAY;IACrD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,YACE,OAAO,GAAE;QAAE,sBAAsB,CAAC,EAAE,MAAM,CAAA;KAAO,EACjD,OAAO,GAAE;QAAE,OAAO,CAAC,EAAE,OAAO,KAAK,CAAA;KAAO,EAUzC;IACD,OAAgB,eAAe,IAAI,MAAM,EAAE,CAE1C;IACD,UAAmB,oBAAoB,IAAI,SAAS,kBAAkB,EAAE,CAEvE;IACQ,eAAe,IAAI,kBAAkB,EAAE,CAE/C;IACc,eAAe,CAC5B,KAAK,EAAE,UAAU,CAAC,YAAY,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC,GACpD,OAAO,CAAC,OAAO,CAAC,CAElB;IACQ,gBAAgB,CACvB,KAAK,EAAE,UAAU,CAAC,YAAY,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC,GACrD,cAAc,CAAC,kBAAkB,CAAC,CAEpC;IACc,qBAAqB,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC,CAI1D;IACc,0BAA0B,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC,CA+B3E;CACF"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { BaseProvider, sniffAudioMime } from "@nodetool-ai/runtime";
|
|
2
|
+
import { decodeAudioBytesToSamples, encodeWav } from "@nodetool-ai/transformers-js-nodes";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
const resultSchema = z.object({ text: z.string() });
|
|
5
|
+
const segmentSchema = z.object({
|
|
6
|
+
start: z.number(),
|
|
7
|
+
end: z.number(),
|
|
8
|
+
text: z.string()
|
|
9
|
+
});
|
|
10
|
+
async function readResult(response) {
|
|
11
|
+
const value = await response.json();
|
|
12
|
+
const result = resultSchema.safeParse(value);
|
|
13
|
+
if (!result.success) {
|
|
14
|
+
throw new Error("Invalid whisper-server response: expected text");
|
|
15
|
+
}
|
|
16
|
+
const chunks = [];
|
|
17
|
+
const segments = Array.isArray(value.segments) ? value.segments : [];
|
|
18
|
+
for (const raw of segments) {
|
|
19
|
+
const segment = segmentSchema.safeParse(raw);
|
|
20
|
+
if (!segment.success) {
|
|
21
|
+
throw new Error("Invalid whisper-server response: expected segment start, end and text");
|
|
22
|
+
}
|
|
23
|
+
chunks.push({
|
|
24
|
+
timestamp: [segment.data.start, segment.data.end],
|
|
25
|
+
text: segment.data.text
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return { text: result.data.text.trim(), chunks };
|
|
29
|
+
}
|
|
30
|
+
/** ID3-tagged MP3 or a raw MPEG audio frame sync. */
|
|
31
|
+
function isMp3(bytes) {
|
|
32
|
+
return ((bytes[0] === 0x49 && bytes[1] === 0x44 && bytes[2] === 0x33) ||
|
|
33
|
+
(bytes[0] === 0xff && (bytes[1] & 0xe0) === 0xe0));
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* whisper-server decodes uploads with miniaudio, which reads WAV, MP3 and
|
|
37
|
+
* FLAC only (unless the server runs with `--convert`). Send those unchanged
|
|
38
|
+
* and convert anything else (WebM/Opus, M4A, OGG) to 16 kHz mono WAV.
|
|
39
|
+
* When local decoding fails, send the original bytes so a `--convert`
|
|
40
|
+
* server can still try.
|
|
41
|
+
*/
|
|
42
|
+
async function toServerAudio(audio) {
|
|
43
|
+
const mime = sniffAudioMime(audio);
|
|
44
|
+
if (mime === "audio/wav") {
|
|
45
|
+
return { bytes: audio, mime, extension: "wav" };
|
|
46
|
+
}
|
|
47
|
+
if (mime === "audio/flac") {
|
|
48
|
+
return { bytes: audio, mime, extension: "flac" };
|
|
49
|
+
}
|
|
50
|
+
if (isMp3(audio)) {
|
|
51
|
+
return { bytes: audio, mime: "audio/mpeg", extension: "mp3" };
|
|
52
|
+
}
|
|
53
|
+
let samples;
|
|
54
|
+
try {
|
|
55
|
+
samples = await decodeAudioBytesToSamples(audio, 16000);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return { bytes: audio, mime: "application/octet-stream", extension: "bin" };
|
|
59
|
+
}
|
|
60
|
+
return { bytes: encodeWav(samples, 16000), mime: "audio/wav", extension: "wav" };
|
|
61
|
+
}
|
|
62
|
+
export class WhisperServerProvider extends BaseProvider {
|
|
63
|
+
baseUrl;
|
|
64
|
+
fetchFn;
|
|
65
|
+
constructor(secrets = {}, options = {}) {
|
|
66
|
+
super("whisper_cpp_server");
|
|
67
|
+
const url = secrets.WHISPER_CPP_SERVER_URL || process.env.WHISPER_CPP_SERVER_URL;
|
|
68
|
+
if (!url?.trim()) {
|
|
69
|
+
throw new Error("WHISPER_CPP_SERVER_URL is required");
|
|
70
|
+
}
|
|
71
|
+
this.baseUrl = url.trim().replace(/\/+$/, "");
|
|
72
|
+
this.fetchFn = options.fetchFn ?? globalThis.fetch.bind(globalThis);
|
|
73
|
+
}
|
|
74
|
+
static requiredSecrets() {
|
|
75
|
+
return ["WHISPER_CPP_SERVER_URL"];
|
|
76
|
+
}
|
|
77
|
+
declaredCapabilities() {
|
|
78
|
+
return ["automatic_speech_recognition"];
|
|
79
|
+
}
|
|
80
|
+
getCapabilities() {
|
|
81
|
+
return ["automatic_speech_recognition"];
|
|
82
|
+
}
|
|
83
|
+
async generateMessage(_args) {
|
|
84
|
+
throw new Error("whisper_cpp_server does not support chat generation");
|
|
85
|
+
}
|
|
86
|
+
generateMessages(_args) {
|
|
87
|
+
throw new Error("whisper_cpp_server does not support chat generation");
|
|
88
|
+
}
|
|
89
|
+
async getAvailableASRModels() {
|
|
90
|
+
return [
|
|
91
|
+
{ id: "default", name: "whisper-server", provider: "whisper_cpp_server" }
|
|
92
|
+
];
|
|
93
|
+
}
|
|
94
|
+
async automaticSpeechRecognition(args) {
|
|
95
|
+
if (args.audio.length === 0) {
|
|
96
|
+
throw new Error("audio must not be empty");
|
|
97
|
+
}
|
|
98
|
+
const { bytes, mime, extension } = await toServerAudio(args.audio);
|
|
99
|
+
const form = new FormData();
|
|
100
|
+
form.set("file", new Blob([new Uint8Array(bytes)], { type: mime }), `audio.${extension}`);
|
|
101
|
+
form.set("response_format", "verbose_json");
|
|
102
|
+
form.set("language", args.language || "auto");
|
|
103
|
+
if (args.temperature !== undefined) {
|
|
104
|
+
form.set("temperature", String(args.temperature));
|
|
105
|
+
}
|
|
106
|
+
if (args.prompt !== undefined) {
|
|
107
|
+
form.set("prompt", args.prompt);
|
|
108
|
+
}
|
|
109
|
+
// Operator-configured local service, following LlamaProvider's fetch policy.
|
|
110
|
+
const response = await this.fetchFn(`${this.baseUrl}/inference`, {
|
|
111
|
+
method: "POST",
|
|
112
|
+
body: form,
|
|
113
|
+
signal: AbortSignal.timeout(10 * 60 * 1000)
|
|
114
|
+
});
|
|
115
|
+
if (!response.ok) {
|
|
116
|
+
throw new Error(`whisper-server inference failed (${response.status}): ${await response.text()}`);
|
|
117
|
+
}
|
|
118
|
+
return readResult(response);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=whisper-server-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whisper-server-provider.js","sourceRoot":"","sources":["../src/whisper-server-provider.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,66 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nodetool-ai/whisper-cpp",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
|
|
6
|
-
}
|
|
3
|
+
"type": "module",
|
|
4
|
+
"engines": {
|
|
5
|
+
"node": ">=22.0.0"
|
|
6
|
+
},
|
|
7
|
+
"version": "0.8.3",
|
|
8
|
+
"description": "Local whisper.cpp speech recognition providers and streaming transcription",
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"types": "dist/index.d.ts",
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "node ../../scripts/build-typescript-workspace.mjs",
|
|
13
|
+
"test": "node ../../scripts/run-vitest.mjs run",
|
|
14
|
+
"test:watch": "node ../../scripts/run-vitest.mjs",
|
|
15
|
+
"lint": "node ../../scripts/run-tsc.mjs --noEmit"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@nodetool-ai/config": "0.8.3",
|
|
19
|
+
"@nodetool-ai/protocol": "0.8.3",
|
|
20
|
+
"@nodetool-ai/runtime": "0.8.3",
|
|
21
|
+
"@nodetool-ai/transformers-js-nodes": "0.8.3",
|
|
22
|
+
"@nodetool-ai/node-sdk": "0.8.3"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "^22.0.0",
|
|
26
|
+
"typescript": "npm:@typescript/typescript6@6.0.2",
|
|
27
|
+
"vitest": "^5.0.1"
|
|
28
|
+
},
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"import": "./dist/index.js",
|
|
33
|
+
"default": "./dist/index.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "https://github.com/nodetool-ai/nodetool.git",
|
|
40
|
+
"directory": "packages/whisper-cpp"
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://nodetool.ai",
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/nodetool-ai/nodetool/issues"
|
|
45
|
+
},
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
},
|
|
49
|
+
"files": [
|
|
50
|
+
"dist",
|
|
51
|
+
"README.md"
|
|
52
|
+
],
|
|
53
|
+
"keywords": [
|
|
54
|
+
"nodetool",
|
|
55
|
+
"whisper",
|
|
56
|
+
"asr"
|
|
57
|
+
],
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"@fugood/whisper.node": "1.1.3"
|
|
60
|
+
},
|
|
61
|
+
"peerDependenciesMeta": {
|
|
62
|
+
"@fugood/whisper.node": {
|
|
63
|
+
"optional": true
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|