node-av 4.0.0 → 5.0.1
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 +23 -0
- package/binding.gyp +19 -11
- package/dist/api/bitstream-filter.d.ts +13 -12
- package/dist/api/bitstream-filter.js +33 -29
- package/dist/api/bitstream-filter.js.map +1 -1
- package/dist/api/decoder.d.ts +211 -96
- package/dist/api/decoder.js +396 -375
- package/dist/api/decoder.js.map +1 -1
- package/dist/api/demuxer.d.ts +10 -10
- package/dist/api/demuxer.js +7 -10
- package/dist/api/demuxer.js.map +1 -1
- package/dist/api/encoder.d.ts +155 -122
- package/dist/api/encoder.js +368 -541
- package/dist/api/encoder.js.map +1 -1
- package/dist/api/filter-complex.d.ts +769 -0
- package/dist/api/filter-complex.js +1596 -0
- package/dist/api/filter-complex.js.map +1 -0
- package/dist/api/filter-presets.d.ts +68 -0
- package/dist/api/filter-presets.js +96 -0
- package/dist/api/filter-presets.js.map +1 -1
- package/dist/api/filter.d.ts +183 -113
- package/dist/api/filter.js +347 -365
- package/dist/api/filter.js.map +1 -1
- package/dist/api/fmp4-stream.d.ts +18 -2
- package/dist/api/fmp4-stream.js +45 -4
- package/dist/api/fmp4-stream.js.map +1 -1
- package/dist/api/hardware.d.ts +47 -0
- package/dist/api/hardware.js +45 -0
- package/dist/api/hardware.js.map +1 -1
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.js +3 -0
- package/dist/api/index.js.map +1 -1
- package/dist/api/io-stream.d.ts +3 -3
- package/dist/api/io-stream.js.map +1 -1
- package/dist/api/muxer.d.ts +10 -10
- package/dist/api/muxer.js +6 -6
- package/dist/api/muxer.js.map +1 -1
- package/dist/api/pipeline.d.ts +2 -2
- package/dist/api/pipeline.js +22 -22
- package/dist/api/pipeline.js.map +1 -1
- package/dist/api/rtp-stream.d.ts +5 -2
- package/dist/api/rtp-stream.js +33 -4
- package/dist/api/rtp-stream.js.map +1 -1
- package/dist/api/types.d.ts +63 -7
- package/dist/api/utilities/audio-sample.d.ts +10 -0
- package/dist/api/utilities/audio-sample.js +10 -0
- package/dist/api/utilities/audio-sample.js.map +1 -1
- package/dist/api/utilities/channel-layout.d.ts +1 -0
- package/dist/api/utilities/channel-layout.js +1 -0
- package/dist/api/utilities/channel-layout.js.map +1 -1
- package/dist/api/utilities/image.d.ts +38 -0
- package/dist/api/utilities/image.js +38 -0
- package/dist/api/utilities/image.js.map +1 -1
- package/dist/api/utilities/index.d.ts +1 -0
- package/dist/api/utilities/index.js +2 -0
- package/dist/api/utilities/index.js.map +1 -1
- package/dist/api/utilities/media-type.d.ts +1 -0
- package/dist/api/utilities/media-type.js +1 -0
- package/dist/api/utilities/media-type.js.map +1 -1
- package/dist/api/utilities/pixel-format.d.ts +3 -0
- package/dist/api/utilities/pixel-format.js +3 -0
- package/dist/api/utilities/pixel-format.js.map +1 -1
- package/dist/api/utilities/sample-format.d.ts +5 -0
- package/dist/api/utilities/sample-format.js +5 -0
- package/dist/api/utilities/sample-format.js.map +1 -1
- package/dist/api/utilities/scheduler.d.ts +21 -52
- package/dist/api/utilities/scheduler.js +20 -58
- package/dist/api/utilities/scheduler.js.map +1 -1
- package/dist/api/utilities/streaming.d.ts +32 -1
- package/dist/api/utilities/streaming.js +32 -1
- package/dist/api/utilities/streaming.js.map +1 -1
- package/dist/api/utilities/timestamp.d.ts +14 -0
- package/dist/api/utilities/timestamp.js +14 -0
- package/dist/api/utilities/timestamp.js.map +1 -1
- package/dist/api/utilities/whisper-model.d.ts +310 -0
- package/dist/api/utilities/whisper-model.js +528 -0
- package/dist/api/utilities/whisper-model.js.map +1 -0
- package/dist/api/whisper.d.ts +324 -0
- package/dist/api/whisper.js +362 -0
- package/dist/api/whisper.js.map +1 -0
- package/dist/constants/constants.d.ts +3 -1
- package/dist/constants/constants.js +1 -0
- package/dist/constants/constants.js.map +1 -1
- package/dist/ffmpeg/index.d.ts +3 -3
- package/dist/ffmpeg/index.js +3 -3
- package/dist/ffmpeg/utils.d.ts +27 -0
- package/dist/ffmpeg/utils.js +28 -16
- package/dist/ffmpeg/utils.js.map +1 -1
- package/dist/lib/binding.d.ts +4 -4
- package/dist/lib/binding.js.map +1 -1
- package/dist/lib/codec-parameters.d.ts +47 -1
- package/dist/lib/codec-parameters.js +55 -0
- package/dist/lib/codec-parameters.js.map +1 -1
- package/dist/lib/fifo.d.ts +416 -0
- package/dist/lib/fifo.js +453 -0
- package/dist/lib/fifo.js.map +1 -0
- package/dist/lib/frame.d.ts +96 -1
- package/dist/lib/frame.js +139 -1
- package/dist/lib/frame.js.map +1 -1
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.js +2 -0
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/native-types.d.ts +29 -2
- package/dist/lib/rational.d.ts +18 -0
- package/dist/lib/rational.js +19 -0
- package/dist/lib/rational.js.map +1 -1
- package/dist/lib/types.d.ts +23 -1
- package/install/check.js +2 -2
- package/package.json +31 -21
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
import { createWriteStream, existsSync } from 'node:fs';
|
|
2
|
+
import { access, constants, mkdir, rename, unlink } from 'node:fs/promises';
|
|
3
|
+
import { Agent, get } from 'node:https';
|
|
4
|
+
import { dirname, resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = dirname(__filename);
|
|
8
|
+
/**
|
|
9
|
+
* Standard GGML Whisper models
|
|
10
|
+
*/
|
|
11
|
+
export const WHISPER_MODELS = [
|
|
12
|
+
'tiny',
|
|
13
|
+
'tiny.en',
|
|
14
|
+
'tiny-q5_1',
|
|
15
|
+
'tiny.en-q5_1',
|
|
16
|
+
'tiny-q8_0',
|
|
17
|
+
'base',
|
|
18
|
+
'base.en',
|
|
19
|
+
'base-q5_1',
|
|
20
|
+
'base.en-q5_1',
|
|
21
|
+
'base-q8_0',
|
|
22
|
+
'small',
|
|
23
|
+
'small.en',
|
|
24
|
+
'small.en-tdrz',
|
|
25
|
+
'small-q5_1',
|
|
26
|
+
'small.en-q5_1',
|
|
27
|
+
'small-q8_0',
|
|
28
|
+
'medium',
|
|
29
|
+
'medium.en',
|
|
30
|
+
'medium-q5_0',
|
|
31
|
+
'medium.en-q5_0',
|
|
32
|
+
'medium-q8_0',
|
|
33
|
+
'large-v1',
|
|
34
|
+
'large-v2',
|
|
35
|
+
'large-v2-q5_0',
|
|
36
|
+
'large-v2-q8_0',
|
|
37
|
+
'large-v3',
|
|
38
|
+
'large-v3-q5_0',
|
|
39
|
+
'large-v3-turbo',
|
|
40
|
+
'large-v3-turbo-q5_0',
|
|
41
|
+
'large-v3-turbo-q8_0',
|
|
42
|
+
];
|
|
43
|
+
/**
|
|
44
|
+
* Whisper VAD (Voice Activity Detection) models
|
|
45
|
+
*/
|
|
46
|
+
export const WHISPER_VAD_MODELS = ['silero-v5.1.2', 'silero-v6.2.0'];
|
|
47
|
+
/**
|
|
48
|
+
* Whisper.cpp model downloader utilities.
|
|
49
|
+
*
|
|
50
|
+
* Provides static methods for downloading GGML and VAD models from HuggingFace,
|
|
51
|
+
* validating model names, and checking model availability. Supports automatic
|
|
52
|
+
* model type detection and prevents concurrent downloads of the same model.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* import { WhisperDownloader } from 'node-av';
|
|
57
|
+
*
|
|
58
|
+
* // Download a GGML model
|
|
59
|
+
* const modelPath = await WhisperDownloader.downloadModel({
|
|
60
|
+
* model: 'base',
|
|
61
|
+
* outputPath: './models'
|
|
62
|
+
* });
|
|
63
|
+
*
|
|
64
|
+
* // Download a VAD model
|
|
65
|
+
* const vadPath = await WhisperDownloader.downloadVADModel('silero-v5.1.2', './models');
|
|
66
|
+
*
|
|
67
|
+
* // Check available models
|
|
68
|
+
* const categories = WhisperDownloader.getModelsByCategory();
|
|
69
|
+
* console.log(categories); // Map { 'tiny' => ['tiny', 'tiny.en', ...], ... }
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export class WhisperDownloader {
|
|
73
|
+
static DEFAULT_MODEL_PATH = resolve(__dirname, '../../../models');
|
|
74
|
+
static DEFAULT_SRC = 'https://huggingface.co/ggerganov/whisper.cpp';
|
|
75
|
+
static DEFAULT_PFX = 'resolve/main/ggml';
|
|
76
|
+
static TDRZ_SRC = 'https://huggingface.co/akashmjn/tinydiarize-whisper.cpp';
|
|
77
|
+
static TDRZ_PFX = 'resolve/main/ggml';
|
|
78
|
+
static VAD_SRC = 'https://huggingface.co/ggml-org/whisper-vad';
|
|
79
|
+
static VAD_PFX = 'resolve/main/ggml';
|
|
80
|
+
// Global map to track ongoing downloads and prevent race conditions
|
|
81
|
+
static activeDownloads = new Map();
|
|
82
|
+
// Private constructor to prevent instantiation
|
|
83
|
+
constructor() { }
|
|
84
|
+
/**
|
|
85
|
+
* Check if a model name is a valid GGML model.
|
|
86
|
+
*
|
|
87
|
+
* Validates whether the provided string matches one of the available
|
|
88
|
+
* GGML Whisper model names.
|
|
89
|
+
*
|
|
90
|
+
* @param model - Model name to validate
|
|
91
|
+
*
|
|
92
|
+
* @returns True if the model is a valid GGML model name
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```typescript
|
|
96
|
+
* import { WhisperDownloader } from 'node-av';
|
|
97
|
+
*
|
|
98
|
+
* console.log(WhisperDownloader.isValidModel('base')); // true
|
|
99
|
+
* console.log(WhisperDownloader.isValidModel('large-v3')); // true
|
|
100
|
+
* console.log(WhisperDownloader.isValidModel('invalid')); // false
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
static isValidModel(model) {
|
|
104
|
+
return WHISPER_MODELS.includes(model);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Check if a model name is a valid VAD model.
|
|
108
|
+
*
|
|
109
|
+
* Validates whether the provided string matches one of the available
|
|
110
|
+
* Silero VAD model names.
|
|
111
|
+
*
|
|
112
|
+
* @param model - Model name to validate
|
|
113
|
+
*
|
|
114
|
+
* @returns True if the model is a valid VAD model name
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```typescript
|
|
118
|
+
* import { WhisperDownloader } from 'node-av';
|
|
119
|
+
*
|
|
120
|
+
* console.log(WhisperDownloader.isValidVADModel('silero-v5.1.2')); // true
|
|
121
|
+
* console.log(WhisperDownloader.isValidVADModel('invalid')); // false
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
static isValidVADModel(model) {
|
|
125
|
+
return WHISPER_VAD_MODELS.includes(model);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Get all available GGML models grouped by category.
|
|
129
|
+
*
|
|
130
|
+
* Returns a map of model categories (tiny, base, small, medium, large)
|
|
131
|
+
* with their corresponding model variants.
|
|
132
|
+
*
|
|
133
|
+
* @returns Map of category names to model name arrays
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```typescript
|
|
137
|
+
* import { WhisperDownloader } from 'node-av';
|
|
138
|
+
*
|
|
139
|
+
* const categories = WhisperDownloader.getModelsByCategory();
|
|
140
|
+
* console.log(categories.get('base'));
|
|
141
|
+
* // ['base', 'base.en', 'base-q5_1', 'base.en-q5_1', 'base-q8_0']
|
|
142
|
+
*
|
|
143
|
+
* // List all categories
|
|
144
|
+
* for (const [category, models] of categories) {
|
|
145
|
+
* console.log(`${category}: ${models.length} variants`);
|
|
146
|
+
* }
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
static getModelsByCategory() {
|
|
150
|
+
const categories = new Map();
|
|
151
|
+
for (const model of WHISPER_MODELS) {
|
|
152
|
+
const category = model.split(/[.-]/)[0];
|
|
153
|
+
if (!categories.has(category)) {
|
|
154
|
+
categories.set(category, []);
|
|
155
|
+
}
|
|
156
|
+
categories.get(category).push(model);
|
|
157
|
+
}
|
|
158
|
+
return categories;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Get the download URL for a model.
|
|
162
|
+
*
|
|
163
|
+
* Constructs the HuggingFace download URL for a given model name.
|
|
164
|
+
* Automatically detects whether it's a GGML or VAD model if type is not specified.
|
|
165
|
+
* Handles special models like tinydiarize variants.
|
|
166
|
+
*
|
|
167
|
+
* @param model - Model name
|
|
168
|
+
*
|
|
169
|
+
* @param type - Model type (auto-detected if not provided)
|
|
170
|
+
*
|
|
171
|
+
* @returns Full download URL for the model
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```typescript
|
|
175
|
+
* import { WhisperDownloader } from 'node-av';
|
|
176
|
+
*
|
|
177
|
+
* // GGML model URL
|
|
178
|
+
* const url = WhisperDownloader.getModelUrl('base');
|
|
179
|
+
* console.log(url);
|
|
180
|
+
* // 'https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.bin'
|
|
181
|
+
*
|
|
182
|
+
* // VAD model URL
|
|
183
|
+
* const vadUrl = WhisperDownloader.getModelUrl('silero-v5.1.2');
|
|
184
|
+
* console.log(vadUrl);
|
|
185
|
+
* // 'https://huggingface.co/ggml-org/whisper-vad/resolve/main/ggml-silero-v5.1.2.bin'
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
188
|
+
static getModelUrl(model, type) {
|
|
189
|
+
// Auto-detect type if not provided
|
|
190
|
+
if (!type) {
|
|
191
|
+
if (this.isValidVADModel(model)) {
|
|
192
|
+
type = 'vad';
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
type = 'ggml';
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (type === 'vad') {
|
|
199
|
+
return `${this.VAD_SRC}/${this.VAD_PFX}-${model}.bin`;
|
|
200
|
+
}
|
|
201
|
+
// GGML models
|
|
202
|
+
const isTdrz = model.includes('tdrz');
|
|
203
|
+
const src = isTdrz ? this.TDRZ_SRC : this.DEFAULT_SRC;
|
|
204
|
+
const pfx = isTdrz ? this.TDRZ_PFX : this.DEFAULT_PFX;
|
|
205
|
+
return `${src}/${pfx}-${model}.bin`;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Check if a model file already exists.
|
|
209
|
+
*
|
|
210
|
+
* Checks whether a model file has already been downloaded to the specified path.
|
|
211
|
+
* Useful for skipping redundant downloads.
|
|
212
|
+
*
|
|
213
|
+
* @param model - Model name
|
|
214
|
+
*
|
|
215
|
+
* @param outputPath - Directory path to check
|
|
216
|
+
*
|
|
217
|
+
* @returns True if the model file exists
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* ```typescript
|
|
221
|
+
* import { WhisperDownloader } from 'node-av';
|
|
222
|
+
*
|
|
223
|
+
* if (WhisperDownloader.modelExists('base', './models')) {
|
|
224
|
+
* console.log('Model already downloaded');
|
|
225
|
+
* } else {
|
|
226
|
+
* await WhisperDownloader.downloadModel({
|
|
227
|
+
* model: 'base',
|
|
228
|
+
* outputPath: './models'
|
|
229
|
+
* });
|
|
230
|
+
* }
|
|
231
|
+
* ```
|
|
232
|
+
*/
|
|
233
|
+
static modelExists(model, outputPath) {
|
|
234
|
+
const filePath = resolve(outputPath, `ggml-${model}.bin`);
|
|
235
|
+
return existsSync(filePath);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Find whisper-cli executable in system PATH.
|
|
239
|
+
*
|
|
240
|
+
* Searches for whisper-cli binary in system PATH and local build directory.
|
|
241
|
+
* Returns the path to the executable if found.
|
|
242
|
+
*
|
|
243
|
+
* @returns Path to whisper-cli executable, or null if not found
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* ```typescript
|
|
247
|
+
* import { WhisperDownloader } from 'node-av';
|
|
248
|
+
*
|
|
249
|
+
* const cliPath = await WhisperDownloader.findWhisperCli();
|
|
250
|
+
* if (cliPath) {
|
|
251
|
+
* console.log(`Found whisper-cli at: ${cliPath}`);
|
|
252
|
+
* } else {
|
|
253
|
+
* console.log('whisper-cli not found');
|
|
254
|
+
* }
|
|
255
|
+
* ```
|
|
256
|
+
*/
|
|
257
|
+
static async findWhisperCli() {
|
|
258
|
+
const pathEnv = process.env.PATH ?? '';
|
|
259
|
+
const paths = pathEnv.split(':');
|
|
260
|
+
for (const path of paths) {
|
|
261
|
+
const whisperPath = resolve(path, 'whisper-cli');
|
|
262
|
+
try {
|
|
263
|
+
await access(whisperPath, constants.X_OK);
|
|
264
|
+
return 'whisper-cli';
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// Check local build
|
|
271
|
+
const localPath = './build/bin/whisper-cli';
|
|
272
|
+
try {
|
|
273
|
+
await access(localPath, constants.X_OK);
|
|
274
|
+
return localPath;
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Download a Whisper model file.
|
|
282
|
+
*
|
|
283
|
+
* Downloads a GGML or VAD model from HuggingFace to the specified directory.
|
|
284
|
+
* Automatically detects model type based on model name if not specified.
|
|
285
|
+
* Prevents race conditions when the same model is downloaded concurrently.
|
|
286
|
+
* Returns immediately if the model file already exists.
|
|
287
|
+
*
|
|
288
|
+
* @param options - Download configuration options
|
|
289
|
+
*
|
|
290
|
+
* @returns Path to the downloaded model file
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* ```typescript
|
|
294
|
+
* import { WhisperDownloader } from 'node-av';
|
|
295
|
+
*
|
|
296
|
+
* // Download GGML model (auto-detected)
|
|
297
|
+
* const path = await WhisperDownloader.downloadModel({
|
|
298
|
+
* model: 'base',
|
|
299
|
+
* outputPath: './models'
|
|
300
|
+
* });
|
|
301
|
+
* console.log(`Downloaded to: ${path}`);
|
|
302
|
+
*
|
|
303
|
+
* // Download VAD model (auto-detected)
|
|
304
|
+
* const vadPath = await WhisperDownloader.downloadModel({
|
|
305
|
+
* model: 'silero-v5.1.2',
|
|
306
|
+
* outputPath: './models'
|
|
307
|
+
* });
|
|
308
|
+
*
|
|
309
|
+
* // Explicit type specification
|
|
310
|
+
* const explicitPath = await WhisperDownloader.downloadModel({
|
|
311
|
+
* model: 'base',
|
|
312
|
+
* outputPath: './models',
|
|
313
|
+
* type: 'ggml'
|
|
314
|
+
* });
|
|
315
|
+
* ```
|
|
316
|
+
*/
|
|
317
|
+
static async downloadModel(options) {
|
|
318
|
+
const { model, outputPath = this.DEFAULT_MODEL_PATH, type } = options;
|
|
319
|
+
// Auto-detect type if not provided
|
|
320
|
+
let modelType = type ?? 'ggml';
|
|
321
|
+
if (!type) {
|
|
322
|
+
if (this.isValidVADModel(model)) {
|
|
323
|
+
modelType = 'vad';
|
|
324
|
+
}
|
|
325
|
+
else if (this.isValidModel(model)) {
|
|
326
|
+
modelType = 'ggml';
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
throw new Error(`Invalid model: ${model}. Use getModelsByCategory() to see available GGML models or WHISPER_VAD_MODELS for VAD models.`);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
// Validate model name based on type
|
|
333
|
+
if (modelType === 'vad' && !this.isValidVADModel(model)) {
|
|
334
|
+
throw new Error(`Invalid VAD model: ${model}. Available: ${WHISPER_VAD_MODELS.join(', ')}`);
|
|
335
|
+
}
|
|
336
|
+
else if (modelType === 'ggml' && !this.isValidModel(model)) {
|
|
337
|
+
throw new Error(`Invalid GGML model: ${model}. Use getModelsByCategory() to see available models.`);
|
|
338
|
+
}
|
|
339
|
+
const filePath = resolve(outputPath, `ggml-${model}.bin`);
|
|
340
|
+
// Check if download is already in progress
|
|
341
|
+
const downloadKey = `${modelType}:${model}:${outputPath}`;
|
|
342
|
+
const existingDownload = this.activeDownloads.get(downloadKey);
|
|
343
|
+
if (existingDownload) {
|
|
344
|
+
return existingDownload;
|
|
345
|
+
}
|
|
346
|
+
// If file already exists AND no download in progress, return path immediately
|
|
347
|
+
if (existsSync(filePath)) {
|
|
348
|
+
return filePath;
|
|
349
|
+
}
|
|
350
|
+
// Create output directory recursively if it doesn't exist
|
|
351
|
+
await mkdir(outputPath, { recursive: true });
|
|
352
|
+
// Start new download to temporary file
|
|
353
|
+
const url = this.getModelUrl(model, modelType);
|
|
354
|
+
const tmpFilePath = `${filePath}.tmp`;
|
|
355
|
+
const downloadPromise = this.followRedirect(url, tmpFilePath, 0)
|
|
356
|
+
.then(async () => {
|
|
357
|
+
// Rename temporary file to final name after successful download
|
|
358
|
+
await rename(tmpFilePath, filePath);
|
|
359
|
+
return filePath;
|
|
360
|
+
})
|
|
361
|
+
.catch(async (error) => {
|
|
362
|
+
// Clean up temporary file on error
|
|
363
|
+
try {
|
|
364
|
+
await unlink(tmpFilePath);
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
// Ignore errors when deleting temp file
|
|
368
|
+
}
|
|
369
|
+
throw error;
|
|
370
|
+
})
|
|
371
|
+
.finally(() => {
|
|
372
|
+
// Clean up from active downloads map
|
|
373
|
+
this.activeDownloads.delete(downloadKey);
|
|
374
|
+
});
|
|
375
|
+
// Store in map to prevent concurrent downloads
|
|
376
|
+
this.activeDownloads.set(downloadKey, downloadPromise);
|
|
377
|
+
return downloadPromise;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Download a VAD model (convenience method).
|
|
381
|
+
*
|
|
382
|
+
* Convenience wrapper for downloading VAD models without specifying type.
|
|
383
|
+
* Equivalent to calling downloadModel() with type: 'vad'.
|
|
384
|
+
*
|
|
385
|
+
* @param model - VAD model name
|
|
386
|
+
*
|
|
387
|
+
* @param outputPath - Directory path for download (default: current directory)
|
|
388
|
+
*
|
|
389
|
+
* @returns Path to the downloaded VAD model file
|
|
390
|
+
*
|
|
391
|
+
* @example
|
|
392
|
+
* ```typescript
|
|
393
|
+
* import { WhisperDownloader } from 'node-av';
|
|
394
|
+
*
|
|
395
|
+
* const vadPath = await WhisperDownloader.downloadVADModel(
|
|
396
|
+
* 'silero-v5.1.2',
|
|
397
|
+
* './models'
|
|
398
|
+
* );
|
|
399
|
+
* console.log(`VAD model downloaded to: ${vadPath}`);
|
|
400
|
+
* ```
|
|
401
|
+
*/
|
|
402
|
+
static async downloadVADModel(model, outputPath) {
|
|
403
|
+
return this.downloadModel({ model, outputPath, type: 'vad' });
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Download multiple Whisper models.
|
|
407
|
+
*
|
|
408
|
+
* Downloads multiple models sequentially to avoid overwhelming the network.
|
|
409
|
+
* Each model is validated and downloaded using the same logic as downloadModel().
|
|
410
|
+
*
|
|
411
|
+
* @param models - Array of model names to download
|
|
412
|
+
*
|
|
413
|
+
* @param outputPath - Directory path for downloads (default: current directory)
|
|
414
|
+
*
|
|
415
|
+
* @param type - Model type for all models (auto-detected if not provided)
|
|
416
|
+
*
|
|
417
|
+
* @returns Array of paths to downloaded model files
|
|
418
|
+
*
|
|
419
|
+
* @example
|
|
420
|
+
* ```typescript
|
|
421
|
+
* import { WhisperDownloader } from 'node-av';
|
|
422
|
+
*
|
|
423
|
+
* // Download multiple GGML models
|
|
424
|
+
* const paths = await WhisperDownloader.downloadModels(
|
|
425
|
+
* ['tiny', 'base', 'small'],
|
|
426
|
+
* './models'
|
|
427
|
+
* );
|
|
428
|
+
* console.log(`Downloaded ${paths.length} models`);
|
|
429
|
+
*
|
|
430
|
+
* // Download multiple VAD models
|
|
431
|
+
* const vadPaths = await WhisperDownloader.downloadModels(
|
|
432
|
+
* ['silero-v5.1.2', 'silero-v6.2.0'],
|
|
433
|
+
* './models',
|
|
434
|
+
* 'vad'
|
|
435
|
+
* );
|
|
436
|
+
* ```
|
|
437
|
+
*/
|
|
438
|
+
static async downloadModels(models, outputPath, type) {
|
|
439
|
+
const downloadedPaths = [];
|
|
440
|
+
for (const model of models) {
|
|
441
|
+
const filePath = await this.downloadModel({ model, outputPath, type });
|
|
442
|
+
downloadedPaths.push(filePath);
|
|
443
|
+
}
|
|
444
|
+
return downloadedPaths;
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Follow HTTP redirects recursively to download a file.
|
|
448
|
+
*
|
|
449
|
+
* Handles HTTP redirects (301, 302, 307, 308) up to a maximum of 5 redirects.
|
|
450
|
+
* Downloads the file to the specified output path.
|
|
451
|
+
*
|
|
452
|
+
* @param url - URL to download
|
|
453
|
+
*
|
|
454
|
+
* @param outputPath - Local file path to save the download
|
|
455
|
+
*
|
|
456
|
+
* @param redirectCount - Current redirect count (used internally)
|
|
457
|
+
*
|
|
458
|
+
* @returns Promise that resolves when the download is complete
|
|
459
|
+
*
|
|
460
|
+
* @internal
|
|
461
|
+
*/
|
|
462
|
+
static followRedirect(url, outputPath, redirectCount = 0) {
|
|
463
|
+
return new Promise((resolve, reject) => {
|
|
464
|
+
if (redirectCount > 5) {
|
|
465
|
+
reject(new Error('Too many redirects'));
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
// Use an agent with keepAlive disabled to ensure connections are closed
|
|
469
|
+
const agent = new Agent({ keepAlive: false });
|
|
470
|
+
const request = get(url, { agent }, (response) => {
|
|
471
|
+
// Handle redirects
|
|
472
|
+
if ([301, 302, 307, 308].includes(response.statusCode ?? 0)) {
|
|
473
|
+
const redirectUrl = response.headers.location;
|
|
474
|
+
if (redirectUrl) {
|
|
475
|
+
// Destroy the response to close the connection
|
|
476
|
+
response.destroy();
|
|
477
|
+
this.followRedirect(redirectUrl, outputPath, redirectCount + 1)
|
|
478
|
+
.then(resolve)
|
|
479
|
+
.catch(reject);
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
if (response.statusCode !== 200) {
|
|
484
|
+
// Destroy the response to close the connection
|
|
485
|
+
response.destroy();
|
|
486
|
+
reject(new Error(`Failed to download: HTTP ${response.statusCode}`));
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
const fileStream = createWriteStream(outputPath);
|
|
490
|
+
let hasError = false;
|
|
491
|
+
response.pipe(fileStream);
|
|
492
|
+
fileStream.on('finish', () => {
|
|
493
|
+
// Don't close if we already had an error
|
|
494
|
+
if (!hasError) {
|
|
495
|
+
fileStream.close((err) => {
|
|
496
|
+
// Destroy the response to close the HTTP connection
|
|
497
|
+
response.destroy();
|
|
498
|
+
if (err) {
|
|
499
|
+
reject(err);
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
resolve();
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
fileStream.on('error', (err) => {
|
|
508
|
+
hasError = true;
|
|
509
|
+
// Destroy the response to close the HTTP connection
|
|
510
|
+
response.destroy();
|
|
511
|
+
fileStream.close(() => {
|
|
512
|
+
reject(err);
|
|
513
|
+
});
|
|
514
|
+
});
|
|
515
|
+
response.on('error', (err) => {
|
|
516
|
+
hasError = true;
|
|
517
|
+
fileStream.close(() => {
|
|
518
|
+
reject(err);
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
request.on('error', (err) => {
|
|
523
|
+
reject(err);
|
|
524
|
+
});
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
//# sourceMappingURL=whisper-model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whisper-model.js","sourceRoot":"","sources":["../../../src/api/utilities/whisper-model.ts"],"names":[],"mappings":"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"}
|