node-av 3.1.3 → 5.0.0

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.
Files changed (192) hide show
  1. package/README.md +88 -52
  2. package/binding.gyp +23 -11
  3. package/dist/api/audio-frame-buffer.d.ts +201 -0
  4. package/dist/api/audio-frame-buffer.js +275 -0
  5. package/dist/api/audio-frame-buffer.js.map +1 -0
  6. package/dist/api/bitstream-filter.d.ts +320 -78
  7. package/dist/api/bitstream-filter.js +684 -151
  8. package/dist/api/bitstream-filter.js.map +1 -1
  9. package/dist/api/constants.d.ts +44 -0
  10. package/dist/api/constants.js +45 -0
  11. package/dist/api/constants.js.map +1 -0
  12. package/dist/api/data/test_av1.ivf +0 -0
  13. package/dist/api/data/test_mjpeg.mjpeg +0 -0
  14. package/dist/api/data/test_vp8.ivf +0 -0
  15. package/dist/api/data/test_vp9.ivf +0 -0
  16. package/dist/api/decoder.d.ts +454 -77
  17. package/dist/api/decoder.js +1081 -271
  18. package/dist/api/decoder.js.map +1 -1
  19. package/dist/api/{media-input.d.ts → demuxer.d.ts} +295 -45
  20. package/dist/api/demuxer.js +1965 -0
  21. package/dist/api/demuxer.js.map +1 -0
  22. package/dist/api/encoder.d.ts +423 -132
  23. package/dist/api/encoder.js +1089 -240
  24. package/dist/api/encoder.js.map +1 -1
  25. package/dist/api/filter-complex.d.ts +769 -0
  26. package/dist/api/filter-complex.js +1596 -0
  27. package/dist/api/filter-complex.js.map +1 -0
  28. package/dist/api/filter-presets.d.ts +80 -5
  29. package/dist/api/filter-presets.js +117 -7
  30. package/dist/api/filter-presets.js.map +1 -1
  31. package/dist/api/filter.d.ts +561 -125
  32. package/dist/api/filter.js +1083 -274
  33. package/dist/api/filter.js.map +1 -1
  34. package/dist/api/{fmp4.d.ts → fmp4-stream.d.ts} +141 -140
  35. package/dist/api/fmp4-stream.js +539 -0
  36. package/dist/api/fmp4-stream.js.map +1 -0
  37. package/dist/api/hardware.d.ts +58 -6
  38. package/dist/api/hardware.js +127 -11
  39. package/dist/api/hardware.js.map +1 -1
  40. package/dist/api/index.d.ts +8 -4
  41. package/dist/api/index.js +17 -8
  42. package/dist/api/index.js.map +1 -1
  43. package/dist/api/io-stream.d.ts +6 -6
  44. package/dist/api/io-stream.js +5 -4
  45. package/dist/api/io-stream.js.map +1 -1
  46. package/dist/api/{media-output.d.ts → muxer.d.ts} +280 -66
  47. package/dist/api/muxer.js +1934 -0
  48. package/dist/api/muxer.js.map +1 -0
  49. package/dist/api/pipeline.d.ts +77 -29
  50. package/dist/api/pipeline.js +449 -439
  51. package/dist/api/pipeline.js.map +1 -1
  52. package/dist/api/rtp-stream.d.ts +312 -0
  53. package/dist/api/rtp-stream.js +630 -0
  54. package/dist/api/rtp-stream.js.map +1 -0
  55. package/dist/api/types.d.ts +533 -56
  56. package/dist/api/utilities/async-queue.d.ts +91 -0
  57. package/dist/api/utilities/async-queue.js +162 -0
  58. package/dist/api/utilities/async-queue.js.map +1 -0
  59. package/dist/api/utilities/audio-sample.d.ts +11 -1
  60. package/dist/api/utilities/audio-sample.js +10 -0
  61. package/dist/api/utilities/audio-sample.js.map +1 -1
  62. package/dist/api/utilities/channel-layout.d.ts +1 -0
  63. package/dist/api/utilities/channel-layout.js +1 -0
  64. package/dist/api/utilities/channel-layout.js.map +1 -1
  65. package/dist/api/utilities/image.d.ts +39 -1
  66. package/dist/api/utilities/image.js +38 -0
  67. package/dist/api/utilities/image.js.map +1 -1
  68. package/dist/api/utilities/index.d.ts +3 -0
  69. package/dist/api/utilities/index.js +6 -0
  70. package/dist/api/utilities/index.js.map +1 -1
  71. package/dist/api/utilities/media-type.d.ts +2 -1
  72. package/dist/api/utilities/media-type.js +1 -0
  73. package/dist/api/utilities/media-type.js.map +1 -1
  74. package/dist/api/utilities/pixel-format.d.ts +4 -1
  75. package/dist/api/utilities/pixel-format.js +3 -0
  76. package/dist/api/utilities/pixel-format.js.map +1 -1
  77. package/dist/api/utilities/sample-format.d.ts +6 -1
  78. package/dist/api/utilities/sample-format.js +5 -0
  79. package/dist/api/utilities/sample-format.js.map +1 -1
  80. package/dist/api/utilities/scheduler.d.ts +138 -0
  81. package/dist/api/utilities/scheduler.js +98 -0
  82. package/dist/api/utilities/scheduler.js.map +1 -0
  83. package/dist/api/utilities/streaming.d.ts +105 -15
  84. package/dist/api/utilities/streaming.js +201 -12
  85. package/dist/api/utilities/streaming.js.map +1 -1
  86. package/dist/api/utilities/timestamp.d.ts +15 -1
  87. package/dist/api/utilities/timestamp.js +14 -0
  88. package/dist/api/utilities/timestamp.js.map +1 -1
  89. package/dist/api/utilities/whisper-model.d.ts +310 -0
  90. package/dist/api/utilities/whisper-model.js +528 -0
  91. package/dist/api/utilities/whisper-model.js.map +1 -0
  92. package/dist/api/webrtc-stream.d.ts +288 -0
  93. package/dist/api/webrtc-stream.js +440 -0
  94. package/dist/api/webrtc-stream.js.map +1 -0
  95. package/dist/api/whisper.d.ts +324 -0
  96. package/dist/api/whisper.js +362 -0
  97. package/dist/api/whisper.js.map +1 -0
  98. package/dist/constants/constants.d.ts +54 -2
  99. package/dist/constants/constants.js +48 -1
  100. package/dist/constants/constants.js.map +1 -1
  101. package/dist/constants/encoders.d.ts +2 -1
  102. package/dist/constants/encoders.js +4 -3
  103. package/dist/constants/encoders.js.map +1 -1
  104. package/dist/constants/hardware.d.ts +26 -0
  105. package/dist/constants/hardware.js +27 -0
  106. package/dist/constants/hardware.js.map +1 -0
  107. package/dist/constants/index.d.ts +1 -0
  108. package/dist/constants/index.js +1 -0
  109. package/dist/constants/index.js.map +1 -1
  110. package/dist/ffmpeg/index.d.ts +3 -3
  111. package/dist/ffmpeg/index.js +3 -3
  112. package/dist/ffmpeg/utils.d.ts +27 -0
  113. package/dist/ffmpeg/utils.js +28 -16
  114. package/dist/ffmpeg/utils.js.map +1 -1
  115. package/dist/lib/binding.d.ts +22 -11
  116. package/dist/lib/binding.js.map +1 -1
  117. package/dist/lib/codec-context.d.ts +87 -0
  118. package/dist/lib/codec-context.js +125 -4
  119. package/dist/lib/codec-context.js.map +1 -1
  120. package/dist/lib/codec-parameters.d.ts +229 -1
  121. package/dist/lib/codec-parameters.js +264 -0
  122. package/dist/lib/codec-parameters.js.map +1 -1
  123. package/dist/lib/codec-parser.d.ts +23 -0
  124. package/dist/lib/codec-parser.js +25 -0
  125. package/dist/lib/codec-parser.js.map +1 -1
  126. package/dist/lib/codec.d.ts +26 -4
  127. package/dist/lib/codec.js +35 -0
  128. package/dist/lib/codec.js.map +1 -1
  129. package/dist/lib/dictionary.js +1 -0
  130. package/dist/lib/dictionary.js.map +1 -1
  131. package/dist/lib/error.js +1 -1
  132. package/dist/lib/error.js.map +1 -1
  133. package/dist/lib/fifo.d.ts +416 -0
  134. package/dist/lib/fifo.js +453 -0
  135. package/dist/lib/fifo.js.map +1 -0
  136. package/dist/lib/filter-context.d.ts +52 -11
  137. package/dist/lib/filter-context.js +56 -12
  138. package/dist/lib/filter-context.js.map +1 -1
  139. package/dist/lib/filter-graph.d.ts +9 -0
  140. package/dist/lib/filter-graph.js +13 -0
  141. package/dist/lib/filter-graph.js.map +1 -1
  142. package/dist/lib/filter.d.ts +21 -0
  143. package/dist/lib/filter.js +28 -0
  144. package/dist/lib/filter.js.map +1 -1
  145. package/dist/lib/format-context.d.ts +48 -14
  146. package/dist/lib/format-context.js +76 -7
  147. package/dist/lib/format-context.js.map +1 -1
  148. package/dist/lib/frame.d.ts +264 -1
  149. package/dist/lib/frame.js +351 -1
  150. package/dist/lib/frame.js.map +1 -1
  151. package/dist/lib/hardware-device-context.d.ts +3 -2
  152. package/dist/lib/hardware-device-context.js.map +1 -1
  153. package/dist/lib/index.d.ts +2 -0
  154. package/dist/lib/index.js +4 -0
  155. package/dist/lib/index.js.map +1 -1
  156. package/dist/lib/input-format.d.ts +21 -0
  157. package/dist/lib/input-format.js +42 -2
  158. package/dist/lib/input-format.js.map +1 -1
  159. package/dist/lib/native-types.d.ts +76 -27
  160. package/dist/lib/option.d.ts +25 -13
  161. package/dist/lib/option.js +28 -0
  162. package/dist/lib/option.js.map +1 -1
  163. package/dist/lib/output-format.d.ts +22 -1
  164. package/dist/lib/output-format.js +28 -0
  165. package/dist/lib/output-format.js.map +1 -1
  166. package/dist/lib/packet.d.ts +35 -0
  167. package/dist/lib/packet.js +52 -2
  168. package/dist/lib/packet.js.map +1 -1
  169. package/dist/lib/rational.d.ts +18 -0
  170. package/dist/lib/rational.js +19 -0
  171. package/dist/lib/rational.js.map +1 -1
  172. package/dist/lib/stream.d.ts +126 -0
  173. package/dist/lib/stream.js +188 -5
  174. package/dist/lib/stream.js.map +1 -1
  175. package/dist/lib/sync-queue.d.ts +179 -0
  176. package/dist/lib/sync-queue.js +197 -0
  177. package/dist/lib/sync-queue.js.map +1 -0
  178. package/dist/lib/types.d.ts +49 -1
  179. package/dist/lib/utilities.d.ts +281 -53
  180. package/dist/lib/utilities.js +298 -55
  181. package/dist/lib/utilities.js.map +1 -1
  182. package/install/check.js +2 -2
  183. package/package.json +37 -26
  184. package/dist/api/fmp4.js +0 -710
  185. package/dist/api/fmp4.js.map +0 -1
  186. package/dist/api/media-input.js +0 -1075
  187. package/dist/api/media-input.js.map +0 -1
  188. package/dist/api/media-output.js +0 -1040
  189. package/dist/api/media-output.js.map +0 -1
  190. package/dist/api/webrtc.d.ts +0 -664
  191. package/dist/api/webrtc.js +0 -1132
  192. package/dist/api/webrtc.js.map +0 -1
@@ -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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"}