whispermix 1.4.14 → 1.5.2

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
@@ -1,6 +1,6 @@
1
1
  # 🎙️ WhisperMix
2
2
 
3
- WhisperMix is a flexible module that provides an interface for transcribing audio using OpenAI's Whisper model, Groq's Whisper Large v3 model, or local Whisper models.
3
+ WhisperMix is a flexible module that provides an interface for transcribing audio using OpenAI's Whisper model, Groq's Whisper Large v3 model, local Whisper models, or local Parakeet TDT v3 models.
4
4
 
5
5
  ## 📦 Installation
6
6
 
@@ -36,6 +36,12 @@ const whisperGroq = new WhisperMix({ model: 'groq/whisper-large-v3' }); // For G
36
36
  const whisperLocal = new WhisperMix({ model: 'xenova/whisper-large-v3' }); // For local Whisper (large)
37
37
  // or
38
38
  const whisperLocalBase = new WhisperMix({ model: 'xenova/whisper-base' }); // For local Whisper (base)
39
+ // or
40
+ const whisperParakeet = new WhisperMix({ model: 'istupakov/parakeet-tdt-0.6b-v3' }); // For local Parakeet v3 (int8)
41
+ // or
42
+ const whisperParakeetInt4 = new WhisperMix({ model: 'efederici/parakeet-tdt-0.6b-v3-int4' }); // For local Parakeet v3 (int4 encoder)
43
+ // or
44
+ const whisperParakeetAlt = new WhisperMix({ model: 'nasedkinpv/parakeet-tdt-0.6b-v3-int8' }); // For local Parakeet v3 (alt int8 repo)
39
45
  ```
40
46
 
41
47
  ### 📄 Transcribing from a File
@@ -67,7 +73,24 @@ whisperGroq.fromStream(audioStream)
67
73
  .catch(error => console.error(error));
68
74
  ```
69
75
 
70
- **Note:** Stream transcription is only available for API-based models (OpenAI and Groq). Local Whisper models require file input.
76
+ **Note:** Stream transcription is only available for API-based models (OpenAI and Groq). Local models (Whisper and Parakeet) require file input.
77
+
78
+ ### 🐦 Parakeet local models
79
+
80
+ Parakeet TDT v3 local models are downloaded once and cached in:
81
+
82
+ `~/.cache/whispermix/parakeet/<modelKey>/`
83
+
84
+ Available local Parakeet model keys:
85
+
86
+ - `istupakov/parakeet-tdt-0.6b-v3` (about 670 MB, int8)
87
+ - `efederici/parakeet-tdt-0.6b-v3-int4` (about 410 MB, int4/int8 hybrid)
88
+ - `nasedkinpv/parakeet-tdt-0.6b-v3-int8` (about 890 MB, int8)
89
+
90
+ Notes:
91
+
92
+ - Parakeet TDT v3 is multilingual (25 European languages).
93
+ - `language` is ignored for Parakeet local models.
71
94
 
72
95
  ### ⏱️ Long Audio Processing
73
96
 
@@ -108,7 +131,7 @@ You can adjust these settings based on your specific rate limiting needs. Note t
108
131
 
109
132
  Creates a new WhisperMix instance.
110
133
 
111
- - `options.model`: The model to use for transcription. Can be `'openai/whisper-1'` (OpenAI), `'groq/whisper-large-v3'` (Groq), `'xenova/whisper-large-v3'` or `'xenova/whisper-base'` (local).
134
+ - `options.model`: The model to use for transcription. Can be `'openai/whisper-1'` (OpenAI), `'groq/whisper-large-v3'` (Groq), `'xenova/whisper-large-v3'` or `'xenova/whisper-base'` (local Whisper), `'istupakov/parakeet-tdt-0.6b-v3'`, `'efederici/parakeet-tdt-0.6b-v3-int4'`, or `'nasedkinpv/parakeet-tdt-0.6b-v3-int8'` (local Parakeet).
112
135
  - `options.bottleneck`: (Optional) Configuration for Bottleneck rate limiting (API models only).
113
136
  - `options.chunkSize`: (Optional) The size in seconds of the chunks to split the audio into. Default is 890 seconds.
114
137
  - `options.language`: (Optional) Language for local Whisper model. Defaults to 'auto' for automatic detection.
