whispermix 1.4.12 → 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 +26 -3
- package/SKILL.md +88 -0
- package/demo/parakeet.js +9 -0
- package/index.js +178 -5
- package/package.json +61 -56
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
|
|
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
|
|
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.
|
package/demo/parakeet.js
ADDED
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
|
-
|
|
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 =
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
+
}
|