whispermix 1.2.2 → 1.3.4
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 +7 -1
- package/index.js +60 -1
- package/package.json +7 -4
package/README.md
CHANGED
|
@@ -53,6 +53,12 @@ whisperGroq.fromStream(audioStream)
|
|
|
53
53
|
.catch(error => console.error(error));
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
+
### ⏱️ Long Audio Processing
|
|
57
|
+
|
|
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:
|
|
59
|
+
|
|
60
|
+
The segmented transcriptions are automatically merged into a single result, ensuring a smooth experience when working with content of any length.
|
|
61
|
+
|
|
56
62
|
### 🚦 Bottleneck Configuration
|
|
57
63
|
|
|
58
64
|
WhisperMix uses Bottleneck for rate limiting. You can configure the Bottleneck settings when initializing WhisperMix:
|
|
@@ -88,7 +94,7 @@ Creates a new WhisperMix instance.
|
|
|
88
94
|
|
|
89
95
|
- `options.model`: The model to use for transcription. Can be 'whisper-1' (OpenAI) or 'whisper-large-v3' (Groq).
|
|
90
96
|
- `options.bottleneck`: (Optional) Configuration for Bottleneck rate limiting.
|
|
91
|
-
|
|
97
|
+
- `options.chunkSize`: (Optional) The size in seconds of the chunks to split the audio into. Default is 890 seconds.
|
|
92
98
|
### `whisper.fromFile(filePath)`
|
|
93
99
|
|
|
94
100
|
Transcribes audio from a file.
|
package/index.js
CHANGED
|
@@ -2,6 +2,10 @@ const axios = require('axios');
|
|
|
2
2
|
const FormData = require('form-data');
|
|
3
3
|
const fs = require('fs');
|
|
4
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
|
|
5
9
|
|
|
6
10
|
class WhisperMix {
|
|
7
11
|
constructor(setup = {}) {
|
|
@@ -13,6 +17,7 @@ class WhisperMix {
|
|
|
13
17
|
reservoirRefreshAmount: 18,
|
|
14
18
|
reservoirRefreshInterval: 60000
|
|
15
19
|
};
|
|
20
|
+
this.chunkSize = 15 * 60 - 10; // 14 minutes 50 seconds
|
|
16
21
|
|
|
17
22
|
const config = {
|
|
18
23
|
'whisper-1': {
|
|
@@ -34,7 +39,61 @@ class WhisperMix {
|
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
async fromFile(filePath) {
|
|
37
|
-
|
|
42
|
+
const absolutePath = path.resolve(filePath);
|
|
43
|
+
const tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'whispermix-chunks-'));
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const duration = await getAudioDurationInSeconds(absolutePath);
|
|
47
|
+
|
|
48
|
+
if (duration <= this.chunkSize) {
|
|
49
|
+
// Process as a single file
|
|
50
|
+
return this.fromStream(fs.createReadStream(absolutePath));
|
|
51
|
+
} else {
|
|
52
|
+
// Split audio and process chunks
|
|
53
|
+
let accumulatedTranscription = "";
|
|
54
|
+
const numChunks = Math.ceil(duration / this.chunkSize);
|
|
55
|
+
|
|
56
|
+
for (let i = 0; i < numChunks; i++) {
|
|
57
|
+
const chunkPath = path.join(tempDir, `chunk-${i}.mp3`);
|
|
58
|
+
const startTime = i * this.chunkSize;
|
|
59
|
+
|
|
60
|
+
await new Promise((resolve, reject) => {
|
|
61
|
+
ffmpeg(absolutePath)
|
|
62
|
+
.setStartTime(startTime)
|
|
63
|
+
.setDuration(this.chunkSize)
|
|
64
|
+
.output(chunkPath)
|
|
65
|
+
.on('end', () => {
|
|
66
|
+
console.log(`Chunk ${i + 1}/${numChunks} created: ${chunkPath}`);
|
|
67
|
+
resolve();
|
|
68
|
+
})
|
|
69
|
+
.on('error', (err) => {
|
|
70
|
+
console.error(`Error creating chunk ${i + 1}:`, err);
|
|
71
|
+
reject(err);
|
|
72
|
+
})
|
|
73
|
+
.run();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const chunkStream = fs.createReadStream(chunkPath);
|
|
77
|
+
const transcription = await this.fromStream(chunkStream);
|
|
78
|
+
accumulatedTranscription += (transcription + " ").trimStart();
|
|
79
|
+
|
|
80
|
+
// Clean up chunk immediately after processing
|
|
81
|
+
try {
|
|
82
|
+
await fs.promises.unlink(chunkPath);
|
|
83
|
+
} catch (unlinkErr) {
|
|
84
|
+
console.warn(`Could not delete chunk ${chunkPath}:`, unlinkErr);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return accumulatedTranscription.trim();
|
|
88
|
+
}
|
|
89
|
+
} finally {
|
|
90
|
+
// Clean up the temporary directory
|
|
91
|
+
try {
|
|
92
|
+
await fs.promises.rm(tempDir, { recursive: true, force: true });
|
|
93
|
+
} catch (rmErr) {
|
|
94
|
+
console.warn(`Could not delete temporary directory ${tempDir}:`, rmErr);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
38
97
|
}
|
|
39
98
|
|
|
40
99
|
async fromStream(audioStream) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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.
|
|
4
|
+
"version": "1.3.4",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"whisper",
|
|
7
7
|
"openai",
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
"streaming",
|
|
24
24
|
"file-processing",
|
|
25
25
|
"multilingual",
|
|
26
|
-
"bottleneck"
|
|
26
|
+
"bottleneck",
|
|
27
|
+
"clasen"
|
|
27
28
|
],
|
|
28
29
|
"repository": {
|
|
29
30
|
"type": "git",
|
|
@@ -39,8 +40,10 @@
|
|
|
39
40
|
"url": "https://github.com/clasen/WhisperMix/issues"
|
|
40
41
|
},
|
|
41
42
|
"dependencies": {
|
|
42
|
-
"axios": "^1.
|
|
43
|
+
"axios": "^1.9.0",
|
|
43
44
|
"bottleneck": "^2.19.5",
|
|
44
|
-
"
|
|
45
|
+
"fluent-ffmpeg": "^2.1.3",
|
|
46
|
+
"form-data": "^4.0.0",
|
|
47
|
+
"get-audio-duration": "^4.0.1"
|
|
45
48
|
}
|
|
46
49
|
}
|