package/SKILL.md ADDED
@@ -0,0 +1,88 @@
1
+ ---
2
+ name: whispermix
3
+ description: Transcribe audio to text using WhisperMix, a Node.js wrapper around OpenAI Whisper, Groq Whisper Large v3, local Whisper (xenova) and local Parakeet TDT v3 models. Use when the user asks to "transcribe audio", "speech to text", "convert audio/voice to text", mentions an audio file (.mp3, .wav, .m4a, .ogg, .flac, .webm) to turn into text, or names any of these models/providers: Whisper, OpenAI Whisper, Groq Whisper, Whisper Large v3, Parakeet, NVIDIA Parakeet, xenova/whisper, onnx-asr. Also use when the user wants to pick between a cloud API and a local on-device transcription model in a Node.js project.
4
+ ---
5
+
6
+ # WhisperMix
7
+
8
+ Single Node.js entry point for audio transcription. Picks one of four backends behind the same API: OpenAI Whisper, Groq Whisper Large v3, local Whisper (xenova), or local Parakeet TDT v3. Handles long files by chunking and rate-limits API calls automatically.
9
+
10
+ Do NOT use for TTS, live microphone streaming, or translation. WhisperMix is one-way: a complete audio file/stream → text.
11
+
12
+ ## Model selection (decide first)
13
+
14
+ | Need | Pick |
15
+ |---|---|
16
+ | Offline, lowest latency | `istupakov/parakeet-tdt-0.6b-v3` |
17
+ | Offline, smallest footprint (~410 MB) | `efederici/parakeet-tdt-0.6b-v3-int4` |
18
+ | Highest accuracy, cloud | `groq/whisper-large-v3` (fast) or `openai/whisper-1` |
19
+ | Offline + per-language control | `xenova/whisper-large-v3` or `xenova/whisper-base` |
20
+ | Node stream input (not a file) | API only: `openai/whisper-1` or `groq/whisper-large-v3` |
21
+
22
+ Constraints to surface before coding:
23
+ - Local models (`xenova/*`, `*/parakeet-tdt-0.6b-v3*`) accept **files only**, not streams.
24
+ - `language` option applies only to local Whisper. Parakeet is multilingual and ignores it.
25
+ - API models need `OPENAI_API_KEY` or `GROQ_API_KEY` in the environment.
26
+ - First Parakeet run downloads weights to `~/.cache/whispermix/parakeet/<modelKey>/` — warn the user.
27
+ - Requires `ffmpeg` on `PATH` for long-audio chunking (>15 min split automatically).
28
+
29
+ ## API
30
+
31
+ ```bash
32
+ npm install whispermix
33
+ ```
34
+
35
+ ESM only. If the consumer is CommonJS, use `const WhisperMix = (await import('whispermix')).default;`.
36
+
37
+ ```javascript
38
+ import WhisperMix from 'whispermix';
39
+
40
+ const w = new WhisperMix({ model: '<modelKey>' });
41
+ const text = await w.fromFile('path/to/audio.mp3');
42
+ // API models only:
43
+ const text2 = await w.fromStream(fs.createReadStream('path/to/audio.mp3'));
44
+ ```
45
+
46
+ Constructor options:
47
+ - `model` (required) — see selection table.
48
+ - `language` — local Whisper only, e.g. `'spanish'`. Default `'auto'`.
49
+ - `chunkSize` — seconds per chunk for long audio. Default `890` (~14m50s).
50
+ - `bottleneck` — Bottleneck config for API models. Defaults: `minTime: 3000`, `maxConcurrent: 1`, `reservoir: 18`, `reservoirRefreshAmount: 18`, `reservoirRefreshInterval: 60000`.
51
+ - `showProgress` — boolean, prints chunk/decoding progress.
52
+
53
+ ## Examples
54
+
55
+ Cheapest local:
56
+
57
+ ```javascript
58
+ import WhisperMix from 'whispermix';
59
+ const w = new WhisperMix({ model: 'efederici/parakeet-tdt-0.6b-v3-int4', showProgress: true });
60
+ console.log(await w.fromFile('meeting.wav'));
61
+ ```
62
+
63
+ Groq with custom rate limit:
64
+
65
+ ```javascript
66
+ import WhisperMix from 'whispermix';
67
+ const w = new WhisperMix({
68
+ model: 'groq/whisper-large-v3',
69
+ bottleneck: { minTime: 4000, maxConcurrent: 1 },
70
+ });
71
+ console.log(await w.fromFile('podcast.mp3'));
72
+ ```
73
+
74
+ Local Whisper, fixed language:
75
+
76
+ ```javascript
77
+ import WhisperMix from 'whispermix';
78
+ const w = new WhisperMix({ model: 'xenova/whisper-large-v3', language: 'spanish' });
79
+ console.log(await w.fromFile('entrevista.m4a'));
80
+ ```
81
+
82
+ ## Troubleshooting
83
+
84
+ - **`OPENAI_API_KEY`/`GROQ_API_KEY is not set`** — export the key, or switch to a local model.
85
+ - **`fromStream` not supported** — local models are file-only. Use `fromFile`, or switch to an API model.
86
+ - **`Cannot find ffmpeg`** — install it (`brew install ffmpeg` / `apt install ffmpeg`).
87
+ - **First Parakeet call hangs** — weights downloading; enable `showProgress: true`.
88
+ - **`ERR_REQUIRE_ESM`** — WhisperMix is ESM-only; use dynamic `import()` from CommonJS.
@@ -0,0 +1,9 @@
1
+ import WhisperMix from '../index.js';
2
+
3
+ const whisperParakeet = new WhisperMix({
4
+ model: 'efederici/parakeet-tdt-0.6b-v3-int4',
5
+ showProgress: true,
6
+ });
7
+
8
+ const result = await whisperParakeet.fromFile('conversation.wav');
9
+ console.log(result);
package/index.js CHANGED
@@ -4,11 +4,52 @@ import path from 'path';
4
4
  import ffmpeg from 'fluent-ffmpeg';
