@fugood/node-whisper-wasm 1.0.21 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,9 +4,10 @@ Browser WASM implementation package for `@fugood/whisper.node`. Application
4
4
  code should import `@fugood/whisper.node`; this package is pulled in by the main
5
5
  package for browser builds.
6
6
 
7
- The package exposes the same high-level context API as the native packages, but
8
- model and audio file paths are fetched as URLs and copied into the Emscripten
9
- filesystem before inference.
7
+ The package exposes the same high-level context API as the native packages
8
+ (`initWhisper`, `initWhisperVad`, and `initParakeet`), but model and audio file
9
+ paths are fetched as URLs and copied into the Emscripten filesystem before
10
+ inference.
10
11
 
11
12
  ```js
12
13
  import { initWhisper } from '@fugood/whisper.node'
package/index.d.ts CHANGED
@@ -22,6 +22,17 @@ export interface NativeVadContextOptions {
22
22
  worker?: boolean
23
23
  }
24
24
 
25
+ export interface NativeParakeetContextOptions {
26
+ filePath: string
27
+ modelUrl?: string
28
+ useGpu?: boolean
29
+ maxModelBytes?: number
30
+ cacheModel?: boolean
31
+ modelCacheName?: string
32
+ modelCacheKey?: string
33
+ worker?: boolean
34
+ }
35
+
25
36
  export interface TranscribeOptions {
26
37
  language?: string
27
38
  translate?: boolean
@@ -61,6 +72,13 @@ export interface TranscribeResult {
61
72
  isAborted: boolean
62
73
  }
63
74
 
75
+ export interface ParakeetTranscribeOptions {
76
+ /** Number of threads to use during computation. */
77
+ maxThreads?: number
78
+ /** Override the model audio context size (0 uses the model default). */
79
+ audioCtx?: number
80
+ }
81
+
64
82
  export interface VadOptions {
65
83
  threshold?: number
66
84
  minSpeechDurationMs?: number
@@ -111,6 +129,32 @@ export interface WhisperContext {
111
129
  getModelInfo(): object
112
130
  }
113
131
 
132
+ export interface ParakeetContext {
133
+ transcribe(
134
+ filePath: string,
135
+ options?: ParakeetTranscribeOptions,
136
+ ): {
137
+ stop: () => Promise<void>
138
+ promise: Promise<TranscribeResult>
139
+ }
140
+ transcribeFile(
141
+ filePath: string,
142
+ options?: ParakeetTranscribeOptions,
143
+ ): {
144
+ stop: () => Promise<void>
145
+ promise: Promise<TranscribeResult>
146
+ }
147
+ transcribeData(
148
+ audioData: ArrayBuffer | ArrayBufferView | Float32Array,
149
+ options?: ParakeetTranscribeOptions,
150
+ ): {
151
+ stop: () => Promise<void>
152
+ promise: Promise<TranscribeResult>
153
+ }
154
+ release(): Promise<void>
155
+ getModelInfo(): object
156
+ }
157
+
114
158
  export interface WhisperVadContext {
115
159
  detectSpeech(filePath: string, options?: VadOptions): Promise<VadSegment[]>
116
160
  detectSpeechFile(filePath: string, options?: VadOptions): Promise<VadSegment[]>
@@ -137,6 +181,13 @@ export interface Module {
137
181
  callback?: (level: string, text: string) => void,
138
182
  ): void | Promise<void>
139
183
  }
184
+ ParakeetContext: {
185
+ new (options: NativeParakeetContextOptions): Promise<ParakeetContext>
186
+ toggleNativeLog(
187
+ enable: boolean,
188
+ callback?: (level: string, text: string) => void,
189
+ ): void | Promise<void>
190
+ }
140
191
  }
141
192
 
142
193
  export interface WasmRuntimeOptions {
@@ -161,6 +212,7 @@ export interface WasmRuntimeOptions {
161
212
 
162
213
  export declare const WhisperContext: Module['WhisperContext']
163
214
  export declare const WhisperVadContext: Module['WhisperVadContext']
215
+ export declare const ParakeetContext: Module['ParakeetContext']
164
216
  export declare const DEFAULT_WASM_MODEL_SIZE_LIMIT_BYTES: number
165
217
  export declare const MAX_WASM_THREADS: number
166
218
  export declare const WASM_CONFIG_PATHS: {
@@ -182,6 +234,9 @@ export declare function initWhisper(
182
234
  export declare function initWhisperVad(
183
235
  options: NativeVadContextOptions,
184
236
  ): Promise<WhisperVadContext>
237
+ export declare function initParakeet(
238
+ options: NativeParakeetContextOptions,
239
+ ): Promise<ParakeetContext>
185
240
  export declare function toggleNativeLog(
186
241
  enable: boolean,
187
242
  callback?: (level: string, text: string) => void,
@@ -189,17 +244,21 @@ export declare function toggleNativeLog(
189
244
  export declare function addNativeLogListener(
190
245
  listener: (level: string, text: string) => void,
191
246
  ): { remove: () => void }
247
+ export declare function isNativeLogEnabled(): boolean
192
248
 
193
249
  declare const _default: {
194
250
  WhisperContext: typeof WhisperContext
195
251
  WhisperVadContext: typeof WhisperVadContext
252
+ ParakeetContext: typeof ParakeetContext
196
253
  configureWasm: typeof configureWasm
197
254
  loadWasmModule: typeof loadWasmModule
198
255
  loadWhisperModule: typeof loadWhisperModule
199
256
  initWhisper: typeof initWhisper
200
257
  initWhisperVad: typeof initWhisperVad
258
+ initParakeet: typeof initParakeet
201
259
  toggleNativeLog: typeof toggleNativeLog
202
260
  addNativeLogListener: typeof addNativeLogListener
261
+ isNativeLogEnabled: typeof isNativeLogEnabled
203
262
  isWasmThreadsSupported: typeof isWasmThreadsSupported
204
263
  DEFAULT_WASM_MODEL_SIZE_LIMIT_BYTES: typeof DEFAULT_WASM_MODEL_SIZE_LIMIT_BYTES
205
264
  MAX_WASM_THREADS: typeof MAX_WASM_THREADS
package/index.js CHANGED
@@ -152,15 +152,32 @@ const createWhisperNodeApi = function (root) {
152
152
  return options
153
153
  }
154
154
 
155
+ function normalizeLogLevel(level) {
156
+ var normalized = String(level || '').toLowerCase()
157
+ if (normalized === 'warning') {
158
+ return 'warn'
159
+ }
160
+ if (
161
+ normalized === 'error' ||
162
+ normalized === 'warn' ||
163
+ normalized === 'info' ||
164
+ normalized === 'debug'
165
+ ) {
166
+ return normalized
167
+ }
168
+ return normalized || 'info'
169
+ }
170
+
155
171
  function emitLog(level, text) {
156
172
  if (!logEnabled) {
157
173
  return
158
174
  }
175
+ var normalizedLevel = normalizeLogLevel(level)
159
176
  if (typeof nativeLogCallback === 'function') {
160
- nativeLogCallback(level, text)
177
+ nativeLogCallback(normalizedLevel, text)
161
178
  }
162
179
  logListeners.slice().forEach(function (listener) {
163
- listener(level, text)
180
+ listener(normalizedLevel, text)
164
181
  })
165
182
  }
166
183
 
@@ -229,7 +246,7 @@ const createWhisperNodeApi = function (root) {
229
246
  }
230
247
  }
231
248
  options.printErr = function (text) {
232
- emitLog('ERROR', String(text))
249
+ emitLog('INFO', String(text))
233
250
  if (typeof userPrintErr === 'function') {
234
251
  userPrintErr(text)
235
252
  }
@@ -1038,9 +1055,18 @@ const createWhisperNodeApi = function (root) {
1038
1055
  return decodeAudioBuffer(buffer)
1039
1056
  }
1040
1057
 
1041
- function validateWebGpu(runtime, useGpu) {
1058
+ function warnWebGpuCpuFallback(error) {
1059
+ var reason = error && error.message ? error.message : String(error)
1060
+ console.warn(
1061
+ '[whisper.node] WebGPU WASM load failed (' +
1062
+ reason +
1063
+ '); falling back to CPU WASM',
1064
+ )
1065
+ }
1066
+
1067
+ function resolveWhisperGpu(runtime, useGpu) {
1042
1068
  if (!useGpu) {
1043
- return
1069
+ return false
1044
1070
  }
1045
1071
 
1046
1072
  if (
@@ -1048,14 +1074,22 @@ const createWhisperNodeApi = function (root) {
1048
1074
  typeof runtime.__wasm_webgpu_enabled !== 'function' ||
1049
1075
  !runtime.__wasm_webgpu_enabled()
1050
1076
  ) {
1051
- throw new Error(
1052
- 'This @fugood/node-whisper-wasm build was not compiled with GGML_WEBGPU=ON',
1077
+ warnWebGpuCpuFallback(
1078
+ new Error(
1079
+ 'This @fugood/node-whisper-wasm build was not compiled with GGML_WEBGPU=ON',
1080
+ ),
1053
1081
  )
1082
+ return false
1054
1083
  }
1055
1084
 
1056
1085
  if (!root.navigator || !root.navigator.gpu) {
1057
- throw new Error('WebGPU was requested, but navigator.gpu is not available')
1086
+ warnWebGpuCpuFallback(
1087
+ new Error('WebGPU was requested, but navigator.gpu is not available'),
1088
+ )
1089
+ return false
1058
1090
  }
1091
+
1092
+ return true
1059
1093
  }
1060
1094
 
1061
1095
  function WhisperContext(options) {
@@ -1171,24 +1205,36 @@ const createWhisperNodeApi = function (root) {
1171
1205
  options = options || {}
1172
1206
  var modelSource = options.filePath || options.modelUrl
1173
1207
  var runtime = await loadRuntime()
1174
- var useGpu = options.useGpu === true
1175
-
1176
- validateWebGpu(runtime, useGpu)
1208
+ var useGpu = resolveWhisperGpu(runtime, options.useGpu === true)
1177
1209
 
1178
1210
  var model = await ensureModel(runtime, modelSource, 'whisper', options)
1179
- var init = unwrapWasmResult(
1180
- await runtime.__wasm_init_whisper(
1181
- model.virtualPath,
1182
- useGpu,
1183
- options.useFlashAttn === true,
1184
- ),
1185
- )
1211
+ var useFlashAttn = useGpu && options.useFlashAttn === true
1212
+ var init
1213
+ try {
1214
+ init = unwrapWasmResult(
1215
+ await runtime.__wasm_init_whisper(
1216
+ model.virtualPath,
1217
+ useGpu,
1218
+ useFlashAttn,
1219
+ ),
1220
+ )
1221
+ } catch (error) {
1222
+ if (!useGpu) {
1223
+ throw error
1224
+ }
1225
+ warnWebGpuCpuFallback(error)
1226
+ useGpu = false
1227
+ useFlashAttn = false
1228
+ init = unwrapWasmResult(
1229
+ await runtime.__wasm_init_whisper(model.virtualPath, false, false),
1230
+ )
1231
+ }
1186
1232
 
1187
1233
  return new WhisperContextInstance(runtime, init.id, {
1188
1234
  filePath: modelSource,
1189
1235
  wasmFilePath: model.virtualPath,
1190
1236
  useGpu: useGpu,
1191
- useFlashAttn: options.useFlashAttn === true,
1237
+ useFlashAttn: useFlashAttn,
1192
1238
  bytes: model.bytes,
1193
1239
  cacheHit: model.cacheHit,
1194
1240
  cacheStored: model.cacheStored,
@@ -1294,6 +1340,244 @@ const createWhisperNodeApi = function (root) {
1294
1340
  return Promise.resolve()
1295
1341
  }
1296
1342
 
1343
+ function ParakeetContext(options) {
1344
+ if (shouldUseWorker(options || {})) {
1345
+ return createWorkerParakeetContext(options || {})
1346
+ }
1347
+ return createParakeetContext(options)
1348
+ }
1349
+
1350
+ ParakeetContext.toggleNativeLog = toggleNativeLog
1351
+ ParakeetContext.loadModelInfo = function (path) {
1352
+ return {
1353
+ path: path,
1354
+ type: 'parakeet',
1355
+ }
1356
+ }
1357
+
1358
+ async function createWorkerParakeetContext(options) {
1359
+ var proxy = await getWorkerProxy()
1360
+ if (!proxy) {
1361
+ return createParakeetContext(options)
1362
+ }
1363
+
1364
+ var created = await proxy.request('initParakeet', [options || {}])
1365
+ created.meta.worker = true
1366
+ return new WorkerParakeetContextInstance(proxy, created.id, created.meta)
1367
+ }
1368
+
1369
+ function WorkerParakeetContextInstance(proxy, id, meta) {
1370
+ this._proxy = proxy
1371
+ this._id = id
1372
+ this._meta = meta
1373
+ this._released = false
1374
+ }
1375
+
1376
+ WorkerParakeetContextInstance.prototype._assertValid = function () {
1377
+ if (this._released) {
1378
+ throw new Error('Invalid parakeet context')
1379
+ }
1380
+ }
1381
+
1382
+ WorkerParakeetContextInstance.prototype.getModelInfo = function () {
1383
+ return this._meta
1384
+ }
1385
+
1386
+ WorkerParakeetContextInstance.prototype._transcribeWorker = function (
1387
+ method,
1388
+ args,
1389
+ transfer,
1390
+ ) {
1391
+ this._assertValid()
1392
+
1393
+ var cancelled = false
1394
+ var operation = this._proxy.requestOperation(method, args, transfer, {})
1395
+ var proxy = this._proxy
1396
+
1397
+ return {
1398
+ _requestId: operation.id,
1399
+ stop: function () {
1400
+ cancelled = true
1401
+ proxy.cancel(operation.id)
1402
+ return Promise.resolve()
1403
+ },
1404
+ promise: operation.promise.then(function (result) {
1405
+ if (cancelled) {
1406
+ result.isAborted = true
1407
+ }
1408
+ return result
1409
+ }),
1410
+ }
1411
+ }
1412
+
1413
+ WorkerParakeetContextInstance.prototype.transcribeData = function (
1414
+ audioData,
1415
+ options,
1416
+ ) {
1417
+ var audio = copyFloat32Audio(audioData)
1418
+ return this._transcribeWorker(
1419
+ 'parakeetTranscribeData',
1420
+ [this._id, audio, normalizeThreadOptions(options)],
1421
+ [audio.buffer],
1422
+ )
1423
+ }
1424
+
1425
+ WorkerParakeetContextInstance.prototype.transcribeFile = function (
1426
+ filePath,
1427
+ options,
1428
+ ) {
1429
+ return this._transcribeWorker(
1430
+ 'parakeetTranscribeFile',
1431
+ [this._id, filePath, normalizeThreadOptions(options)],
1432
+ [],
1433
+ )
1434
+ }
1435
+
1436
+ WorkerParakeetContextInstance.prototype.transcribe =
1437
+ WorkerParakeetContextInstance.prototype.transcribeFile
1438
+
1439
+ WorkerParakeetContextInstance.prototype.release = async function () {
1440
+ if (!this._released) {
1441
+ await this._proxy.request('releaseParakeet', [this._id])
1442
+ this._released = true
1443
+ }
1444
+ }
1445
+
1446
+ async function createParakeetContext(options) {
1447
+ options = options || {}
1448
+ var modelSource = options.filePath || options.modelUrl
1449
+ var runtime = await loadRuntime()
1450
+ if (typeof runtime.__wasm_init_parakeet !== 'function') {
1451
+ throw new Error(
1452
+ 'ParakeetContext is not available in this @fugood/node-whisper-wasm build, please update the package',
1453
+ )
1454
+ }
1455
+ var useGpu = resolveWhisperGpu(runtime, options.useGpu === true)
1456
+
1457
+ var model = await ensureModel(runtime, modelSource, 'parakeet', options)
1458
+ var init
1459
+ try {
1460
+ init = unwrapWasmResult(
1461
+ await runtime.__wasm_init_parakeet(model.virtualPath, useGpu),
1462
+ )
1463
+ } catch (error) {
1464
+ if (!useGpu) {
1465
+ throw error
1466
+ }
1467
+ warnWebGpuCpuFallback(error)
1468
+ useGpu = false
1469
+ init = unwrapWasmResult(
1470
+ await runtime.__wasm_init_parakeet(model.virtualPath, false),
1471
+ )
1472
+ }
1473
+
1474
+ return new ParakeetContextInstance(runtime, init.id, {
1475
+ filePath: modelSource,
1476
+ wasmFilePath: model.virtualPath,
1477
+ useGpu: useGpu,
1478
+ bytes: model.bytes,
1479
+ cacheHit: model.cacheHit,
1480
+ cacheStored: model.cacheStored,
1481
+ cacheName: model.cacheName,
1482
+ cacheKey: model.cacheKey,
1483
+ wasm: true,
1484
+ })
1485
+ }
1486
+
1487
+ function ParakeetContextInstance(runtime, id, meta) {
1488
+ this._runtime = runtime
1489
+ this._id = id
1490
+ this._meta = meta
1491
+ this._released = false
1492
+ }
1493
+
1494
+ ParakeetContextInstance.prototype._assertValid = function () {
1495
+ if (this._released) {
1496
+ throw new Error('Invalid parakeet context')
1497
+ }
1498
+ }
1499
+
1500
+ ParakeetContextInstance.prototype.getModelInfo = function () {
1501
+ return this._meta
1502
+ }
1503
+
1504
+ ParakeetContextInstance.prototype._transcribeFloat32 = function (
1505
+ audio,
1506
+ options,
1507
+ isCancelled,
1508
+ ) {
1509
+ this._assertValid()
1510
+
1511
+ if (isCancelled && isCancelled()) {
1512
+ return Promise.resolve(abortedResult())
1513
+ }
1514
+
1515
+ var runtime = this._runtime
1516
+ var id = this._id
1517
+ return defer(async function () {
1518
+ if (isCancelled && isCancelled()) {
1519
+ return abortedResult()
1520
+ }
1521
+
1522
+ var result = unwrapWasmResult(
1523
+ await runtime.__wasm_transcribe_parakeet(
1524
+ id,
1525
+ audio,
1526
+ normalizeThreadOptions(options, runtime),
1527
+ ),
1528
+ )
1529
+ if (isCancelled && isCancelled()) {
1530
+ result.isAborted = true
1531
+ }
1532
+ return result
1533
+ })
1534
+ }
1535
+
1536
+ ParakeetContextInstance.prototype.transcribeData = function (audioData, options) {
1537
+ var cancelled = false
1538
+ var audio = toFloat32Audio(audioData)
1539
+ return {
1540
+ stop: function () {
1541
+ cancelled = true
1542
+ return Promise.resolve()
1543
+ },
1544
+ promise: this._transcribeFloat32(audio, options, function () {
1545
+ return cancelled
1546
+ }),
1547
+ }
1548
+ }
1549
+
1550
+ ParakeetContextInstance.prototype.transcribeFile = function (filePath, options) {
1551
+ var cancelled = false
1552
+ var self = this
1553
+ return {
1554
+ stop: function () {
1555
+ cancelled = true
1556
+ return Promise.resolve()
1557
+ },
1558
+ promise: (async function () {
1559
+ if (cancelled) {
1560
+ return abortedResult()
1561
+ }
1562
+ var audio = await loadAudioUrl(filePath)
1563
+ return self._transcribeFloat32(audio, options, function () {
1564
+ return cancelled
1565
+ })
1566
+ })(),
1567
+ }
1568
+ }
1569
+
1570
+ ParakeetContextInstance.prototype.transcribe =
1571
+ ParakeetContextInstance.prototype.transcribeFile
1572
+
1573
+ ParakeetContextInstance.prototype.release = function () {
1574
+ if (!this._released) {
1575
+ this._runtime.__wasm_free_parakeet(this._id)
1576
+ this._released = true
1577
+ }
1578
+ return Promise.resolve()
1579
+ }
1580
+
1297
1581
  function WhisperVadContext(options) {
1298
1582
  if (shouldUseWorker(options || {})) {
1299
1583
  return createWorkerWhisperVadContext(options || {})
@@ -1463,16 +1747,25 @@ const createWhisperNodeApi = function (root) {
1463
1747
  return Promise.resolve(new WhisperVadContext(options))
1464
1748
  }
1465
1749
 
1750
+ function initParakeet(options) {
1751
+ return Promise.resolve(new ParakeetContext(options))
1752
+ }
1753
+
1466
1754
  var api = {
1467
1755
  WhisperContext: WhisperContext,
1468
1756
  WhisperVadContext: WhisperVadContext,
1757
+ ParakeetContext: ParakeetContext,
1469
1758
  configureWasm: configureWasm,
1470
1759
  loadWasmModule: loadRuntime,
1471
1760
  loadWhisperModule: loadWhisperModule,
1472
1761
  initWhisper: initWhisper,
1473
1762
  initWhisperVad: initWhisperVad,
1763
+ initParakeet: initParakeet,
1474
1764
  toggleNativeLog: toggleNativeLog,
1475
1765
  addNativeLogListener: addNativeLogListener,
1766
+ isNativeLogEnabled: function () {
1767
+ return logEnabled
1768
+ },
1476
1769
  isWasmThreadsSupported: isWasmThreadsSupported,
1477
1770
  DEFAULT_WASM_MODEL_SIZE_LIMIT_BYTES: 1500 * MIB,
1478
1771
  MAX_WASM_THREADS: MAX_WASM_THREADS,
@@ -1488,6 +1781,7 @@ const api = createWhisperNodeApi(root)
1488
1781
 
1489
1782
  export const WhisperContext = api.WhisperContext
1490
1783
  export const WhisperVadContext = api.WhisperVadContext
1784
+ export const ParakeetContext = api.ParakeetContext
1491
1785
  export const DEFAULT_WASM_MODEL_SIZE_LIMIT_BYTES =
1492
1786
  api.DEFAULT_WASM_MODEL_SIZE_LIMIT_BYTES
1493
1787
  export const MAX_WASM_THREADS = api.MAX_WASM_THREADS
@@ -1496,8 +1790,10 @@ export const loadWasmModule = api.loadWasmModule
1496
1790
  export const loadWhisperModule = api.loadWhisperModule
1497
1791
  export const initWhisper = api.initWhisper
1498
1792
  export const initWhisperVad = api.initWhisperVad
1793
+ export const initParakeet = api.initParakeet
1499
1794
  export const toggleNativeLog = api.toggleNativeLog
1500
1795
  export const addNativeLogListener = api.addNativeLogListener
1796
+ export const isNativeLogEnabled = api.isNativeLogEnabled
1501
1797
  export const isWasmThreadsSupported = api.isWasmThreadsSupported
1502
1798
 
1503
1799
  if (root) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fugood/node-whisper-wasm",
3
- "version": "1.0.21",
3
+ "version": "1.1.0",
4
4
  "description": "Browser WASM module for whisper.node",
5
5
  "type": "module",
6
6
  "main": "index.js",