@fugood/node-whisper-wasm 1.0.22 → 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,
@@ -194,11 +249,13 @@ export declare function isNativeLogEnabled(): boolean
194
249
  declare const _default: {
195
250
  WhisperContext: typeof WhisperContext
196
251
  WhisperVadContext: typeof WhisperVadContext
252
+ ParakeetContext: typeof ParakeetContext
197
253
  configureWasm: typeof configureWasm
198
254
  loadWasmModule: typeof loadWasmModule
199
255
  loadWhisperModule: typeof loadWhisperModule
200
256
  initWhisper: typeof initWhisper
201
257
  initWhisperVad: typeof initWhisperVad
258
+ initParakeet: typeof initParakeet
202
259
  toggleNativeLog: typeof toggleNativeLog
203
260
  addNativeLogListener: typeof addNativeLogListener
204
261
  isNativeLogEnabled: typeof isNativeLogEnabled
package/index.js CHANGED
@@ -246,7 +246,7 @@ const createWhisperNodeApi = function (root) {
246
246
  }
247
247
  }
248
248
  options.printErr = function (text) {
249
- emitLog('ERROR', String(text))
249
+ emitLog('INFO', String(text))
250
250
  if (typeof userPrintErr === 'function') {
251
251
  userPrintErr(text)
252
252
  }
@@ -1055,9 +1055,18 @@ const createWhisperNodeApi = function (root) {
1055
1055
  return decodeAudioBuffer(buffer)
1056
1056
  }
1057
1057
 
1058
- 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) {
1059
1068
  if (!useGpu) {
1060
- return
1069
+ return false
1061
1070
  }
1062
1071
 
1063
1072
  if (
@@ -1065,14 +1074,22 @@ const createWhisperNodeApi = function (root) {
1065
1074
  typeof runtime.__wasm_webgpu_enabled !== 'function' ||
1066
1075
  !runtime.__wasm_webgpu_enabled()
1067
1076
  ) {
1068
- throw new Error(
1069
- '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
+ ),
1070
1081
  )
1082
+ return false
1071
1083
  }
1072
1084
 
1073
1085
  if (!root.navigator || !root.navigator.gpu) {
1074
- 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
1075
1090
  }
1091
+
1092
+ return true
1076
1093
  }
1077
1094
 
1078
1095
  function WhisperContext(options) {
@@ -1188,24 +1205,36 @@ const createWhisperNodeApi = function (root) {
1188
1205
  options = options || {}
1189
1206
  var modelSource = options.filePath || options.modelUrl
1190
1207
  var runtime = await loadRuntime()
1191
- var useGpu = options.useGpu === true
1192
-
1193
- validateWebGpu(runtime, useGpu)
1208
+ var useGpu = resolveWhisperGpu(runtime, options.useGpu === true)
1194
1209
 
1195
1210
  var model = await ensureModel(runtime, modelSource, 'whisper', options)
1196
- var init = unwrapWasmResult(
1197
- await runtime.__wasm_init_whisper(
1198
- model.virtualPath,
1199
- useGpu,
1200
- options.useFlashAttn === true,
1201
- ),
1202
- )
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
+ }
1203
1232
 
1204
1233
  return new WhisperContextInstance(runtime, init.id, {
1205
1234
  filePath: modelSource,
1206
1235
  wasmFilePath: model.virtualPath,
1207
1236
  useGpu: useGpu,
1208
- useFlashAttn: options.useFlashAttn === true,
1237
+ useFlashAttn: useFlashAttn,
1209
1238
  bytes: model.bytes,
1210
1239
  cacheHit: model.cacheHit,
1211
1240
  cacheStored: model.cacheStored,
@@ -1311,6 +1340,244 @@ const createWhisperNodeApi = function (root) {
1311
1340
  return Promise.resolve()
1312
1341
  }
1313
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
+
1314
1581
  function WhisperVadContext(options) {
1315
1582
  if (shouldUseWorker(options || {})) {
1316
1583
  return createWorkerWhisperVadContext(options || {})
@@ -1480,14 +1747,20 @@ const createWhisperNodeApi = function (root) {
1480
1747
  return Promise.resolve(new WhisperVadContext(options))
1481
1748
  }
1482
1749
 
1750
+ function initParakeet(options) {
1751
+ return Promise.resolve(new ParakeetContext(options))
1752
+ }
1753
+
1483
1754
  var api = {
1484
1755
  WhisperContext: WhisperContext,
1485
1756
  WhisperVadContext: WhisperVadContext,
1757
+ ParakeetContext: ParakeetContext,
1486
1758
  configureWasm: configureWasm,
1487
1759
  loadWasmModule: loadRuntime,
1488
1760
  loadWhisperModule: loadWhisperModule,
1489
1761
  initWhisper: initWhisper,
1490
1762
  initWhisperVad: initWhisperVad,
1763
+ initParakeet: initParakeet,
1491
1764
  toggleNativeLog: toggleNativeLog,
1492
1765
  addNativeLogListener: addNativeLogListener,
1493
1766
  isNativeLogEnabled: function () {
@@ -1508,6 +1781,7 @@ const api = createWhisperNodeApi(root)
1508
1781
 
1509
1782
  export const WhisperContext = api.WhisperContext
1510
1783
  export const WhisperVadContext = api.WhisperVadContext
1784
+ export const ParakeetContext = api.ParakeetContext
1511
1785
  export const DEFAULT_WASM_MODEL_SIZE_LIMIT_BYTES =
1512
1786
  api.DEFAULT_WASM_MODEL_SIZE_LIMIT_BYTES
1513
1787
  export const MAX_WASM_THREADS = api.MAX_WASM_THREADS
@@ -1516,6 +1790,7 @@ export const loadWasmModule = api.loadWasmModule
1516
1790
  export const loadWhisperModule = api.loadWhisperModule
1517
1791
  export const initWhisper = api.initWhisper
1518
1792
  export const initWhisperVad = api.initWhisperVad
1793
+ export const initParakeet = api.initParakeet
1519
1794
  export const toggleNativeLog = api.toggleNativeLog
1520
1795
  export const addNativeLogListener = api.addNativeLogListener
1521
1796
  export const isNativeLogEnabled = api.isNativeLogEnabled
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fugood/node-whisper-wasm",
3
- "version": "1.0.22",
3
+ "version": "1.1.0",
4
4
  "description": "Browser WASM module for whisper.node",
5
5
  "type": "module",
6
6
  "main": "index.js",