whispermix 1.3.6 β 1.3.8
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 +27 -16
- package/default.env +2 -0
- package/demo/groq.js +12 -7
- package/demo/local.js +8 -0
- package/demo/package.json +1 -0
- package/index.js +106 -19
- package/package.json +9 -4
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
|
|
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.
|
|
4
4
|
|
|
5
5
|
## π¦ Installation
|
|
6
6
|
|
|
@@ -10,17 +10,18 @@ npm install whispermix
|
|
|
10
10
|
|
|
11
11
|
## βοΈ Configuration
|
|
12
12
|
|
|
13
|
-
Before using WhisperMix, you need to set up your environment variables:
|
|
13
|
+
Before using WhisperMix with API-based models, you need to set up your environment variables:
|
|
14
14
|
|
|
15
15
|
- For OpenAI's Whisper: Set `OPENAI_API_KEY` in your environment or `.env` file.
|
|
16
16
|
- For Groq's Whisper Large v3: Set `GROQ_API_KEY` in your environment or `.env` file.
|
|
17
|
+
- For local Whisper: No API key required.
|
|
17
18
|
|
|
18
19
|
## π Usage
|
|
19
20
|
|
|
20
21
|
First, import the WhisperMix class:
|
|
21
22
|
|
|
22
23
|
```javascript
|
|
23
|
-
|
|
24
|
+
import WhisperMix from 'whispermix';
|
|
24
25
|
```
|
|
25
26
|
|
|
26
27
|
### π§ Initializing WhisperMix
|
|
@@ -28,9 +29,11 @@ const WhisperMix = require('whispermix');
|
|
|
28
29
|
You can initialize WhisperMix with a specific model:
|
|
29
30
|
|
|
30
31
|
```javascript
|
|
31
|
-
const whisper = new WhisperMix({ model: '
|
|
32
|
+
const whisper = new WhisperMix({ model: 'openai' }); // For OpenAI's Whisper
|
|
32
33
|
// or
|
|
33
|
-
const whisperGroq = new WhisperMix({ model: '
|
|
34
|
+
const whisperGroq = new WhisperMix({ model: 'groq/large-v3' }); // For Groq's Whisper Large v3
|
|
35
|
+
// or
|
|
36
|
+
const whisperLocal = new WhisperMix({ model: 'xenova/large-v3' }); // For local Whisper
|
|
34
37
|
```
|
|
35
38
|
|
|
36
39
|
### π Transcribing from a File
|
|
@@ -40,12 +43,21 @@ const filePath = 'path/to/your/audio/file.mp3';
|
|
|
40
43
|
whisperGroq.fromFile(filePath)
|
|
41
44
|
.then(transcription => console.log(transcription))
|
|
42
45
|
.catch(error => console.error(error));
|
|
46
|
+
|
|
47
|
+
// For local Whisper with language specification
|
|
48
|
+
const whisperLocal = new WhisperMix({
|
|
49
|
+
model: 'xenova/large-v3',
|
|
50
|
+
language: 'spanish' // Optional
|
|
51
|
+
});
|
|
52
|
+
whisperLocal.fromFile(filePath)
|
|
53
|
+
.then(transcription => console.log(transcription))
|
|
54
|
+
.catch(error => console.error(error));
|
|
43
55
|
```
|
|
44
56
|
|
|
45
57
|
### π Transcribing from a Stream
|
|
46
58
|
|
|
47
59
|
```javascript
|
|
48
|
-
|
|
60
|
+
import fs from 'fs';
|
|
49
61
|
const audioStream = fs.createReadStream('path/to/your/audio/file.mp3');
|
|
50
62
|
|
|
51
63
|
whisperGroq.fromStream(audioStream)
|
|
@@ -53,15 +65,17 @@ whisperGroq.fromStream(audioStream)
|
|
|
53
65
|
.catch(error => console.error(error));
|
|
54
66
|
```
|
|
55
67
|
|
|
68
|
+
**Note:** Stream transcription is only available for API-based models (OpenAI and Groq). Local Whisper models require file input.
|
|
69
|
+
|
|
56
70
|
### β±οΈ Long Audio Processing
|
|
57
71
|
|
|
58
|
-
WhisperMix automatically handles long audio files by splitting them into smaller segments if they exceed 15 minutes in duration. This process is transparent to the user:
|
|
72
|
+
WhisperMix automatically handles long audio files by splitting them into smaller segments if they exceed 15 minutes in duration. This process is transparent to the user and works with both API-based and local models:
|
|
59
73
|
|
|
60
74
|
The segmented transcriptions are automatically merged into a single result, ensuring a smooth experience when working with content of any length.
|
|
61
75
|
|
|
62
|
-
### π¦
|
|
76
|
+
### π¦ Rate Limiting Configuration
|
|
63
77
|
|
|
64
|
-
WhisperMix uses Bottleneck for rate limiting. You can configure the Bottleneck settings when initializing WhisperMix:
|
|
78
|
+
WhisperMix uses Bottleneck for rate limiting API-based models. You can configure the Bottleneck settings when initializing WhisperMix:
|
|
65
79
|
|
|
66
80
|
```javascript
|
|
67
81
|
const whisper = new WhisperMix({
|
|
@@ -84,7 +98,7 @@ The default Bottleneck configuration is:
|
|
|
84
98
|
- `reservoirRefreshAmount`: 18 (number of requests added back to the reservoir)
|
|
85
99
|
- `reservoirRefreshInterval`: 60000 ms (time interval for refreshing the reservoir)
|
|
86
100
|
|
|
87
|
-
You can adjust these settings based on your specific rate limiting needs.
|
|
101
|
+
You can adjust these settings based on your specific rate limiting needs. Note that rate limiting is not applied to local Whisper models.
|
|
88
102
|
|
|
89
103
|
## π API
|
|
90
104
|
|
|
@@ -92,9 +106,10 @@ You can adjust these settings based on your specific rate limiting needs.
|
|
|
92
106
|
|
|
93
107
|
Creates a new WhisperMix instance.
|
|
94
108
|
|
|
95
|
-
- `options.model`: The model to use for transcription. Can be '
|
|
96
|
-
- `options.bottleneck`: (Optional) Configuration for Bottleneck rate limiting.
|
|
109
|
+
- `options.model`: The model to use for transcription. Can be 'openai' (OpenAI), 'groq/large-v3' (Groq), or 'xenova/large-v3' (local).
|
|
110
|
+
- `options.bottleneck`: (Optional) Configuration for Bottleneck rate limiting (API models only).
|
|
97
111
|
- `options.chunkSize`: (Optional) The size in seconds of the chunks to split the audio into. Default is 890 seconds.
|
|
112
|
+
- `options.language`: (Optional) Language for local Whisper model. Defaults to 'auto' for automatic detection.
|
|
98
113
|
### `whisper.fromFile(filePath)`
|
|
99
114
|
|
|
100
115
|
Transcribes audio from a file.
|
|
@@ -111,10 +126,6 @@ Transcribes audio from a stream.
|
|
|
111
126
|
|
|
112
127
|
Returns a Promise that resolves with the transcription text.
|
|
113
128
|
|
|
114
|
-
## β οΈ Error Handling
|
|
115
|
-
|
|
116
|
-
WhisperMix throws errors for API request failures. Always wrap your calls in try-catch blocks or use `.catch()` with promises to handle potential errors.
|
|
117
|
-
|
|
118
129
|
## π License
|
|
119
130
|
|
|
120
131
|
The MIT License (MIT)
|
package/default.env
ADDED
package/demo/groq.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
import WhisperMix from '../index.js';
|
|
3
3
|
|
|
4
4
|
const transcribe = new WhisperMix({ model: 'whisper-large-v3' });
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
async function main() {
|
|
7
7
|
for (let i = 0; i < 10; i++) {
|
|
8
|
-
|
|
9
|
-
.
|
|
10
|
-
.
|
|
8
|
+
try {
|
|
9
|
+
const result = await transcribe.fromFile('./example.mp3');
|
|
10
|
+
console.log(`${i}/10`, result);
|
|
11
|
+
} catch (error) {
|
|
12
|
+
console.error(error);
|
|
13
|
+
}
|
|
11
14
|
}
|
|
12
|
-
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
main();
|
package/demo/local.js
ADDED
package/demo/package.json
CHANGED
package/index.js
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import FormData from 'form-data';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import Bottleneck from 'bottleneck';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import ffmpeg from 'fluent-ffmpeg';
|
|
7
|
+
import { getAudioDurationInSeconds } from 'get-audio-duration';
|
|
8
|
+
import os from 'os'; // For temporary directory
|
|
9
|
+
|
|
10
|
+
// Static imports for local dependencies
|
|
11
|
+
import { pipeline } from '@xenova/transformers';
|
|
12
|
+
import audioDecode from 'audio-decode';
|
|
9
13
|
|
|
10
14
|
class WhisperMix {
|
|
11
15
|
constructor(setup = {}) {
|
|
12
|
-
this.model = '
|
|
16
|
+
this.model = 'openai';
|
|
13
17
|
this.bottleneck = {
|
|
14
18
|
minTime: 2000,
|
|
15
19
|
maxConcurrent: 1,
|
|
@@ -18,28 +22,49 @@ class WhisperMix {
|
|
|
18
22
|
reservoirRefreshInterval: 60000
|
|
19
23
|
};
|
|
20
24
|
this.chunkSize = 15 * 60 - 10; // 14 minutes 50 seconds
|
|
21
|
-
|
|
25
|
+
|
|
22
26
|
const config = {
|
|
23
|
-
'
|
|
27
|
+
'openai': {
|
|
24
28
|
url: 'https://api.openai.com/v1/audio/transcriptions',
|
|
29
|
+
modelName: 'whisper-1',
|
|
25
30
|
apiKey: process.env.OPENAI_API_KEY,
|
|
26
31
|
},
|
|
27
|
-
'
|
|
32
|
+
'groq/large-v3': {
|
|
28
33
|
url: 'https://api.groq.com/openai/v1/audio/transcriptions',
|
|
34
|
+
modelName: 'whisper-large-v3',
|
|
29
35
|
apiKey: process.env.GROQ_API_KEY,
|
|
30
36
|
},
|
|
37
|
+
'xenova/large-v3': {
|
|
38
|
+
local: true,
|
|
39
|
+
modelName: 'Xenova/whisper-large-v3',
|
|
40
|
+
},
|
|
41
|
+
'xenova/base': {
|
|
42
|
+
local: true,
|
|
43
|
+
modelName: 'Xenova/whisper-base',
|
|
44
|
+
},
|
|
31
45
|
};
|
|
32
46
|
|
|
33
47
|
Object.assign(this, setup);
|
|
34
48
|
|
|
35
|
-
this.
|
|
36
|
-
this.
|
|
49
|
+
this.config = config[this.model];
|
|
50
|
+
this.apiKey = this.apiKey || this.config.apiKey;
|
|
51
|
+
this.apiUrl = this.config.url;
|
|
52
|
+
this.isLocal = this.config.local || false;
|
|
53
|
+
this.modelName = this.config.modelName;
|
|
37
54
|
|
|
38
55
|
this.limiter = new Bottleneck(this.bottleneck);
|
|
39
56
|
}
|
|
40
57
|
|
|
41
58
|
async fromFile(filePath) {
|
|
42
59
|
const absolutePath = path.resolve(filePath);
|
|
60
|
+
|
|
61
|
+
// Check if file exists
|
|
62
|
+
try {
|
|
63
|
+
await fs.promises.access(absolutePath, fs.constants.F_OK);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
throw new Error(`File not found: ${absolutePath}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
43
68
|
const tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'whispermix-chunks-'));
|
|
44
69
|
|
|
45
70
|
try {
|
|
@@ -47,7 +72,11 @@ class WhisperMix {
|
|
|
47
72
|
|
|
48
73
|
if (duration <= this.chunkSize) {
|
|
49
74
|
// Process as a single file
|
|
50
|
-
|
|
75
|
+
if (this.isLocal) {
|
|
76
|
+
return this._transcribeLocalFile(absolutePath);
|
|
77
|
+
} else {
|
|
78
|
+
return this.fromStream(fs.createReadStream(absolutePath));
|
|
79
|
+
}
|
|
51
80
|
} else {
|
|
52
81
|
// Split audio and process chunks
|
|
53
82
|
let accumulatedTranscription = "";
|
|
@@ -72,8 +101,13 @@ class WhisperMix {
|
|
|
72
101
|
.run();
|
|
73
102
|
});
|
|
74
103
|
|
|
75
|
-
|
|
76
|
-
|
|
104
|
+
let transcription;
|
|
105
|
+
if (this.isLocal) {
|
|
106
|
+
transcription = await this._transcribeLocalFile(chunkPath);
|
|
107
|
+
} else {
|
|
108
|
+
const chunkStream = fs.createReadStream(chunkPath);
|
|
109
|
+
transcription = await this.fromStream(chunkStream);
|
|
110
|
+
}
|
|
77
111
|
accumulatedTranscription += (transcription + " ").trimStart();
|
|
78
112
|
|
|
79
113
|
// Clean up chunk immediately after processing
|
|
@@ -96,10 +130,14 @@ class WhisperMix {
|
|
|
96
130
|
}
|
|
97
131
|
|
|
98
132
|
async fromStream(audioStream) {
|
|
133
|
+
if (this.isLocal) {
|
|
134
|
+
throw new Error('fromStream is not supported for local Whisper model. Use fromFile instead.');
|
|
135
|
+
}
|
|
136
|
+
|
|
99
137
|
return this.limiter.schedule(() => new Promise((resolve, reject) => {
|
|
100
138
|
const formData = new FormData();
|
|
101
139
|
formData.append('file', audioStream);
|
|
102
|
-
formData.append('model', this.
|
|
140
|
+
formData.append('model', this.modelName);
|
|
103
141
|
|
|
104
142
|
this._makeRequest(formData)
|
|
105
143
|
.then(resolve)
|
|
@@ -120,6 +158,55 @@ class WhisperMix {
|
|
|
120
158
|
throw error.response ? error.response.data : error.message;
|
|
121
159
|
}
|
|
122
160
|
}
|
|
161
|
+
|
|
162
|
+
async _transcribeLocalFile(filePath) {
|
|
163
|
+
try {
|
|
164
|
+
// Read the audio file as a buffer
|
|
165
|
+
const buffer = fs.readFileSync(filePath);
|
|
166
|
+
|
|
167
|
+
// Decode the audio file (supports MP3, WAV, etc.)
|
|
168
|
+
const audioBuffer = await audioDecode(buffer);
|
|
169
|
+
|
|
170
|
+
// Convert to Float32Array and resample to 16kHz if needed
|
|
171
|
+
let audioData = new Float32Array(audioBuffer.length);
|
|
172
|
+
|
|
173
|
+
// Copy audio data
|
|
174
|
+
for (let i = 0; i < audioBuffer.length; i++) {
|
|
175
|
+
audioData[i] = audioBuffer.getChannelData(0)[i];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Resample to 16kHz if the sample rate is different
|
|
179
|
+
if (audioBuffer.sampleRate !== 16000) {
|
|
180
|
+
const ratio = 16000 / audioBuffer.sampleRate;
|
|
181
|
+
const newLength = Math.round(audioData.length * ratio);
|
|
182
|
+
const resampledData = new Float32Array(newLength);
|
|
183
|
+
|
|
184
|
+
for (let i = 0; i < newLength; i++) {
|
|
185
|
+
const oldIndex = Math.floor(i / ratio);
|
|
186
|
+
resampledData[i] = audioData[oldIndex] || 0;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
audioData = resampledData;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Create the transcriber pipeline
|
|
193
|
+
const transcriber = await pipeline('automatic-speech-recognition', this.modelName);
|
|
194
|
+
|
|
195
|
+
// Pass the processed audio data
|
|
196
|
+
const result = await transcriber(audioData, {
|
|
197
|
+
language: this.language,
|
|
198
|
+
task: 'transcribe',
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
return result.text.trim();
|
|
202
|
+
} catch (error) {
|
|
203
|
+
throw new Error(`Local transcription failed: ${error.message}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
123
206
|
}
|
|
124
207
|
|
|
125
|
-
|
|
208
|
+
// Support both CommonJS and ES modules
|
|
209
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
210
|
+
module.exports = WhisperMix;
|
|
211
|
+
}
|
|
212
|
+
export default WhisperMix;
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whispermix",
|
|
3
3
|
"description": "ποΈ WhisperMix is a versatile module for transcribing audio using OpenAIβs Whisper or Groqβs Whisper v3 model.",
|
|
4
|
-
"version": "1.3.
|
|
4
|
+
"version": "1.3.8",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"keywords": [
|
|
6
7
|
"whisper",
|
|
7
8
|
"openai",
|
|
@@ -25,7 +26,9 @@
|
|
|
25
26
|
"multilingual",
|
|
26
27
|
"bottleneck",
|
|
27
28
|
"chunk",
|
|
28
|
-
"clasen"
|
|
29
|
+
"clasen",
|
|
30
|
+
"local",
|
|
31
|
+
"xenova"
|
|
29
32
|
],
|
|
30
33
|
"repository": {
|
|
31
34
|
"type": "git",
|
|
@@ -41,10 +44,12 @@
|
|
|
41
44
|
"url": "https://github.com/clasen/WhisperMix/issues"
|
|
42
45
|
},
|
|
43
46
|
"dependencies": {
|
|
44
|
-
"
|
|
47
|
+
"@xenova/transformers": "^2.17.2",
|
|
48
|
+
"audio-decode": "^2.1.3",
|
|
49
|
+
"axios": "^1.12.1",
|
|
45
50
|
"bottleneck": "^2.19.5",
|
|
46
51
|
"fluent-ffmpeg": "^2.1.3",
|
|
47
|
-
"form-data": "^4.0.
|
|
52
|
+
"form-data": "^4.0.4",
|
|
48
53
|
"get-audio-duration": "^4.0.1"
|
|
49
54
|
}
|
|
50
55
|
}
|