5
5
  import { getAudioDurationInSeconds } from 'get-audio-duration';
6
6
  import os from 'os'; // For temporary directory
7
+ import { Readable } from 'stream';
8
+ import { pipeline as streamPipeline } from 'stream/promises';
7
9
 
8
10
  // Static imports for local dependencies
9
- import { pipeline, env } from '@huggingface/transformers';
11
+ import { pipeline as hfPipeline, env } from '@huggingface/transformers';
10
12
  import audioDecode from 'audio-decode';
11
13
 
14
+ const PARAKEET_LAYOUTS = {
15
+ 'istupakov/parakeet-tdt-0.6b-v3': {
16
+ cacheKey: 'istupakov-parakeet-tdt-0.6b-v3',
17
+ repo: 'istupakov/parakeet-tdt-0.6b-v3-onnx',
18
+ files: [
19
+ { src: 'config.json', dst: 'config.json' },
20
+ { src: 'nemo128.onnx', dst: 'nemo128.onnx' },
21
+ { src: 'encoder-model.int8.onnx', dst: 'encoder-model.int8.onnx' },
22
+ { src: 'decoder_joint-model.int8.onnx', dst: 'decoder_joint-model.int8.onnx' },
23
+ { src: 'vocab.txt', dst: 'vocab.txt' },
24
+ ],
25
+ },
26
+ 'efederici/parakeet-tdt-0.6b-v3-int4': {
27
+ cacheKey: 'efederici-parakeet-tdt-0.6b-v3-int4',
28
+ repo: 'efederici/parakeet-tdt-0.6b-v3-onnx-int4',
29
+ files: [
30
+ { src: 'config.json', dst: 'config.json' },
31
+ { src: 'nemo128.onnx', dst: 'nemo128.onnx' },
32
+ { src: 'encoder-model.int4.onnx', dst: 'encoder-model.int8.onnx' },
33
+ { src: 'decoder_joint-model.int8.onnx', dst: 'decoder_joint-model.int8.onnx' },
34
+ { src: 'vocab.txt', dst: 'vocab.txt' },
35
+ ],
36
+ },
37
+ 'nasedkinpv/parakeet-tdt-0.6b-v3-int8': {
38
+ cacheKey: 'nasedkinpv-parakeet-tdt-0.6b-v3-int8',
39
+ repo: 'nasedkinpv/parakeet-tdt-0.6b-v3-onnx-int8',
40
+ files: [
41
+ { src: 'encoder-int8.onnx', dst: 'encoder-model.int8.onnx' },
42
+ { src: 'encoder-int8.onnx.data', dst: 'encoder-model.int8.onnx.data' },
43
+ { src: 'decoder_joint-int8.onnx', dst: 'decoder_joint-model.int8.onnx' },
44
+ { src: 'vocab.txt', dst: 'vocab.txt' },
45
+ { srcRepo: 'istupakov/parakeet-tdt-0.6b-v3-onnx', src: 'nemo128.onnx', dst: 'nemo128.onnx' },
46
+ ],
47
+ synthConfig: {
48
+ model_type: 'nemo-conformer-tdt',
49
+ },
50
+ },
51
+ };
52
+
12
53
  class WhisperMix {
13
54
  constructor(setup = {}) {
14
55
  this.model = 'openai/whisper-1';
@@ -41,7 +82,26 @@ class WhisperMix {
41
82
  local: true,
42
83
  modelName: 'Xenova/whisper-base',
43
84
  dtype: 'q8',
44
- },
85
+ backend: 'transformers',
86
+ },
87
+ 'istupakov/parakeet-tdt-0.6b-v3': {
88
+ local: true,
89
+ modelName: 'Parakeet-TDT-0.6B-v3',
90
+ backend: 'onnx-asr-web',
91
+ layout: PARAKEET_LAYOUTS['istupakov/parakeet-tdt-0.6b-v3'],
92
+ },
93
+ 'efederici/parakeet-tdt-0.6b-v3-int4': {
94
+ local: true,
95
+ modelName: 'Parakeet-TDT-0.6B-v3-int4',
96
+ backend: 'onnx-asr-web',
97
+ layout: PARAKEET_LAYOUTS['efederici/parakeet-tdt-0.6b-v3-int4'],
98
+ },
99
+ 'nasedkinpv/parakeet-tdt-0.6b-v3-int8': {
100
+ local: true,
101
+ modelName: 'Parakeet-TDT-0.6B-v3-int8',
102
+ backend: 'onnx-asr-web',
103
+ layout: PARAKEET_LAYOUTS['nasedkinpv/parakeet-tdt-0.6b-v3-int8'],
104
+ },
45
105
  };
