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 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 or Groq's Whisper Large v3 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
- const WhisperMix = require('whispermix');
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: 'whisper-1' }); // For OpenAI's Whisper
32
+ const whisper = new WhisperMix({ model: 'openai' }); // For OpenAI's Whisper
32
33
  // or
33
- const whisperGroq = new WhisperMix({ model: 'whisper-large-v3' }); // For Groq's Whisper Large v3
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
- const fs = require('fs');
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
- ### 🚦 Bottleneck Configuration
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 'whisper-1' (OpenAI) or 'whisper-large-v3' (Groq).
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
@@ -0,0 +1,2 @@
1
+ OPENAI_API_KEY="sk-proj-..."
2
+ GROQ_API_KEY="gsk_..."
package/demo/groq.js CHANGED
@@ -1,12 +1,17 @@
1
- require('dotenv').config();
2
- const WhisperMix = require('../index.js')
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
- main(); async function main() {
6
+ async function main() {
7
7
  for (let i = 0; i < 10; i++) {
8
- transcribe.fromFile('./example.mp3')
9
- .then(console.log)
10
- .catch(console.error);
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
@@ -0,0 +1,8 @@
1
+ import WhisperMix from '../index.js';
2
+
3
+ const whisperLocal = new WhisperMix({
4
+ model: 'xenova/large-v3'
5
+ });
6
+
7
+ const r = await whisperLocal.fromFile('conversation.wav');
8
+ console.log(r);
package/demo/package.json CHANGED
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "name": "demo",
3
+ "type": "module",
3
4
  "version": "1.0.0",
4
5
  "description": "",
5
6
  "main": "groq.js",
package/index.js CHANGED
@@ -1,15 +1,19 @@
1
- const axios = require('axios');
2
- const FormData = require('form-data');
3
- const fs = require('fs');
4
- const Bottleneck = require('bottleneck');
5
- const path = require('path');
6
- const ffmpeg = require('fluent-ffmpeg');
7
- const { getAudioDurationInSeconds } = require('get-audio-duration');
8
- const os = require('os'); // For temporary directory
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 = 'whisper-1';
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
- 'whisper-1': {
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
- 'whisper-large-v3': {
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.apiKey = this.apiKey || config[this.model].apiKey;
36
- this.apiUrl = config[this.model].url;
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
- return this.fromStream(fs.createReadStream(absolutePath));
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
- const chunkStream = fs.createReadStream(chunkPath);
76
- const transcription = await this.fromStream(chunkStream);
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.model);
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
- module.exports = WhisperMix;
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.6",
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
- "axios": "^1.9.0",
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.0",
52
+ "form-data": "^4.0.4",
48
53
  "get-audio-duration": "^4.0.1"
49
54
  }
50
55
  }