@fugood/node-whisper-wasm 1.0.19 → 1.0.20
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 +32 -17
- package/index.d.ts +21 -0
- package/index.js +291 -61
- package/package.json +5 -4
- package/wasm/whisper-node.js +2 -0
- package/{whisper-node.wasm → wasm/whisper-node.wasm} +0 -0
- package/worker.js +12 -11
- package/whisper-node.js +0 -2
package/README.md
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
# @fugood/node-whisper-wasm
|
|
2
2
|
|
|
3
|
-
Browser WASM package for `@fugood/whisper.node`.
|
|
3
|
+
Browser WASM implementation package for `@fugood/whisper.node`. Application
|
|
4
|
+
code should import `@fugood/whisper.node`; this package is pulled in by the main
|
|
5
|
+
package for browser builds.
|
|
4
6
|
|
|
5
7
|
The package exposes the same high-level context API as the native packages, but
|
|
6
8
|
model and audio file paths are fetched as URLs and copied into the Emscripten
|
|
7
9
|
filesystem before inference.
|
|
8
10
|
|
|
9
11
|
```js
|
|
10
|
-
|
|
12
|
+
import { initWhisper } from '@fugood/whisper.node'
|
|
13
|
+
|
|
14
|
+
const whisper = await initWhisper({
|
|
11
15
|
filePath: 'https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.en.bin',
|
|
12
16
|
useGpu: false,
|
|
13
17
|
})
|
|
@@ -23,18 +27,29 @@ await whisper.release()
|
|
|
23
27
|
The WASM build uses pthreads, so browsers must serve the page with COOP/COEP
|
|
24
28
|
headers and expose `SharedArrayBuffer`. Whisper transcription defaults to up to
|
|
25
29
|
8 threads based on browser hardware concurrency; pass `maxThreads` to override
|
|
26
|
-
it. Browser
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
`
|
|
30
|
+
it. Browser WASM clamps `maxThreads` to the compiled pool limit of 8. Browser
|
|
31
|
+
pages run model loading, transcription, benchmarks, and VAD in a dedicated
|
|
32
|
+
module worker by default so the UI thread can keep rendering. Use the main
|
|
33
|
+
`whisper.node` package entrypoint in browser code:
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
import { configureWasm, initWhisper } from '@fugood/whisper.node'
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Use `configureWasm({ worker: false })` only when you explicitly need the
|
|
40
|
+
in-thread runtime, or pass `workerPath`, `jsPath`, and `wasmPath` when serving
|
|
41
|
+
the package files from custom URLs. The older `workerUrl` and
|
|
42
|
+
`runtimeScriptUrl` option names still work. Model downloads are cached in
|
|
43
|
+
browser Cache Storage by default. Pass `cacheModel: false` to disable persistent
|
|
44
|
+
caching, `modelCacheName` to isolate the cache namespace, or `modelCacheKey`
|
|
45
|
+
when the fetch URL is a proxy or signed URL but should reuse the same cached
|
|
46
|
+
model. Set `useGpu: true` only with a package built using `GGML_WEBGPU=ON` and a
|
|
47
|
+
browser that exposes `navigator.gpu`. VAD currently falls back to CPU in the
|
|
48
|
+
browser package because the Silero VAD graph hits unsupported WebGPU ops.
|
|
49
|
+
|
|
50
|
+
The default build emits `wasm/whisper-node.js` and `wasm/whisper-node.wasm`.
|
|
51
|
+
Use `bash scripts/build-wasm.sh --single-file` only when you want the WASM
|
|
52
|
+
binary embedded into `wasm/whisper-node.js`. Modern Emscripten embeds the
|
|
53
|
+
pthread worker bootstrap in the main JS file, so a separate
|
|
54
|
+
`whisper-node.worker.js` is not expected. The package module worker that keeps
|
|
55
|
+
UI work off the main thread is `worker.js`.
|
package/index.d.ts
CHANGED
|
@@ -4,6 +4,9 @@ export interface NativeContextOptions {
|
|
|
4
4
|
useFlashAttn?: boolean
|
|
5
5
|
useGpu?: boolean
|
|
6
6
|
maxModelBytes?: number
|
|
7
|
+
cacheModel?: boolean
|
|
8
|
+
modelCacheName?: string
|
|
9
|
+
modelCacheKey?: string
|
|
7
10
|
worker?: boolean
|
|
8
11
|
}
|
|
9
12
|
|
|
@@ -13,6 +16,9 @@ export interface NativeVadContextOptions {
|
|
|
13
16
|
useGpu?: boolean
|
|
14
17
|
nThreads?: number
|
|
15
18
|
maxModelBytes?: number
|
|
19
|
+
cacheModel?: boolean
|
|
20
|
+
modelCacheName?: string
|
|
21
|
+
modelCacheKey?: string
|
|
16
22
|
worker?: boolean
|
|
17
23
|
}
|
|
18
24
|
|
|
@@ -136,12 +142,18 @@ export interface Module {
|
|
|
136
142
|
export interface WasmRuntimeOptions {
|
|
137
143
|
worker?: boolean
|
|
138
144
|
workerUrl?: string
|
|
145
|
+
workerPath?: string
|
|
139
146
|
indexScriptUrl?: string
|
|
140
147
|
scriptUrl?: string
|
|
141
148
|
runtimeScriptUrl?: string
|
|
149
|
+
jsPath?: string
|
|
150
|
+
wasmPath?: string
|
|
142
151
|
locateFileBaseUrl?: string
|
|
143
152
|
locateFile?: (path: string, prefix: string) => string
|
|
144
153
|
mainScriptUrlOrBlob?: string | Blob
|
|
154
|
+
modelCacheName?: string
|
|
155
|
+
moduleFactory?: (options?: Record<string, unknown>) => Promise<unknown> | unknown
|
|
156
|
+
moduleOptions?: Record<string, unknown>
|
|
145
157
|
print?: (text: string) => void
|
|
146
158
|
printErr?: (text: string) => void
|
|
147
159
|
}
|
|
@@ -149,6 +161,13 @@ export interface WasmRuntimeOptions {
|
|
|
149
161
|
export declare const WhisperContext: Module['WhisperContext']
|
|
150
162
|
export declare const WhisperVadContext: Module['WhisperVadContext']
|
|
151
163
|
export declare const DEFAULT_WASM_MODEL_SIZE_LIMIT_BYTES: number
|
|
164
|
+
export declare const MAX_WASM_THREADS: number
|
|
165
|
+
export declare const WASM_CONFIG_PATHS: {
|
|
166
|
+
index: string
|
|
167
|
+
js: string
|
|
168
|
+
wasm: string
|
|
169
|
+
worker: string
|
|
170
|
+
}
|
|
152
171
|
|
|
153
172
|
export declare function configureWasm(options: WasmRuntimeOptions): void
|
|
154
173
|
export declare function loadWasmModule(): Promise<unknown>
|
|
@@ -178,6 +197,8 @@ declare const _default: {
|
|
|
178
197
|
toggleNativeLog: typeof toggleNativeLog
|
|
179
198
|
addNativeLogListener: typeof addNativeLogListener
|
|
180
199
|
DEFAULT_WASM_MODEL_SIZE_LIMIT_BYTES: typeof DEFAULT_WASM_MODEL_SIZE_LIMIT_BYTES
|
|
200
|
+
MAX_WASM_THREADS: typeof MAX_WASM_THREADS
|
|
201
|
+
WASM_CONFIG_PATHS: typeof WASM_CONFIG_PATHS
|
|
181
202
|
}
|
|
182
203
|
|
|
183
204
|
export default _default
|
package/index.js
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
|
|
2
|
-
if (typeof module === 'object' && module.exports) {
|
|
3
|
-
module.exports = factory(function () {
|
|
4
|
-
return require('./whisper-node.js')
|
|
5
|
-
}, root)
|
|
6
|
-
} else {
|
|
7
|
-
root.WhisperNodeWasm = factory(function () {
|
|
8
|
-
return root.createWhisperNodeModule
|
|
9
|
-
}, root)
|
|
10
|
-
}
|
|
11
|
-
})(
|
|
1
|
+
const root =
|
|
12
2
|
typeof globalThis !== 'undefined'
|
|
13
3
|
? globalThis
|
|
14
4
|
: typeof self !== 'undefined'
|
|
15
5
|
? self
|
|
16
6
|
: typeof window !== 'undefined'
|
|
17
7
|
? window
|
|
18
|
-
:
|
|
19
|
-
|
|
8
|
+
: undefined
|
|
9
|
+
|
|
10
|
+
export const WASM_CONFIG_PATHS = {
|
|
11
|
+
index: new URL('./index.js', import.meta.url).href,
|
|
12
|
+
js: new URL('./wasm/whisper-node.js', import.meta.url).href,
|
|
13
|
+
wasm: new URL('./wasm/whisper-node.wasm', import.meta.url).href,
|
|
14
|
+
worker: new URL('./worker.js', import.meta.url).href,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const createWhisperNodeApi = function (root) {
|
|
20
18
|
'use strict'
|
|
21
19
|
|
|
22
20
|
var SAMPLE_RATE = 16000
|
|
23
21
|
var MIB = 1024 * 1024
|
|
24
22
|
var FIREFOX_MODEL_LIMIT_BYTES = 256 * MIB
|
|
25
23
|
var MODEL_MEMORY_RATIO = 0.75
|
|
24
|
+
var MAX_WASM_THREADS = 8
|
|
25
|
+
var DEFAULT_MODEL_CACHE_NAME = 'whisper.node.wasm.models'
|
|
26
26
|
|
|
27
27
|
var runtimePromise = null
|
|
28
28
|
var runtimeOptions = {}
|
|
29
29
|
var workerProxyPromise = null
|
|
30
|
-
var capturedScriptUrl =
|
|
30
|
+
var capturedScriptUrl = WASM_CONFIG_PATHS.index
|
|
31
31
|
var modelCache = Object.create(null)
|
|
32
32
|
var logEnabled = false
|
|
33
33
|
var logListeners = []
|
|
@@ -40,19 +40,8 @@
|
|
|
40
40
|
runtimeOptions = Object.assign({}, runtimeOptions, options || {})
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
function getCurrentScriptUrl() {
|
|
44
|
-
if (
|
|
45
|
-
root.document &&
|
|
46
|
-
root.document.currentScript &&
|
|
47
|
-
root.document.currentScript.src
|
|
48
|
-
) {
|
|
49
|
-
return root.document.currentScript.src
|
|
50
|
-
}
|
|
51
|
-
return null
|
|
52
|
-
}
|
|
53
|
-
|
|
54
43
|
function isBrowserLike() {
|
|
55
|
-
return typeof root.window !== 'undefined' || typeof root.
|
|
44
|
+
return typeof root.window !== 'undefined' || typeof root.WorkerGlobalScope !== 'undefined'
|
|
56
45
|
}
|
|
57
46
|
|
|
58
47
|
function isMainBrowserThread() {
|
|
@@ -80,19 +69,27 @@
|
|
|
80
69
|
}
|
|
81
70
|
|
|
82
71
|
function getRuntimeScriptUrl(indexScriptUrl) {
|
|
83
|
-
var configured = runtimeOptions.runtimeScriptUrl
|
|
72
|
+
var configured = runtimeOptions.jsPath || runtimeOptions.runtimeScriptUrl
|
|
84
73
|
if (configured) {
|
|
85
74
|
return resolveUrl(configured, indexScriptUrl)
|
|
86
75
|
}
|
|
87
|
-
return
|
|
76
|
+
return WASM_CONFIG_PATHS.js
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function getWasmBinaryUrl(runtimeScriptUrl) {
|
|
80
|
+
var configured = runtimeOptions.wasmPath
|
|
81
|
+
if (configured) {
|
|
82
|
+
return resolveUrl(configured, runtimeScriptUrl)
|
|
83
|
+
}
|
|
84
|
+
return WASM_CONFIG_PATHS.wasm
|
|
88
85
|
}
|
|
89
86
|
|
|
90
87
|
function getWorkerScriptUrl(indexScriptUrl) {
|
|
91
|
-
var configured = runtimeOptions.workerUrl
|
|
88
|
+
var configured = runtimeOptions.workerPath || runtimeOptions.workerUrl
|
|
92
89
|
if (configured) {
|
|
93
90
|
return resolveUrl(configured, indexScriptUrl)
|
|
94
91
|
}
|
|
95
|
-
return
|
|
92
|
+
return WASM_CONFIG_PATHS.worker
|
|
96
93
|
}
|
|
97
94
|
|
|
98
95
|
function shouldUseWorker(options) {
|
|
@@ -108,11 +105,16 @@
|
|
|
108
105
|
var blocked = {
|
|
109
106
|
worker: true,
|
|
110
107
|
workerUrl: true,
|
|
108
|
+
workerPath: true,
|
|
111
109
|
indexScriptUrl: true,
|
|
112
110
|
scriptUrl: true,
|
|
113
111
|
runtimeScriptUrl: true,
|
|
112
|
+
jsPath: true,
|
|
113
|
+
wasmPath: true,
|
|
114
114
|
locateFileBaseUrl: true,
|
|
115
115
|
locateFile: true,
|
|
116
|
+
moduleFactory: true,
|
|
117
|
+
moduleOptions: true,
|
|
116
118
|
print: true,
|
|
117
119
|
printErr: true,
|
|
118
120
|
mainScriptUrlOrBlob: true,
|
|
@@ -151,35 +153,74 @@
|
|
|
151
153
|
if (!runtimePromise) {
|
|
152
154
|
assertThreadSupport()
|
|
153
155
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
156
|
+
runtimePromise = (async function () {
|
|
157
|
+
var indexScriptUrl = getIndexScriptUrl()
|
|
158
|
+
var runtimeScriptUrl = getRuntimeScriptUrl(indexScriptUrl)
|
|
159
|
+
var wasmUrl = getWasmBinaryUrl(runtimeScriptUrl)
|
|
160
|
+
var moduleFactory = runtimeOptions.moduleFactory
|
|
161
|
+
|
|
162
|
+
if (!moduleFactory) {
|
|
163
|
+
var runtimeModule = await import(runtimeScriptUrl)
|
|
164
|
+
moduleFactory =
|
|
165
|
+
runtimeModule.default ||
|
|
166
|
+
runtimeModule.createWhisperNodeModule ||
|
|
167
|
+
runtimeModule
|
|
168
|
+
}
|
|
169
|
+
if (typeof moduleFactory !== 'function') {
|
|
170
|
+
throw new Error(
|
|
171
|
+
'Failed to load whisper.node WASM runtime. Make sure wasm/whisper-node.js is built and included in the package.',
|
|
172
|
+
)
|
|
173
|
+
}
|
|
167
174
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
175
|
+
var options = Object.assign(
|
|
176
|
+
{},
|
|
177
|
+
runtimeOptions.moduleOptions || {},
|
|
178
|
+
runtimeOptions,
|
|
179
|
+
)
|
|
180
|
+
var userPrint = options.print
|
|
181
|
+
var userPrintErr = options.printErr
|
|
182
|
+
var userLocateFile = options.locateFile
|
|
183
|
+
var locateFileBaseUrl =
|
|
184
|
+
options.locateFileBaseUrl || resolveUrl('.', runtimeScriptUrl)
|
|
185
|
+
|
|
186
|
+
delete options.moduleFactory
|
|
187
|
+
delete options.moduleOptions
|
|
188
|
+
delete options.worker
|
|
189
|
+
delete options.workerUrl
|
|
190
|
+
delete options.workerPath
|
|
191
|
+
delete options.indexScriptUrl
|
|
192
|
+
delete options.scriptUrl
|
|
193
|
+
delete options.runtimeScriptUrl
|
|
194
|
+
delete options.jsPath
|
|
195
|
+
delete options.wasmPath
|
|
196
|
+
delete options.locateFileBaseUrl
|
|
197
|
+
|
|
198
|
+
options.noInitialRun = true
|
|
199
|
+
options.mainScriptUrlOrBlob = options.mainScriptUrlOrBlob || runtimeScriptUrl
|
|
200
|
+
options.locateFile = function (path, prefix) {
|
|
201
|
+
if (path === 'whisper-node.wasm' || path.slice(-5) === '.wasm') {
|
|
202
|
+
return wasmUrl
|
|
203
|
+
}
|
|
204
|
+
if (typeof userLocateFile === 'function') {
|
|
205
|
+
return userLocateFile(path, prefix)
|
|
206
|
+
}
|
|
207
|
+
return resolveUrl(path, locateFileBaseUrl || prefix)
|
|
173
208
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
209
|
+
options.print = function (text) {
|
|
210
|
+
emitLog('INFO', String(text))
|
|
211
|
+
if (typeof userPrint === 'function') {
|
|
212
|
+
userPrint(text)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
options.printErr = function (text) {
|
|
216
|
+
emitLog('ERROR', String(text))
|
|
217
|
+
if (typeof userPrintErr === 'function') {
|
|
218
|
+
userPrintErr(text)
|
|
219
|
+
}
|
|
179
220
|
}
|
|
180
|
-
}
|
|
181
221
|
|
|
182
|
-
|
|
222
|
+
return moduleFactory(options)
|
|
223
|
+
})()
|
|
183
224
|
}
|
|
184
225
|
|
|
185
226
|
return runtimePromise
|
|
@@ -213,6 +254,7 @@
|
|
|
213
254
|
function createWorkerProxy() {
|
|
214
255
|
var indexScriptUrl = getIndexScriptUrl()
|
|
215
256
|
var runtimeScriptUrl = getRuntimeScriptUrl(indexScriptUrl)
|
|
257
|
+
var wasmUrl = getWasmBinaryUrl(runtimeScriptUrl)
|
|
216
258
|
var workerScriptUrl = getWorkerScriptUrl(indexScriptUrl)
|
|
217
259
|
|
|
218
260
|
if (!indexScriptUrl || !runtimeScriptUrl || !workerScriptUrl) {
|
|
@@ -220,6 +262,7 @@
|
|
|
220
262
|
}
|
|
221
263
|
|
|
222
264
|
var worker = new root.Worker(workerScriptUrl, {
|
|
265
|
+
type: 'module',
|
|
223
266
|
name: 'whisper.node.wasm',
|
|
224
267
|
})
|
|
225
268
|
var proxy = {
|
|
@@ -323,6 +366,7 @@
|
|
|
323
366
|
{
|
|
324
367
|
indexScriptUrl: indexScriptUrl,
|
|
325
368
|
runtimeScriptUrl: runtimeScriptUrl,
|
|
369
|
+
wasmPath: wasmUrl,
|
|
326
370
|
runtimeOptions: getWorkerRuntimeOptions(),
|
|
327
371
|
locateFileBaseUrl:
|
|
328
372
|
runtimeOptions.locateFileBaseUrl || resolveUrl('.', runtimeScriptUrl),
|
|
@@ -366,6 +410,39 @@
|
|
|
366
410
|
throw new Error('fetch is required to load models or audio by URL')
|
|
367
411
|
}
|
|
368
412
|
|
|
413
|
+
function getCacheStorage() {
|
|
414
|
+
return root.caches || null
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function isModelCacheEnabled(options) {
|
|
418
|
+
return !options || options.cacheModel !== false
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function getModelCacheName(options) {
|
|
422
|
+
return (
|
|
423
|
+
(options && options.modelCacheName) ||
|
|
424
|
+
runtimeOptions.modelCacheName ||
|
|
425
|
+
DEFAULT_MODEL_CACHE_NAME
|
|
426
|
+
)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function getModelCacheKey(source, options) {
|
|
430
|
+
var key = (options && options.modelCacheKey) || source
|
|
431
|
+
try {
|
|
432
|
+
return new URL(key, root.location && root.location.href).href
|
|
433
|
+
} catch (_) {
|
|
434
|
+
return key
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function canUseModelCache(options) {
|
|
439
|
+
return (
|
|
440
|
+
isModelCacheEnabled(options) &&
|
|
441
|
+
!!getCacheStorage() &&
|
|
442
|
+
typeof root.Response === 'function'
|
|
443
|
+
)
|
|
444
|
+
}
|
|
445
|
+
|
|
369
446
|
function formatBytes(bytes) {
|
|
370
447
|
if (bytes >= 1024 * MIB) {
|
|
371
448
|
return (bytes / (1024 * MIB)).toFixed(2) + ' GiB'
|
|
@@ -497,6 +574,106 @@
|
|
|
497
574
|
return buffer
|
|
498
575
|
}
|
|
499
576
|
|
|
577
|
+
async function readCachedModel(source, limit, options) {
|
|
578
|
+
if (!canUseModelCache(options)) {
|
|
579
|
+
return null
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
var cacheName = getModelCacheName(options)
|
|
583
|
+
var cacheKey = getModelCacheKey(source, options)
|
|
584
|
+
|
|
585
|
+
try {
|
|
586
|
+
var cache = await getCacheStorage().open(cacheName)
|
|
587
|
+
var response = await cache.match(cacheKey)
|
|
588
|
+
if (!response) {
|
|
589
|
+
return null
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
var buffer = await response.arrayBuffer()
|
|
593
|
+
if (limit) {
|
|
594
|
+
assertModelSize(buffer.byteLength, limit, source)
|
|
595
|
+
}
|
|
596
|
+
emitLog('INFO', 'Loaded cached WASM model: ' + source)
|
|
597
|
+
return {
|
|
598
|
+
buffer: buffer,
|
|
599
|
+
cacheHit: true,
|
|
600
|
+
cacheStored: false,
|
|
601
|
+
cacheName: cacheName,
|
|
602
|
+
cacheKey: cacheKey,
|
|
603
|
+
}
|
|
604
|
+
} catch (error) {
|
|
605
|
+
emitLog(
|
|
606
|
+
'WARN',
|
|
607
|
+
'Failed to read cached WASM model ' +
|
|
608
|
+
source +
|
|
609
|
+
': ' +
|
|
610
|
+
(error && error.message ? error.message : String(error)),
|
|
611
|
+
)
|
|
612
|
+
return null
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
async function writeCachedModel(source, buffer, options) {
|
|
617
|
+
if (!canUseModelCache(options)) {
|
|
618
|
+
return {
|
|
619
|
+
cacheStored: false,
|
|
620
|
+
cacheName: null,
|
|
621
|
+
cacheKey: null,
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
var cacheName = getModelCacheName(options)
|
|
626
|
+
var cacheKey = getModelCacheKey(source, options)
|
|
627
|
+
|
|
628
|
+
try {
|
|
629
|
+
var cache = await getCacheStorage().open(cacheName)
|
|
630
|
+
await cache.put(
|
|
631
|
+
cacheKey,
|
|
632
|
+
new root.Response(buffer.slice(0), {
|
|
633
|
+
headers: {
|
|
634
|
+
'content-type': 'application/octet-stream',
|
|
635
|
+
},
|
|
636
|
+
}),
|
|
637
|
+
)
|
|
638
|
+
emitLog('INFO', 'Cached WASM model download: ' + source)
|
|
639
|
+
return {
|
|
640
|
+
cacheStored: true,
|
|
641
|
+
cacheName: cacheName,
|
|
642
|
+
cacheKey: cacheKey,
|
|
643
|
+
}
|
|
644
|
+
} catch (error) {
|
|
645
|
+
emitLog(
|
|
646
|
+
'WARN',
|
|
647
|
+
'Failed to cache WASM model ' +
|
|
648
|
+
source +
|
|
649
|
+
': ' +
|
|
650
|
+
(error && error.message ? error.message : String(error)),
|
|
651
|
+
)
|
|
652
|
+
return {
|
|
653
|
+
cacheStored: false,
|
|
654
|
+
cacheName: cacheName,
|
|
655
|
+
cacheKey: cacheKey,
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
async function fetchModelArrayBuffer(source, limit, options) {
|
|
661
|
+
var cached = await readCachedModel(source, limit, options)
|
|
662
|
+
if (cached) {
|
|
663
|
+
return cached
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
var buffer = await fetchArrayBuffer(source, limit)
|
|
667
|
+
var cache = await writeCachedModel(source, buffer, options)
|
|
668
|
+
return {
|
|
669
|
+
buffer: buffer,
|
|
670
|
+
cacheHit: false,
|
|
671
|
+
cacheStored: cache.cacheStored,
|
|
672
|
+
cacheName: cache.cacheName,
|
|
673
|
+
cacheKey: cache.cacheKey,
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
500
677
|
async function ensureModel(runtime, source, kind, options) {
|
|
501
678
|
if (!source) {
|
|
502
679
|
throw new Error('Model path is required')
|
|
@@ -510,8 +687,8 @@
|
|
|
510
687
|
if (!modelCache[cacheKey]) {
|
|
511
688
|
modelCache[cacheKey] = (async function () {
|
|
512
689
|
var limit = getModelSizeLimit(runtime, options)
|
|
513
|
-
var
|
|
514
|
-
var bytes = new Uint8Array(buffer)
|
|
690
|
+
var loaded = await fetchModelArrayBuffer(source, limit, options)
|
|
691
|
+
var bytes = new Uint8Array(loaded.buffer)
|
|
515
692
|
var virtualPath =
|
|
516
693
|
'/models/' + kind + '-' + hashString(source) + '-' + basenameFromUrl(source)
|
|
517
694
|
|
|
@@ -521,6 +698,10 @@
|
|
|
521
698
|
return {
|
|
522
699
|
virtualPath: virtualPath,
|
|
523
700
|
bytes: bytes.byteLength,
|
|
701
|
+
cacheHit: loaded.cacheHit,
|
|
702
|
+
cacheStored: loaded.cacheStored,
|
|
703
|
+
cacheName: loaded.cacheName,
|
|
704
|
+
cacheKey: loaded.cacheKey,
|
|
524
705
|
}
|
|
525
706
|
})()
|
|
526
707
|
}
|
|
@@ -562,8 +743,27 @@
|
|
|
562
743
|
return value
|
|
563
744
|
}
|
|
564
745
|
|
|
565
|
-
function
|
|
746
|
+
function normalizeMaxThreads(value) {
|
|
747
|
+
var nThreads = Number(value)
|
|
748
|
+
if (!Number.isFinite(nThreads) || nThreads <= 0) {
|
|
749
|
+
return null
|
|
750
|
+
}
|
|
751
|
+
return Math.max(1, Math.min(MAX_WASM_THREADS, Math.floor(nThreads)))
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function normalizeThreadOptions(options) {
|
|
566
755
|
var normalized = Object.assign({}, options || {})
|
|
756
|
+
var maxThreads = normalizeMaxThreads(normalized.maxThreads)
|
|
757
|
+
if (maxThreads) {
|
|
758
|
+
normalized.maxThreads = maxThreads
|
|
759
|
+
} else {
|
|
760
|
+
delete normalized.maxThreads
|
|
761
|
+
}
|
|
762
|
+
return normalized
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
function normalizeTranscribeOptions(options) {
|
|
766
|
+
var normalized = normalizeThreadOptions(options)
|
|
567
767
|
if (typeof normalized.onProgress !== 'function') {
|
|
568
768
|
delete normalized.onProgress
|
|
569
769
|
}
|
|
@@ -574,7 +774,7 @@
|
|
|
574
774
|
}
|
|
575
775
|
|
|
576
776
|
function splitTranscribeOptions(options) {
|
|
577
|
-
var normalized =
|
|
777
|
+
var normalized = normalizeThreadOptions(options)
|
|
578
778
|
var callbacks = {}
|
|
579
779
|
|
|
580
780
|
if (typeof normalized.onProgress === 'function') {
|
|
@@ -966,6 +1166,10 @@
|
|
|
966
1166
|
useGpu: useGpu,
|
|
967
1167
|
useFlashAttn: options.useFlashAttn === true,
|
|
968
1168
|
bytes: model.bytes,
|
|
1169
|
+
cacheHit: model.cacheHit,
|
|
1170
|
+
cacheStored: model.cacheStored,
|
|
1171
|
+
cacheName: model.cacheName,
|
|
1172
|
+
cacheKey: model.cacheKey,
|
|
969
1173
|
wasm: true,
|
|
970
1174
|
})
|
|
971
1175
|
}
|
|
@@ -1168,6 +1372,10 @@
|
|
|
1168
1372
|
useGpu: useGpu,
|
|
1169
1373
|
nThreads: init.nThreads,
|
|
1170
1374
|
bytes: model.bytes,
|
|
1375
|
+
cacheHit: model.cacheHit,
|
|
1376
|
+
cacheStored: model.cacheStored,
|
|
1377
|
+
cacheName: model.cacheName,
|
|
1378
|
+
cacheKey: model.cacheKey,
|
|
1171
1379
|
wasm: true,
|
|
1172
1380
|
})
|
|
1173
1381
|
}
|
|
@@ -1241,10 +1449,32 @@
|
|
|
1241
1449
|
toggleNativeLog: toggleNativeLog,
|
|
1242
1450
|
addNativeLogListener: addNativeLogListener,
|
|
1243
1451
|
DEFAULT_WASM_MODEL_SIZE_LIMIT_BYTES: 1500 * MIB,
|
|
1452
|
+
MAX_WASM_THREADS: MAX_WASM_THREADS,
|
|
1453
|
+
WASM_CONFIG_PATHS: WASM_CONFIG_PATHS,
|
|
1244
1454
|
}
|
|
1245
1455
|
|
|
1246
1456
|
api.default = api
|
|
1247
1457
|
|
|
1248
1458
|
return api
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
const api = createWhisperNodeApi(root)
|
|
1462
|
+
|
|
1463
|
+
export const WhisperContext = api.WhisperContext
|
|
1464
|
+
export const WhisperVadContext = api.WhisperVadContext
|
|
1465
|
+
export const DEFAULT_WASM_MODEL_SIZE_LIMIT_BYTES =
|
|
1466
|
+
api.DEFAULT_WASM_MODEL_SIZE_LIMIT_BYTES
|
|
1467
|
+
export const MAX_WASM_THREADS = api.MAX_WASM_THREADS
|
|
1468
|
+
export const configureWasm = api.configureWasm
|
|
1469
|
+
export const loadWasmModule = api.loadWasmModule
|
|
1470
|
+
export const loadWhisperModule = api.loadWhisperModule
|
|
1471
|
+
export const initWhisper = api.initWhisper
|
|
1472
|
+
export const initWhisperVad = api.initWhisperVad
|
|
1473
|
+
export const toggleNativeLog = api.toggleNativeLog
|
|
1474
|
+
export const addNativeLogListener = api.addNativeLogListener
|
|
1475
|
+
|
|
1476
|
+
if (root) {
|
|
1477
|
+
root.WhisperNodeWasm = api
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
export default api
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fugood/node-whisper-wasm",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.20",
|
|
4
4
|
"description": "Browser WASM module for whisper.node",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"main": "index.js",
|
|
6
7
|
"browser": "index.js",
|
|
7
8
|
"types": "index.d.ts",
|
|
@@ -39,8 +40,8 @@
|
|
|
39
40
|
"index.js",
|
|
40
41
|
"index.d.ts",
|
|
41
42
|
"README.md",
|
|
42
|
-
"whisper-node.js",
|
|
43
43
|
"worker.js",
|
|
44
|
-
"whisper-node.
|
|
44
|
+
"wasm/whisper-node.js",
|
|
45
|
+
"wasm/whisper-node.wasm"
|
|
45
46
|
]
|
|
46
|
-
}
|
|
47
|
+
}
|