46
106
 
47
107
  Object.assign(this, setup);
@@ -56,7 +116,11 @@ class WhisperMix {
56
116
  this.modelName = this.config.modelName;
57
117
  this.dtype = this.dtype || this.config.dtype;
58
118
  this.showProgress = this.showProgress || false;
119
+ this.localBackend = this.config.backend || 'transformers';
120
+ this.layout = this.config.layout;
59
121
  this.transcriber = null;
122
+ this._onnxAsrNodeModule = null;
123
+ this._warnedParakeetLanguage = false;
60
124
 
61
125
  this.limiter = new Bottleneck(this.bottleneck);
62
126
  }
@@ -212,6 +276,19 @@ class WhisperMix {
212
276
  // Reuse a single pipeline instance so repeated calls don't re-download/re-initialize.
213
277
  const transcriber = await this._getLocalTranscriber();
214
278
 
279
+ if (this.localBackend === 'onnx-asr-web') {
280
+ if (this.language !== undefined && this.showProgress && !this._warnedParakeetLanguage) {
281
+ console.log('[WhisperMix] "language" option is ignored for Parakeet local models.');
282
+ this._warnedParakeetLanguage = true;
283
+ }
284
+ const result = await transcriber.transcribeSamples(audioData, 16000);
285
+ const text = result?.text || result?.utterance_text;
286
+ if (typeof text !== 'string') {
287
+ throw new Error('Parakeet transcription returned no text output.');
288
+ }
289
+ return text.trim();
290
+ }
291
+
215
292
  // Pass the processed audio data
216
293
  const transcriberOptions = {
217
294
  task: 'transcribe',
@@ -220,15 +297,32 @@ class WhisperMix {
220
297
  transcriberOptions.language = this.language;
221
298
  }
222
299
  const result = await transcriber(audioData, transcriberOptions);
223
-
224
300
  return result.text.trim();
225
301
  } catch (error) {
226
- throw new Error(`Local transcription failed: ${error.message}. If this happened after an interrupted download, remove the model cache at ${env.cacheDir}${this.modelName}/ and try again.`);
302
+ const cacheHint = this.localBackend === 'onnx-asr-web'
303
+ ? this._getParakeetCacheDir(this.layout)
304
+ : `${env.cacheDir}${this.modelName}/`;
305
+ throw new Error(`Local transcription failed: ${error.message}. If this happened after an interrupted download, remove the model cache at ${cacheHint} and try again.`);
227
306
  }
228
307
  }
