@fugood/node-whisper-wasm 1.0.20 → 1.0.21
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 +21 -15
- package/index.d.ts +5 -0
- package/index.js +56 -29
- package/package.json +4 -2
- package/wasm/whisper-node.js +2 -2
- package/wasm/whisper-node.threads.js +2 -0
- package/wasm/whisper-node.threads.wasm +0 -0
- package/wasm/whisper-node.wasm +0 -0
- package/worker.js +1 -0
package/README.md
CHANGED
|
@@ -24,21 +24,25 @@ console.log(await promise)
|
|
|
24
24
|
await whisper.release()
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
The
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
The package ships both single-thread and pthread WASM artifacts. On
|
|
28
|
+
cross-origin isolated pages with `SharedArrayBuffer`, the loader uses the
|
|
29
|
+
pthread artifact; otherwise it falls back to the single-thread artifact
|
|
30
|
+
automatically. Whisper transcription defaults to up to 8 threads based on
|
|
31
|
+
browser hardware concurrency when pthreads are available; pass `maxThreads` to
|
|
32
|
+
override it. Browser WASM clamps `maxThreads` to the compiled pool limit of 8,
|
|
33
|
+
or 1 in the single-thread fallback. Browser pages run model loading,
|
|
34
|
+
transcription, benchmarks, and VAD in a dedicated module worker by default so
|
|
35
|
+
the UI thread can keep rendering. Use the main `whisper.node` package entrypoint
|
|
36
|
+
in browser code:
|
|
34
37
|
|
|
35
38
|
```js
|
|
36
39
|
import { configureWasm, initWhisper } from '@fugood/whisper.node'
|
|
37
40
|
```
|
|
38
41
|
|
|
39
42
|
Use `configureWasm({ worker: false })` only when you explicitly need the
|
|
40
|
-
in-thread runtime,
|
|
41
|
-
|
|
43
|
+
in-thread runtime, `configureWasm({ threads: false })` to force the
|
|
44
|
+
single-thread artifact, or pass `workerPath`, `jsPath`, and `wasmPath` when
|
|
45
|
+
serving the package files from custom URLs. The older `workerUrl` and
|
|
42
46
|
`runtimeScriptUrl` option names still work. Model downloads are cached in
|
|
43
47
|
browser Cache Storage by default. Pass `cacheModel: false` to disable persistent
|
|
44
48
|
caching, `modelCacheName` to isolate the cache namespace, or `modelCacheKey`
|
|
@@ -47,9 +51,11 @@ model. Set `useGpu: true` only with a package built using `GGML_WEBGPU=ON` and a
|
|
|
47
51
|
browser that exposes `navigator.gpu`. VAD currently falls back to CPU in the
|
|
48
52
|
browser package because the Silero VAD graph hits unsupported WebGPU ops.
|
|
49
53
|
|
|
50
|
-
The default build emits `wasm/whisper-node.js
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
`whisper-node.worker.js` is
|
|
55
|
-
UI work off the main thread
|
|
54
|
+
The default build emits `wasm/whisper-node.js`, `wasm/whisper-node.wasm`,
|
|
55
|
+
`wasm/whisper-node.threads.js`, and `wasm/whisper-node.threads.wasm`. Use
|
|
56
|
+
`bash scripts/build-wasm.sh --single-file` only when you want the WASM binary
|
|
57
|
+
embedded into each generated JS file. Modern Emscripten embeds the pthread
|
|
58
|
+
worker bootstrap in the main JS file, so a separate `whisper-node.worker.js` is
|
|
59
|
+
not expected. The package module worker that keeps UI work off the main thread
|
|
60
|
+
is `worker.js`. Use `npm run build-wasm-docker` to build through Docker; it
|
|
61
|
+
selects a native arm64 Emscripten image on Apple Silicon hosts.
|
package/index.d.ts
CHANGED
|
@@ -148,6 +148,7 @@ export interface WasmRuntimeOptions {
|
|
|
148
148
|
runtimeScriptUrl?: string
|
|
149
149
|
jsPath?: string
|
|
150
150
|
wasmPath?: string
|
|
151
|
+
threads?: boolean
|
|
151
152
|
locateFileBaseUrl?: string
|
|
152
153
|
locateFile?: (path: string, prefix: string) => string
|
|
153
154
|
mainScriptUrlOrBlob?: string | Blob
|
|
@@ -166,10 +167,13 @@ export declare const WASM_CONFIG_PATHS: {
|
|
|
166
167
|
index: string
|
|
167
168
|
js: string
|
|
168
169
|
wasm: string
|
|
170
|
+
threadsJs: string
|
|
171
|
+
threadsWasm: string
|
|
169
172
|
worker: string
|
|
170
173
|
}
|
|
171
174
|
|
|
172
175
|
export declare function configureWasm(options: WasmRuntimeOptions): void
|
|
176
|
+
export declare function isWasmThreadsSupported(): boolean
|
|
173
177
|
export declare function loadWasmModule(): Promise<unknown>
|
|
174
178
|
export declare function loadWhisperModule(): Promise<Module>
|
|
175
179
|
export declare function initWhisper(
|
|
@@ -196,6 +200,7 @@ declare const _default: {
|
|
|
196
200
|
initWhisperVad: typeof initWhisperVad
|
|
197
201
|
toggleNativeLog: typeof toggleNativeLog
|
|
198
202
|
addNativeLogListener: typeof addNativeLogListener
|
|
203
|
+
isWasmThreadsSupported: typeof isWasmThreadsSupported
|
|
199
204
|
DEFAULT_WASM_MODEL_SIZE_LIMIT_BYTES: typeof DEFAULT_WASM_MODEL_SIZE_LIMIT_BYTES
|
|
200
205
|
MAX_WASM_THREADS: typeof MAX_WASM_THREADS
|
|
201
206
|
WASM_CONFIG_PATHS: typeof WASM_CONFIG_PATHS
|
package/index.js
CHANGED
|
@@ -11,6 +11,8 @@ export const WASM_CONFIG_PATHS = {
|
|
|
11
11
|
index: new URL('./index.js', import.meta.url).href,
|
|
12
12
|
js: new URL('./wasm/whisper-node.js', import.meta.url).href,
|
|
13
13
|
wasm: new URL('./wasm/whisper-node.wasm', import.meta.url).href,
|
|
14
|
+
threadsJs: new URL('./wasm/whisper-node.threads.js', import.meta.url).href,
|
|
15
|
+
threadsWasm: new URL('./wasm/whisper-node.threads.wasm', import.meta.url).href,
|
|
14
16
|
worker: new URL('./worker.js', import.meta.url).href,
|
|
15
17
|
}
|
|
16
18
|
|
|
@@ -68,20 +70,39 @@ const createWhisperNodeApi = function (root) {
|
|
|
68
70
|
return capturedScriptUrl
|
|
69
71
|
}
|
|
70
72
|
|
|
71
|
-
function
|
|
73
|
+
function isWasmThreadsSupported() {
|
|
74
|
+
if (!isBrowserLike()) {
|
|
75
|
+
return true
|
|
76
|
+
}
|
|
77
|
+
return (
|
|
78
|
+
typeof root.SharedArrayBuffer !== 'undefined' &&
|
|
79
|
+
typeof root.Atomics !== 'undefined' &&
|
|
80
|
+
typeof root.crossOriginIsolated !== 'undefined' &&
|
|
81
|
+
root.crossOriginIsolated === true
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function shouldUseThreadedRuntime() {
|
|
86
|
+
if (typeof runtimeOptions.__resolvedThreads === 'boolean') {
|
|
87
|
+
return runtimeOptions.__resolvedThreads
|
|
88
|
+
}
|
|
89
|
+
return runtimeOptions.threads !== false && isWasmThreadsSupported()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function getRuntimeScriptUrl(indexScriptUrl, useThreads) {
|
|
72
93
|
var configured = runtimeOptions.jsPath || runtimeOptions.runtimeScriptUrl
|
|
73
94
|
if (configured) {
|
|
74
95
|
return resolveUrl(configured, indexScriptUrl)
|
|
75
96
|
}
|
|
76
|
-
return WASM_CONFIG_PATHS.js
|
|
97
|
+
return useThreads ? WASM_CONFIG_PATHS.threadsJs : WASM_CONFIG_PATHS.js
|
|
77
98
|
}
|
|
78
99
|
|
|
79
|
-
function getWasmBinaryUrl(runtimeScriptUrl) {
|
|
100
|
+
function getWasmBinaryUrl(runtimeScriptUrl, useThreads) {
|
|
80
101
|
var configured = runtimeOptions.wasmPath
|
|
81
102
|
if (configured) {
|
|
82
103
|
return resolveUrl(configured, runtimeScriptUrl)
|
|
83
104
|
}
|
|
84
|
-
return WASM_CONFIG_PATHS.wasm
|
|
105
|
+
return useThreads ? WASM_CONFIG_PATHS.threadsWasm : WASM_CONFIG_PATHS.wasm
|
|
85
106
|
}
|
|
86
107
|
|
|
87
108
|
function getWorkerScriptUrl(indexScriptUrl) {
|
|
@@ -111,6 +132,8 @@ const createWhisperNodeApi = function (root) {
|
|
|
111
132
|
runtimeScriptUrl: true,
|
|
112
133
|
jsPath: true,
|
|
113
134
|
wasmPath: true,
|
|
135
|
+
threads: true,
|
|
136
|
+
__resolvedThreads: true,
|
|
114
137
|
locateFileBaseUrl: true,
|
|
115
138
|
locateFile: true,
|
|
116
139
|
moduleFactory: true,
|
|
@@ -129,14 +152,6 @@ const createWhisperNodeApi = function (root) {
|
|
|
129
152
|
return options
|
|
130
153
|
}
|
|
131
154
|
|
|
132
|
-
function assertThreadSupport() {
|
|
133
|
-
if (isBrowserLike() && typeof root.SharedArrayBuffer === 'undefined') {
|
|
134
|
-
throw new Error(
|
|
135
|
-
'whisper.node WASM is built with pthreads and requires SharedArrayBuffer. Serve the page with COOP/COEP headers so the browser is cross-origin isolated.',
|
|
136
|
-
)
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
155
|
function emitLog(level, text) {
|
|
141
156
|
if (!logEnabled) {
|
|
142
157
|
return
|
|
@@ -151,12 +166,11 @@ const createWhisperNodeApi = function (root) {
|
|
|
151
166
|
|
|
152
167
|
function loadRuntime() {
|
|
153
168
|
if (!runtimePromise) {
|
|
154
|
-
assertThreadSupport()
|
|
155
|
-
|
|
156
169
|
runtimePromise = (async function () {
|
|
157
170
|
var indexScriptUrl = getIndexScriptUrl()
|
|
158
|
-
var
|
|
159
|
-
var
|
|
171
|
+
var useThreads = shouldUseThreadedRuntime()
|
|
172
|
+
var runtimeScriptUrl = getRuntimeScriptUrl(indexScriptUrl, useThreads)
|
|
173
|
+
var wasmUrl = getWasmBinaryUrl(runtimeScriptUrl, useThreads)
|
|
160
174
|
var moduleFactory = runtimeOptions.moduleFactory
|
|
161
175
|
|
|
162
176
|
if (!moduleFactory) {
|
|
@@ -168,7 +182,7 @@ const createWhisperNodeApi = function (root) {
|
|
|
168
182
|
}
|
|
169
183
|
if (typeof moduleFactory !== 'function') {
|
|
170
184
|
throw new Error(
|
|
171
|
-
'Failed to load whisper.node WASM runtime. Make sure wasm/whisper-node
|
|
185
|
+
'Failed to load whisper.node WASM runtime. Make sure the selected wasm/whisper-node*.js artifact is built and included in the package.',
|
|
172
186
|
)
|
|
173
187
|
}
|
|
174
188
|
|
|
@@ -193,6 +207,8 @@ const createWhisperNodeApi = function (root) {
|
|
|
193
207
|
delete options.runtimeScriptUrl
|
|
194
208
|
delete options.jsPath
|
|
195
209
|
delete options.wasmPath
|
|
210
|
+
delete options.threads
|
|
211
|
+
delete options.__resolvedThreads
|
|
196
212
|
delete options.locateFileBaseUrl
|
|
197
213
|
|
|
198
214
|
options.noInitialRun = true
|
|
@@ -219,7 +235,9 @@ const createWhisperNodeApi = function (root) {
|
|
|
219
235
|
}
|
|
220
236
|
}
|
|
221
237
|
|
|
222
|
-
|
|
238
|
+
var runtime = await moduleFactory(options)
|
|
239
|
+
runtime.__whisperNodeWasmThreads = useThreads
|
|
240
|
+
return runtime
|
|
223
241
|
})()
|
|
224
242
|
}
|
|
225
243
|
|
|
@@ -253,8 +271,9 @@ const createWhisperNodeApi = function (root) {
|
|
|
253
271
|
|
|
254
272
|
function createWorkerProxy() {
|
|
255
273
|
var indexScriptUrl = getIndexScriptUrl()
|
|
256
|
-
var
|
|
257
|
-
var
|
|
274
|
+
var useThreads = shouldUseThreadedRuntime()
|
|
275
|
+
var runtimeScriptUrl = getRuntimeScriptUrl(indexScriptUrl, useThreads)
|
|
276
|
+
var wasmUrl = getWasmBinaryUrl(runtimeScriptUrl, useThreads)
|
|
258
277
|
var workerScriptUrl = getWorkerScriptUrl(indexScriptUrl)
|
|
259
278
|
|
|
260
279
|
if (!indexScriptUrl || !runtimeScriptUrl || !workerScriptUrl) {
|
|
@@ -367,6 +386,7 @@ const createWhisperNodeApi = function (root) {
|
|
|
367
386
|
indexScriptUrl: indexScriptUrl,
|
|
368
387
|
runtimeScriptUrl: runtimeScriptUrl,
|
|
369
388
|
wasmPath: wasmUrl,
|
|
389
|
+
threads: useThreads,
|
|
370
390
|
runtimeOptions: getWorkerRuntimeOptions(),
|
|
371
391
|
locateFileBaseUrl:
|
|
372
392
|
runtimeOptions.locateFileBaseUrl || resolveUrl('.', runtimeScriptUrl),
|
|
@@ -743,17 +763,21 @@ const createWhisperNodeApi = function (root) {
|
|
|
743
763
|
return value
|
|
744
764
|
}
|
|
745
765
|
|
|
746
|
-
function normalizeMaxThreads(value) {
|
|
766
|
+
function normalizeMaxThreads(value, runtime) {
|
|
767
|
+
var maxThreads =
|
|
768
|
+
runtime && runtime.__whisperNodeWasmThreads !== true
|
|
769
|
+
? 1
|
|
770
|
+
: MAX_WASM_THREADS
|
|
747
771
|
var nThreads = Number(value)
|
|
748
772
|
if (!Number.isFinite(nThreads) || nThreads <= 0) {
|
|
749
773
|
return null
|
|
750
774
|
}
|
|
751
|
-
return Math.max(1, Math.min(
|
|
775
|
+
return Math.max(1, Math.min(maxThreads, Math.floor(nThreads)))
|
|
752
776
|
}
|
|
753
777
|
|
|
754
|
-
function normalizeThreadOptions(options) {
|
|
778
|
+
function normalizeThreadOptions(options, runtime) {
|
|
755
779
|
var normalized = Object.assign({}, options || {})
|
|
756
|
-
var maxThreads = normalizeMaxThreads(normalized.maxThreads)
|
|
780
|
+
var maxThreads = normalizeMaxThreads(normalized.maxThreads, runtime)
|
|
757
781
|
if (maxThreads) {
|
|
758
782
|
normalized.maxThreads = maxThreads
|
|
759
783
|
} else {
|
|
@@ -762,8 +786,8 @@ const createWhisperNodeApi = function (root) {
|
|
|
762
786
|
return normalized
|
|
763
787
|
}
|
|
764
788
|
|
|
765
|
-
function normalizeTranscribeOptions(options) {
|
|
766
|
-
var normalized = normalizeThreadOptions(options)
|
|
789
|
+
function normalizeTranscribeOptions(options, runtime) {
|
|
790
|
+
var normalized = normalizeThreadOptions(options, runtime)
|
|
767
791
|
if (typeof normalized.onProgress !== 'function') {
|
|
768
792
|
delete normalized.onProgress
|
|
769
793
|
}
|
|
@@ -1209,7 +1233,7 @@ const createWhisperNodeApi = function (root) {
|
|
|
1209
1233
|
await runtime.__wasm_transcribe(
|
|
1210
1234
|
id,
|
|
1211
1235
|
audio,
|
|
1212
|
-
normalizeTranscribeOptions(options),
|
|
1236
|
+
normalizeTranscribeOptions(options, runtime),
|
|
1213
1237
|
),
|
|
1214
1238
|
)
|
|
1215
1239
|
if (isCancelled && isCancelled()) {
|
|
@@ -1258,7 +1282,8 @@ const createWhisperNodeApi = function (root) {
|
|
|
1258
1282
|
|
|
1259
1283
|
WhisperContextInstance.prototype.bench = async function (nThreads) {
|
|
1260
1284
|
this._assertValid()
|
|
1261
|
-
|
|
1285
|
+
var threads = normalizeMaxThreads(nThreads || 1, this._runtime) || 1
|
|
1286
|
+
return unwrapWasmResult(await this._runtime.__wasm_bench(this._id, threads))
|
|
1262
1287
|
}
|
|
1263
1288
|
|
|
1264
1289
|
WhisperContextInstance.prototype.release = function () {
|
|
@@ -1352,7 +1377,7 @@ const createWhisperNodeApi = function (root) {
|
|
|
1352
1377
|
var modelSource = options.filePath || options.modelUrl
|
|
1353
1378
|
var runtime = await loadRuntime()
|
|
1354
1379
|
var useGpu = false
|
|
1355
|
-
var nThreads = options.nThreads || 1
|
|
1380
|
+
var nThreads = normalizeMaxThreads(options.nThreads || 1, runtime) || 1
|
|
1356
1381
|
|
|
1357
1382
|
if (options.useGpu === true) {
|
|
1358
1383
|
emitLog(
|
|
@@ -1448,6 +1473,7 @@ const createWhisperNodeApi = function (root) {
|
|
|
1448
1473
|
initWhisperVad: initWhisperVad,
|
|
1449
1474
|
toggleNativeLog: toggleNativeLog,
|
|
1450
1475
|
addNativeLogListener: addNativeLogListener,
|
|
1476
|
+
isWasmThreadsSupported: isWasmThreadsSupported,
|
|
1451
1477
|
DEFAULT_WASM_MODEL_SIZE_LIMIT_BYTES: 1500 * MIB,
|
|
1452
1478
|
MAX_WASM_THREADS: MAX_WASM_THREADS,
|
|
1453
1479
|
WASM_CONFIG_PATHS: WASM_CONFIG_PATHS,
|
|
@@ -1472,6 +1498,7 @@ export const initWhisper = api.initWhisper
|
|
|
1472
1498
|
export const initWhisperVad = api.initWhisperVad
|
|
1473
1499
|
export const toggleNativeLog = api.toggleNativeLog
|
|
1474
1500
|
export const addNativeLogListener = api.addNativeLogListener
|
|
1501
|
+
export const isWasmThreadsSupported = api.isWasmThreadsSupported
|
|
1475
1502
|
|
|
1476
1503
|
if (root) {
|
|
1477
1504
|
root.WhisperNodeWasm = api
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fugood/node-whisper-wasm",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.21",
|
|
4
4
|
"description": "Browser WASM module for whisper.node",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -42,6 +42,8 @@
|
|
|
42
42
|
"README.md",
|
|
43
43
|
"worker.js",
|
|
44
44
|
"wasm/whisper-node.js",
|
|
45
|
-
"wasm/whisper-node.wasm"
|
|
45
|
+
"wasm/whisper-node.wasm",
|
|
46
|
+
"wasm/whisper-node.threads.js",
|
|
47
|
+
"wasm/whisper-node.threads.wasm"
|
|
46
48
|
]
|
|
47
49
|
}
|