229
308
 
230
309
  async _getLocalTranscriber() {
231
310
  if (!this.transcriber) {
311
+ if (this.localBackend === 'onnx-asr-web') {
312
+ if (!this.layout) {
313
+ throw new Error(`Missing Parakeet layout for model: ${this.model}`);
314
+ }
315
+ const modelDir = await this._ensureParakeetAssets(this.layout);
316
+ const { loadLocalModel } = await this._getOnnxAsrNodeModule();
317
+ this.transcriber = await loadLocalModel(modelDir, {
318
+ quantization: 'int8',
319
+ sessionOptions: {
320
+ executionProviders: ['wasm'],
321
+ },
322
+ });
323
+ return this.transcriber;
324
+ }
325
+
232
326
  const options = {};
233
327
  if (this.dtype) {
234
328
  options.dtype = this.dtype;
@@ -244,11 +338,90 @@ class WhisperMix {
244
338
  };
245
339
  }
246
340
 
247
- this.transcriber = pipeline('automatic-speech-recognition', this.modelName, options);
341
+ this.transcriber = hfPipeline('automatic-speech-recognition', this.modelName, options);
248
342
  }
249
343
 
250
344
  return this.transcriber;
251
345
  }
346
+
347
+ async _getOnnxAsrNodeModule() {
348
+ if (!this._onnxAsrNodeModule) {
349
+ this._onnxAsrNodeModule = await import('onnx-asr-web/node');
350
+ }
351
+ return this._onnxAsrNodeModule;
352
+ }
353
+
354
+ _getParakeetCacheDir(layout) {
355
+ if (this.cacheDir) {
356
+ return path.resolve(this.cacheDir);
357
+ }
358
+ if (!layout?.cacheKey) {
359
+ throw new Error(`Missing cache key for Parakeet layout: ${this.model}`);
360
+ }
361
+ return path.join(os.homedir(), '.cache', 'whispermix', 'parakeet', layout.cacheKey);
362
+ }
363
+
364
+ async _ensureParakeetAssets(layout) {
365
+ const cacheDir = this._getParakeetCacheDir(layout);
366
+ await fs.promises.mkdir(cacheDir, { recursive: true });
367
+
368
+ for (const fileDef of layout.files) {
369
+ const destination = path.join(cacheDir, fileDef.dst);
370
+ const sourceRepo = fileDef.srcRepo || layout.repo;
371
+
372
+ if (await this._hasFileWithContent(destination)) {
373
+ continue;
374
+ }
375
+
376
+ const sourceUrl = `https://huggingface.co/${sourceRepo}/resolve/main/${fileDef.src}`;
377
+ if (this.showProgress) {
378
+ console.log(`[WhisperMix] Downloading ${fileDef.src} from ${sourceRepo}`);
379
+ }
380
+ await this._downloadToFile(sourceUrl, destination);
381
+ }
382
+
383
+ if (layout.synthConfig) {
384
+ const configPath = path.join(cacheDir, 'config.json');
385
+ if (!(await this._hasFileWithContent(configPath))) {
386
+ await fs.promises.writeFile(configPath, JSON.stringify(layout.synthConfig, null, 2));
387
+ }
388
+ }
389
+
390
+ return cacheDir;
391
+ }
392
+
393
+ async _hasFileWithContent(filePath) {
394
+ try {
395
+ const stats = await fs.promises.stat(filePath);
396
+ return stats.isFile() && stats.size > 0;
397
+ } catch {
398
+ return false;
399
+ }
400
+ }
401
+
402
+ async _downloadToFile(url, filePath) {
403
+ const response = await fetch(url);
404
+ if (!response.ok) {
405
+ throw new Error(`Download failed (${response.status} ${response.statusText}) for ${url}`);
406
+ }
407
+ if (!response.body) {
408
+ throw new Error(`Empty response body while downloading ${url}`);
409
+ }
410
+
411
+ await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
412
+ const tempPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
413
+
414
+ try {
415
+ await streamPipeline(
416
+ Readable.fromWeb(response.body),
417
+ fs.createWriteStream(tempPath),
418
+ );
419
+ await fs.promises.rename(tempPath, filePath);
420
+ } catch (error) {
421
+ await fs.promises.rm(tempPath, { force: true });
422
+ throw error;
423
+ }
424
+ }
252
425
  }
253
426
 
254
427
  // Support both CommonJS and ES modules
package/package.json CHANGED
@@ -1,57 +1,62 @@
1
1
  {
2
- "name": "whispermix",
3
- "description": "🎙️ WhisperMix is a versatile module for transcribing audio using OpenAI’s Whisper or Groq’s Whisper v3 model.",
4
- "version": "1.4.14",
5
- "type": "module",
6
- "keywords": [
7
- "whisper",
8
- "openai",
9
- "groq",
10
- "transcription",
11
- "speech-to-text",
12
- "audio",
13
- "voice",
14
- "ai",
15
- "machine-learning",
16
- "nlp",
17
- "natural-language-processing",
18
- "audio-processing",
19
- "voice-recognition",
20
- "speech-recognition",
21
- "api-wrapper",
22
- "whisper-large-v3",
23
- "whisper-1",
24
- "streaming",
25
- "file-processing",
26
- "multilingual",
27
- "bottleneck",
28
- "chunk",
29
- "clasen",
30
- "local",
31
- "xenova"
32
- ],
33
- "repository": {
34
- "type": "git",
35
- "url": "git+https://github.com/clasen/WhisperMix.git"
36
- },
37
- "main": "index.js",
38
- "author": "Martin Clasen",
39
- "license": "MIT",
40
- "bugs": {
41
- "url": "https://github.com/clasen/WhisperMix/issues"
42
- },
43
- "dependencies": {
44
- "@huggingface/transformers": "^4.2.0",
45
- "audio-decode": "^2.2.3",
46
- "bottleneck": "^2.19.5",
47
- "fluent-ffmpeg": "^2.1.3",
48
- "get-audio-duration": "^4.0.1"
49
- },
50
- "devDependencies": {
51
- "node-addon-api": "^8.7.0",
52
- "node-gyp": "^12.3.0"
53
- },
54
- "scripts": {
55
- "test": "echo \"Error: no test specified\" && exit 1"
56
- }
57
- }
2
+ "name": "whispermix",
3
+ "description": "🎙️ WhisperMix is a versatile module for transcribing audio using OpenAI’s Whisper or Groq’s Whisper v3 model.",
4
+ "version": "1.5.2",
5
+ "type": "module",
6
+ "keywords": [
7
+ "whisper",
8
+ "openai",
9
+ "groq",
10
+ "transcription",
11
+ "speech-to-text",
12
+ "audio",
13
+ "voice",
14
+ "ai",
15
+ "machine-learning",
16
+ "nlp",
17
+ "natural-language-processing",
18
+ "audio-processing",
19
+ "voice-recognition",
20
+ "speech-recognition",
21
+ "api-wrapper",
22
+ "whisper-large-v3",
23
+ "whisper-1",
24
+ "streaming",
25
+ "file-processing",
26
+ "multilingual",
27
+ "bottleneck",
28
+ "chunk",
29
+ "clasen",
30
+ "local",
31
+ "xenova",
32
+ "parakeet",
33
+ "nvidia",
34
+ "onnx"
35
+ ],
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/clasen/WhisperMix.git"
39
+ },
40
+ "main": "index.js",
41
+ "scripts": {
42
+ "test": "echo \"Error: no test specified\" && exit 1"
43
+ },
44
+ "author": "Martin Clasen",
45
+ "license": "MIT",
46
+ "bugs": {
47
+ "url": "https://github.com/clasen/WhisperMix/issues"
48
+ },
49
+ "dependencies": {
50
+ "@huggingface/transformers": "^4.2.0",
51
+ "audio-decode": "^2.2.3",
52
+ "bottleneck": "^2.19.5",
53
+ "fluent-ffmpeg": "^2.1.3",
54
+ "get-audio-duration": "^4.0.1",
55
+ "onnx-asr-web": "^0.1.3"
56
+ },
57
+ "devDependencies": {
58
+ "node-addon-api": "^8.7.0",
59
+ "node-gyp": "^12.3.0"
60
+ },
61
+ "packageManager": "pnpm@11.1.2"
62
+ }