nodejs-audio-visualizer 4.4.12 → 4.5.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.
Files changed (48) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +80 -75
  3. package/dist/audio.d.ts +12 -12
  4. package/dist/audio.js +120 -120
  5. package/dist/beats.d.ts +19 -19
  6. package/dist/beats.js +43 -43
  7. package/dist/bpmEncoder.d.ts +14 -14
  8. package/dist/bpmEncoder.js +97 -97
  9. package/dist/config.d.ts +77 -67
  10. package/dist/config.js +369 -356
  11. package/dist/config.js.map +1 -1
  12. package/dist/dsp.d.ts +16 -16
  13. package/dist/dsp.js +76 -76
  14. package/dist/highlight.d.ts +32 -32
  15. package/dist/highlight.js +325 -325
  16. package/dist/image.d.ts +79 -79
  17. package/dist/image.js +458 -458
  18. package/dist/index.d.ts +79 -74
  19. package/dist/index.js +531 -528
  20. package/dist/index.js.map +1 -1
  21. package/dist/subtitleConvert.d.ts +3 -3
  22. package/dist/subtitleConvert.js +61 -61
  23. package/dist/video.d.ts +80 -80
  24. package/dist/video.js +434 -430
  25. package/dist/video.js.map +1 -1
  26. package/dist/waitForEventLoop.d.ts +1 -1
  27. package/dist/waitForEventLoop.js +5 -5
  28. package/package.json +61 -61
  29. package/src/audio.ts +120 -120
  30. package/src/beats.ts +68 -68
  31. package/src/bpmEncoder.ts +97 -97
  32. package/src/config.ts +476 -462
  33. package/src/dsp.ts +95 -95
  34. package/src/highlight.ts +284 -284
  35. package/src/image.ts +602 -602
  36. package/src/index.ts +713 -685
  37. package/src/subtitleConvert.ts +63 -63
  38. package/src/tests/audio.test.ts +96 -96
  39. package/src/tests/config.test.ts +496 -475
  40. package/src/tests/dsp.test.ts +24 -24
  41. package/src/tests/highlight.test.ts +178 -178
  42. package/src/tests/image.test.ts +222 -222
  43. package/src/tests/index.test.ts +112 -112
  44. package/src/tests/subtitleConvert.test.ts +23 -23
  45. package/src/tests/video.test.ts +285 -216
  46. package/src/typings.d.ts +6 -6
  47. package/src/video.ts +550 -547
  48. package/src/waitForEventLoop.ts +2 -2
package/dist/video.js CHANGED
@@ -1,431 +1,435 @@
1
- "use strict";
2
- var __assign = (this && this.__assign) || function () {
3
- __assign = Object.assign || function(t) {
4
- for (var s, i = 1, n = arguments.length; i < n; i++) {
5
- s = arguments[i];
6
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
- t[p] = s[p];
8
- }
9
- return t;
10
- };
11
- return __assign.apply(this, arguments);
12
- };
13
- var __spreadArrays = (this && this.__spreadArrays) || function () {
14
- for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
15
- for (var r = Array(s), k = 0, i = 0; i < il; i++)
16
- for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
17
- r[k] = a[j];
18
- return r;
19
- };
20
- var __importDefault = (this && this.__importDefault) || function (mod) {
21
- return (mod && mod.__esModule) ? mod : { "default": mod };
22
- };
23
- Object.defineProperty(exports, "__esModule", { value: true });
24
- var child_process_1 = require("child_process");
25
- var fs_1 = require("fs");
26
- var path_1 = require("path");
27
- var os_1 = require("os");
28
- var ffmpeg_static_1 = __importDefault(require("ffmpeg-static"));
29
- var timeouts = {
30
- readVideoFrame: 30 * 1000,
31
- waitDrain: 120 * 1000,
32
- waitForProcessExit: 480 * 1000,
33
- };
34
- var escapeSubtitleFilterPath = function (subtitlePath) {
35
- return subtitlePath
36
- .replace(/\\/g, '\\\\')
37
- .replace(/:/g, '\\:')
38
- .replace(/'/g, "\\'")
39
- .replace(/,/g, '\\,')
40
- .replace(/\[/g, '\\[')
41
- .replace(/\]/g, '\\]');
42
- };
43
- exports.spawnFfmpegVideoWriter = function (config) {
44
- var _a, _b, _c, _d, _e;
45
- if (!ffmpeg_static_1.default) {
46
- throw new Error('ffmpeg path not found');
47
- }
48
- var crf = config.crf || '23';
49
- var preset = config.preset || 'medium';
50
- var args = ['-y'];
51
- var seek = (_b = (_a = config.audioSegment) === null || _a === void 0 ? void 0 : _a.seekSeconds, (_b !== null && _b !== void 0 ? _b : config.audioSeekSeconds));
52
- var duration = (_d = (_c = config.audioSegment) === null || _c === void 0 ? void 0 : _c.durationSeconds, (_d !== null && _d !== void 0 ? _d : config.audioDurationSeconds));
53
- if (seek !== undefined && duration !== undefined) {
54
- args.push('-ss', String(seek), '-t', String(duration));
55
- }
56
- args.push('-i', config.audioFilename, '-f', 'image2pipe', '-vcodec', 'bmp', '-framerate', "" + config.fps, '-i', '-', '-crf', crf, '-c:a', 'aac', '-b:a', '384k', '-profile:a', 'aac_low', '-c:v', 'libx264', '-r', "" + config.fps, '-pix_fmt', 'yuv420p', '-preset', preset, '-shortest');
57
- if (config.subtitleFilename) {
58
- var subPath = escapeSubtitleFilterPath(config.subtitleFilename);
59
- var alignment = (_e = config.subtitleAlignmentAss, (_e !== null && _e !== void 0 ? _e : 2));
60
- args.push('-vf', "subtitles='" + subPath + "':force_style='Alignment=" + alignment + "'");
61
- }
62
- args.push(config.videoFileName);
63
- var ffmpeg = child_process_1.spawn(ffmpeg_static_1.default, args);
64
- if (config.onStderr) {
65
- ffmpeg.stderr.on('data', config.onStderr);
66
- }
67
- return ffmpeg;
68
- };
69
- exports.detectSceneChanges = function (videoPath, threshold) {
70
- if (threshold === void 0) { threshold = 0.4; }
71
- return new Promise(function (resolve, reject) {
72
- if (!ffmpeg_static_1.default) {
73
- reject(new Error('ffmpeg path not found'));
74
- return;
75
- }
76
- var ffmpeg = child_process_1.spawn(ffmpeg_static_1.default, [
77
- '-i', videoPath,
78
- '-filter:v', "select='gt(scene," + threshold + ")',showinfo",
79
- '-f', 'null',
80
- '-'
81
- ]);
82
- var stderr = '';
83
- ffmpeg.stderr.on('data', function (data) { stderr += data.toString(); });
84
- ffmpeg.on('close', function () {
85
- var sceneChanges = [];
86
- for (var _i = 0, _a = stderr.split('\n'); _i < _a.length; _i++) {
87
- var line = _a[_i];
88
- if (!line.includes('Parsed_showinfo'))
89
- continue;
90
- var nMatch = line.match(/n:\s*(\d+)/);
91
- var ptsMatch = line.match(/pts:\s*(\d+)/);
92
- var ptsTimeMatch = line.match(/pts_time:\s*([\d.]+)/);
93
- if (nMatch && ptsTimeMatch) {
94
- sceneChanges.push({
95
- frameNumber: parseInt(nMatch[1]),
96
- pts: ptsMatch ? parseInt(ptsMatch[1]) : 0,
97
- ptsTime: parseFloat(ptsTimeMatch[1]),
98
- });
99
- }
100
- }
101
- resolve(sceneChanges);
102
- });
103
- ffmpeg.on('error', reject);
104
- });
105
- };
106
- exports.getVideoInfo = function (videoPath) {
107
- return new Promise(function (resolvePromise, reject) {
108
- if (!ffmpeg_static_1.default) {
109
- reject(new Error('ffmpeg path not found'));
110
- return;
111
- }
112
- var ffmpeg = child_process_1.spawn(ffmpeg_static_1.default, ['-i', videoPath, '-hide_banner']);
113
- var stderr = '';
114
- ffmpeg.stderr.on('data', function (data) { stderr += data.toString(); });
115
- ffmpeg.on('close', function () {
116
- var dimMatch = stderr.match(/Stream.*Video:.*?(\d{2,5})x(\d{2,5})/);
117
- if (!dimMatch) {
118
- reject(new Error("Could not determine video dimensions for: " + videoPath));
119
- return;
120
- }
121
- var durMatch = stderr.match(/Duration:\s*(\d+):(\d+):([\d.]+)/);
122
- var duration = durMatch
123
- ? parseInt(durMatch[1]) * 3600 + parseInt(durMatch[2]) * 60 + parseFloat(durMatch[3])
124
- : 0;
125
- resolvePromise({
126
- width: parseInt(dimMatch[1]),
127
- height: parseInt(dimMatch[2]),
128
- duration: duration,
129
- });
130
- });
131
- });
132
- };
133
- var isOrientationMismatch = function (sw, sh, tw, th) {
134
- return (sw > sh && tw < th) || (sw < sh && tw > th);
135
- };
136
- exports.buildVideoFilter = function (sourceWidth, sourceHeight, targetWidth, targetHeight) {
137
- if (!isOrientationMismatch(sourceWidth, sourceHeight, targetWidth, targetHeight)) {
138
- return "scale=" + targetWidth + ":" + targetHeight;
139
- }
140
- var targetAspect = targetWidth / targetHeight;
141
- var makeEven = function (n) { return Math.floor(n / 2) * 2; };
142
- var cropW, cropH, cropX, cropY;
143
- if (sourceWidth / sourceHeight > targetAspect) {
144
- cropW = makeEven(Math.round(sourceHeight * targetAspect));
145
- cropH = makeEven(sourceHeight);
146
- cropX = Math.floor((sourceWidth - cropW) / 2);
147
- cropY = Math.floor((sourceHeight - cropH) / 2);
148
- }
149
- else {
150
- cropW = makeEven(sourceWidth);
151
- cropH = makeEven(Math.round(sourceWidth / targetAspect));
152
- cropX = Math.floor((sourceWidth - cropW) / 2);
153
- cropY = Math.floor((sourceHeight - cropH) / 2);
154
- }
155
- return "crop=" + cropW + ":" + cropH + ":" + cropX + ":" + cropY + ",scale=" + targetWidth + ":" + targetHeight;
156
- };
157
- exports.spawnVideoFrameReader = function (config) {
158
- if (!ffmpeg_static_1.default) {
159
- throw new Error('ffmpeg path not found');
160
- }
161
- var args = [
162
- '-stream_loop', '-1',
163
- '-i', config.videoPath,
164
- '-r', "" + config.fps,
165
- '-frames:v', "" + config.totalFrames,
166
- ];
167
- if (config.width && config.height) {
168
- var filter = config.sourceWidth && config.sourceHeight
169
- ? exports.buildVideoFilter(config.sourceWidth, config.sourceHeight, config.width, config.height)
170
- : "scale=" + config.width + ":" + config.height;
171
- args.push('-vf', filter);
172
- }
173
- args.push('-f', 'rawvideo', '-pix_fmt', 'bgr24', '-v', 'quiet', 'pipe:1');
174
- return child_process_1.spawn(ffmpeg_static_1.default, args);
175
- };
176
- exports.readVideoFrame = function (stream, frameSize, timeoutMs) {
177
- if (timeoutMs === void 0) { timeoutMs = timeouts.readVideoFrame; }
178
- return new Promise(function (resolve) {
179
- var timer;
180
- var cleanup = function () {
181
- if (timer) {
182
- clearTimeout(timer);
183
- timer = undefined;
184
- }
185
- stream.removeListener('readable', onReadable);
186
- stream.removeListener('end', onEnd);
187
- stream.removeListener('error', onErrorOrClose);
188
- stream.removeListener('close', onErrorOrClose);
189
- };
190
- var onReadable = function () {
191
- tryRead();
192
- };
193
- var onEnd = function () {
194
- cleanup();
195
- resolve(null);
196
- };
197
- var onErrorOrClose = function () {
198
- cleanup();
199
- resolve(null);
200
- };
201
- var tryRead = function () {
202
- var data = stream.read(frameSize);
203
- if (data !== null) {
204
- cleanup();
205
- resolve(data.length === frameSize ? data : null);
206
- return;
207
- }
208
- if (timeoutMs > 0) {
209
- timer = setTimeout(function () {
210
- console.error('readVideoFrame timeout:', timeoutMs);
211
- cleanup();
212
- resolve(null);
213
- }, timeoutMs);
214
- }
215
- stream.once('readable', onReadable);
216
- stream.once('end', onEnd);
217
- stream.once('error', onErrorOrClose);
218
- stream.once('close', onErrorOrClose);
219
- };
220
- tryRead();
221
- });
222
- };
223
- exports.getProgress = function (onProgress) {
224
- return function (stderrOutput) {
225
- var matchResult = stderrOutput.toString().match(/frame=[ ]+(\d+)/);
226
- if (!matchResult) {
227
- return;
228
- }
229
- var currentFrame = +matchResult[1];
230
- if (isNaN(currentFrame)) {
231
- return;
232
- }
233
- onProgress(currentFrame);
234
- };
235
- };
236
- exports.calculateProgress = function (framesCount, callback) {
237
- return function (currentFrame) {
238
- return callback(+(currentFrame / framesCount * 100).toFixed(2));
239
- };
240
- };
241
- exports.waitDrain = function (stream, processStream, timeoutMs) {
242
- if (timeoutMs === void 0) { timeoutMs = timeouts.waitDrain; }
243
- return new Promise(function (resolve) {
244
- var _a, _b, _c;
245
- var cleanup = function () {
246
- var _a, _b, _c;
247
- if (timer) {
248
- clearTimeout(timer);
249
- timer = undefined;
250
- }
251
- stream.removeListener('drain', onDrain);
252
- stream.removeListener('error', onErrorOrClose);
253
- stream.removeListener('close', onErrorOrClose);
254
- (_a = processStream) === null || _a === void 0 ? void 0 : _a.removeListener('error', onErrorOrClose);
255
- (_b = processStream) === null || _b === void 0 ? void 0 : _b.removeListener('exit', onErrorOrClose);
256
- (_c = processStream) === null || _c === void 0 ? void 0 : _c.removeListener('close', onErrorOrClose);
257
- };
258
- var onDrain = function () {
259
- cleanup();
260
- resolve(true);
261
- };
262
- var onErrorOrClose = function () {
263
- cleanup();
264
- resolve(false);
265
- };
266
- var timer;
267
- if (timeoutMs > 0) {
268
- timer = setTimeout(function () {
269
- console.error('waitDrain timeout:', timeoutMs);
270
- cleanup();
271
- resolve(false);
272
- }, timeoutMs);
273
- }
274
- stream.once('drain', onDrain);
275
- stream.once('error', onErrorOrClose);
276
- stream.once('close', onErrorOrClose);
277
- (_a = processStream) === null || _a === void 0 ? void 0 : _a.once('error', onErrorOrClose);
278
- (_b = processStream) === null || _b === void 0 ? void 0 : _b.once('exit', onErrorOrClose);
279
- (_c = processStream) === null || _c === void 0 ? void 0 : _c.once('close', onErrorOrClose);
280
- });
281
- };
282
- var resolveProcessExit = function (resolve, exitCode, reason) {
283
- resolve(__assign({ exitCode: exitCode }, (reason && { reason: reason })));
284
- };
285
- exports.waitForProcessExit = function (processStream, timeoutMs) {
286
- if (timeoutMs === void 0) { timeoutMs = timeouts.waitForProcessExit; }
287
- return new Promise(function (resolve) {
288
- var cleanup = function () {
289
- if (timer) {
290
- clearTimeout(timer);
291
- timer = undefined;
292
- }
293
- processStream.removeListener('exit', onExit);
294
- processStream.removeListener('close', onClose);
295
- processStream.removeListener('error', onError);
296
- };
297
- var onExit = function (code) {
298
- cleanup();
299
- var exitCode = (code !== null && code !== void 0 ? code : 0);
300
- resolveProcessExit(resolve, exitCode, exitCode !== 0 ? "ffmpeg exited with code " + exitCode : undefined);
301
- };
302
- var onClose = function (code) {
303
- cleanup();
304
- var exitCode = (code !== null && code !== void 0 ? code : 0);
305
- resolveProcessExit(resolve, exitCode, exitCode !== 0 ? "ffmpeg exited with code " + exitCode : undefined);
306
- };
307
- var onError = function () {
308
- cleanup();
309
- resolveProcessExit(resolve, 1, 'ffmpeg process error');
310
- };
311
- var timer;
312
- if (timeoutMs > 0) {
313
- timer = setTimeout(function () {
314
- console.error('waitForProcessExit timeout:', timeoutMs);
315
- cleanup();
316
- resolveProcessExit(resolve, 1, "waitForProcessExit timeout (" + timeoutMs + "ms)");
317
- }, timeoutMs);
318
- }
319
- processStream.once('exit', onExit);
320
- processStream.once('close', onClose);
321
- processStream.once('error', onError);
322
- });
323
- };
324
- var mergeSmallScenes = function (sceneChanges, videoDuration, minDuration) {
325
- if (minDuration === void 0) { minDuration = 2; }
326
- if (sceneChanges.length === 0)
327
- return [];
328
- var merged = [sceneChanges[0]];
329
- for (var i = 1; i < sceneChanges.length; i++) {
330
- var lastKept = merged[merged.length - 1];
331
- var gap = sceneChanges[i].ptsTime - lastKept.ptsTime;
332
- if (gap >= minDuration) {
333
- merged.push(sceneChanges[i]);
334
- }
335
- }
336
- var lastScene = merged[merged.length - 1];
337
- if (videoDuration - lastScene.ptsTime < minDuration && merged.length > 1) {
338
- merged.pop();
339
- }
340
- return merged;
341
- };
342
- exports.buildBeatSyncedSegments = function (beatFrameIndices, totalFrames, sceneChanges, videoDuration) {
343
- if (sceneChanges.length === 0) {
344
- return [
345
- {
346
- outputStartFrame: 0,
347
- videoSeekSeconds: 0,
348
- frameCount: totalFrames,
349
- },
350
- ];
351
- }
352
- var consolidated = mergeSmallScenes(sceneChanges, videoDuration);
353
- var seekPositions;
354
- if (consolidated.length >= 2) {
355
- seekPositions = consolidated.map(function (sc) { return sc.ptsTime; });
356
- }
357
- else {
358
- var count_1 = Math.max(10, Math.ceil(videoDuration / 3));
359
- seekPositions = Array.from({ length: count_1 }, function (_, i) { return (i * videoDuration) / count_1; });
360
- }
361
- var boundaries = __spreadArrays([0], beatFrameIndices);
362
- if (boundaries[boundaries.length - 1] !== totalFrames) {
363
- boundaries.push(totalFrames);
364
- }
365
- var unique = Array.from(new Set(boundaries)).sort(function (a, b) { return a - b; });
366
- var segments = [];
367
- for (var i = 0; i < unique.length - 1; i++) {
368
- segments.push({
369
- outputStartFrame: unique[i],
370
- videoSeekSeconds: seekPositions[i % seekPositions.length],
371
- frameCount: unique[i + 1] - unique[i],
372
- });
373
- }
374
- return segments;
375
- };
376
- exports.writeConcatFile = function (segments, videoPath, videoDuration, fps) {
377
- var absVideoPath = path_1.resolve(videoPath).replace(/\\/g, '/');
378
- var escaped = absVideoPath.replace(/'/g, "'\\''");
379
- var content = '';
380
- for (var _i = 0, segments_1 = segments; _i < segments_1.length; _i++) {
381
- var seg = segments_1[_i];
382
- var segDuration = seg.frameCount / fps;
383
- var remaining = segDuration;
384
- var pos = seg.videoSeekSeconds % videoDuration;
385
- while (remaining > 0.001) {
386
- var available = videoDuration - pos;
387
- var take = Math.min(remaining, available);
388
- content += "file '" + escaped + "'\n";
389
- content += "inpoint " + pos.toFixed(6) + "\n";
390
- content += "outpoint " + (pos + take).toFixed(6) + "\n\n";
391
- remaining -= take;
392
- pos = 0;
393
- }
394
- }
395
- var concatPath = path_1.join(os_1.tmpdir(), "av-concat-" + Date.now() + ".txt");
396
- fs_1.writeFileSync(concatPath, content, 'utf-8');
397
- return concatPath;
398
- };
399
- exports.writeSubtitlesFile = function (subtitles) {
400
- var subtitlePath = path_1.join(os_1.tmpdir(), "av-subtitles-" + Date.now() + ".srt");
401
- fs_1.writeFileSync(subtitlePath, subtitles, 'utf-8');
402
- return subtitlePath;
403
- };
404
- exports.spawnConcatVideoFrameReader = function (config) {
405
- if (!ffmpeg_static_1.default) {
406
- throw new Error('ffmpeg path not found');
407
- }
408
- var args = [
409
- '-f', 'concat',
410
- '-safe', '0',
411
- '-i', config.concatFilePath,
412
- '-r', "" + config.fps,
413
- '-frames:v', "" + config.totalFrames,
414
- ];
415
- if (config.width && config.height) {
416
- var filter = config.sourceWidth && config.sourceHeight
417
- ? exports.buildVideoFilter(config.sourceWidth, config.sourceHeight, config.width, config.height)
418
- : "scale=" + config.width + ":" + config.height;
419
- args.push('-vf', filter);
420
- }
421
- args.push('-f', 'rawvideo', '-pix_fmt', 'bgr24', '-v', 'quiet', 'pipe:1');
422
- return child_process_1.spawn(ffmpeg_static_1.default, args);
423
- };
424
- exports.cleanupTempFile = function (filePath) {
425
- try {
426
- fs_1.unlinkSync(filePath);
427
- }
428
- catch (_a) { }
429
- };
430
- exports.cleanupConcatFile = exports.cleanupTempFile;
1
+ "use strict";
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
8
+ }
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
12
+ };
13
+ var __spreadArrays = (this && this.__spreadArrays) || function () {
14
+ for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
15
+ for (var r = Array(s), k = 0, i = 0; i < il; i++)
16
+ for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
17
+ r[k] = a[j];
18
+ return r;
19
+ };
20
+ var __importDefault = (this && this.__importDefault) || function (mod) {
21
+ return (mod && mod.__esModule) ? mod : { "default": mod };
22
+ };
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ var child_process_1 = require("child_process");
25
+ var fs_1 = require("fs");
26
+ var path_1 = require("path");
27
+ var os_1 = require("os");
28
+ var ffmpeg_static_1 = __importDefault(require("ffmpeg-static"));
29
+ var config_1 = require("./config");
30
+ var escapeSubtitleFilterPath = function (subtitlePath) {
31
+ return subtitlePath
32
+ .replace(/\\/g, '\\\\')
33
+ .replace(/:/g, '\\:')
34
+ .replace(/'/g, "\\'")
35
+ .replace(/,/g, '\\,')
36
+ .replace(/\[/g, '\\[')
37
+ .replace(/\]/g, '\\]');
38
+ };
39
+ exports.spawnFfmpegVideoWriter = function (config) {
40
+ var _a, _b, _c, _d, _e;
41
+ if (!ffmpeg_static_1.default) {
42
+ throw new Error('ffmpeg path not found');
43
+ }
44
+ var crf = config.crf || '23';
45
+ var preset = config.preset || 'medium';
46
+ var args = ['-y'];
47
+ var seek = (_b = (_a = config.audioSegment) === null || _a === void 0 ? void 0 : _a.seekSeconds, (_b !== null && _b !== void 0 ? _b : config.audioSeekSeconds));
48
+ var duration = (_d = (_c = config.audioSegment) === null || _c === void 0 ? void 0 : _c.durationSeconds, (_d !== null && _d !== void 0 ? _d : config.audioDurationSeconds));
49
+ if (seek !== undefined && duration !== undefined) {
50
+ args.push('-ss', String(seek), '-t', String(duration));
51
+ }
52
+ args.push('-i', config.audioFilename, '-f', 'image2pipe', '-vcodec', 'bmp', '-framerate', "" + config.fps, '-i', '-', '-crf', crf, '-c:a', 'aac', '-b:a', '384k', '-profile:a', 'aac_low', '-c:v', 'libx264', '-r', "" + config.fps, '-pix_fmt', 'yuv420p', '-preset', preset, '-shortest');
53
+ if (config.subtitleFilename) {
54
+ var subPath = escapeSubtitleFilterPath(config.subtitleFilename);
55
+ var alignment = (_e = config.subtitleAlignmentAss, (_e !== null && _e !== void 0 ? _e : 2));
56
+ args.push('-vf', "subtitles='" + subPath + "':force_style='Alignment=" + alignment + "'");
57
+ }
58
+ args.push(config.videoFileName);
59
+ var ffmpeg = child_process_1.spawn(ffmpeg_static_1.default, args);
60
+ if (config.onStderr) {
61
+ ffmpeg.stderr.on('data', config.onStderr);
62
+ }
63
+ return ffmpeg;
64
+ };
65
+ exports.detectSceneChanges = function (videoPath, threshold) {
66
+ if (threshold === void 0) { threshold = 0.4; }
67
+ return new Promise(function (resolve, reject) {
68
+ if (!ffmpeg_static_1.default) {
69
+ reject(new Error('ffmpeg path not found'));
70
+ return;
71
+ }
72
+ var ffmpeg = child_process_1.spawn(ffmpeg_static_1.default, [
73
+ '-i', videoPath,
74
+ '-filter:v', "select='gt(scene," + threshold + ")',showinfo",
75
+ '-f', 'null',
76
+ '-'
77
+ ]);
78
+ var stderr = '';
79
+ ffmpeg.stderr.on('data', function (data) { stderr += data.toString(); });
80
+ ffmpeg.on('close', function () {
81
+ var sceneChanges = [];
82
+ for (var _i = 0, _a = stderr.split('\n'); _i < _a.length; _i++) {
83
+ var line = _a[_i];
84
+ if (!line.includes('Parsed_showinfo'))
85
+ continue;
86
+ var nMatch = line.match(/n:\s*(\d+)/);
87
+ var ptsMatch = line.match(/pts:\s*(\d+)/);
88
+ var ptsTimeMatch = line.match(/pts_time:\s*([\d.]+)/);
89
+ if (nMatch && ptsTimeMatch) {
90
+ sceneChanges.push({
91
+ frameNumber: parseInt(nMatch[1]),
92
+ pts: ptsMatch ? parseInt(ptsMatch[1]) : 0,
93
+ ptsTime: parseFloat(ptsTimeMatch[1]),
94
+ });
95
+ }
96
+ }
97
+ resolve(sceneChanges);
98
+ });
99
+ ffmpeg.on('error', reject);
100
+ });
101
+ };
102
+ exports.getVideoInfo = function (videoPath) {
103
+ return new Promise(function (resolvePromise, reject) {
104
+ if (!ffmpeg_static_1.default) {
105
+ reject(new Error('ffmpeg path not found'));
106
+ return;
107
+ }
108
+ var ffmpeg = child_process_1.spawn(ffmpeg_static_1.default, ['-i', videoPath, '-hide_banner']);
109
+ var stderr = '';
110
+ ffmpeg.stderr.on('data', function (data) { stderr += data.toString(); });
111
+ ffmpeg.on('close', function () {
112
+ var dimMatch = stderr.match(/Stream.*Video:.*?(\d{2,5})x(\d{2,5})/);
113
+ if (!dimMatch) {
114
+ reject(new Error("Could not determine video dimensions for: " + videoPath));
115
+ return;
116
+ }
117
+ var durMatch = stderr.match(/Duration:\s*(\d+):(\d+):([\d.]+)/);
118
+ var duration = durMatch
119
+ ? parseInt(durMatch[1]) * 3600 + parseInt(durMatch[2]) * 60 + parseFloat(durMatch[3])
120
+ : 0;
121
+ resolvePromise({
122
+ width: parseInt(dimMatch[1]),
123
+ height: parseInt(dimMatch[2]),
124
+ duration: duration,
125
+ });
126
+ });
127
+ });
128
+ };
129
+ var isOrientationMismatch = function (sw, sh, tw, th) {
130
+ return (sw > sh && tw < th) || (sw < sh && tw > th);
131
+ };
132
+ exports.buildVideoFilter = function (sourceWidth, sourceHeight, targetWidth, targetHeight) {
133
+ if (!isOrientationMismatch(sourceWidth, sourceHeight, targetWidth, targetHeight)) {
134
+ return "scale=" + targetWidth + ":" + targetHeight;
135
+ }
136
+ var targetAspect = targetWidth / targetHeight;
137
+ var makeEven = function (n) { return Math.floor(n / 2) * 2; };
138
+ var cropW, cropH, cropX, cropY;
139
+ if (sourceWidth / sourceHeight > targetAspect) {
140
+ cropW = makeEven(Math.round(sourceHeight * targetAspect));
141
+ cropH = makeEven(sourceHeight);
142
+ cropX = Math.floor((sourceWidth - cropW) / 2);
143
+ cropY = Math.floor((sourceHeight - cropH) / 2);
144
+ }
145
+ else {
146
+ cropW = makeEven(sourceWidth);
147
+ cropH = makeEven(Math.round(sourceWidth / targetAspect));
148
+ cropX = Math.floor((sourceWidth - cropW) / 2);
149
+ cropY = Math.floor((sourceHeight - cropH) / 2);
150
+ }
151
+ return "crop=" + cropW + ":" + cropH + ":" + cropX + ":" + cropY + ",scale=" + targetWidth + ":" + targetHeight;
152
+ };
153
+ exports.spawnVideoFrameReader = function (config) {
154
+ if (!ffmpeg_static_1.default) {
155
+ throw new Error('ffmpeg path not found');
156
+ }
157
+ var args = [
158
+ '-stream_loop', '-1',
159
+ '-i', config.videoPath,
160
+ '-r', "" + config.fps,
161
+ '-frames:v', "" + config.totalFrames,
162
+ ];
163
+ if (config.width && config.height) {
164
+ var filter = config.sourceWidth && config.sourceHeight
165
+ ? exports.buildVideoFilter(config.sourceWidth, config.sourceHeight, config.width, config.height)
166
+ : "scale=" + config.width + ":" + config.height;
167
+ args.push('-vf', filter);
168
+ }
169
+ args.push('-f', 'rawvideo', '-pix_fmt', 'bgr24', '-v', 'quiet', 'pipe:1');
170
+ return child_process_1.spawn(ffmpeg_static_1.default, args);
171
+ };
172
+ exports.readVideoFrame = function (stream, frameSize, timeoutMs) {
173
+ if (timeoutMs === void 0) { timeoutMs = config_1.defaults.timeouts.readVideoFrame; }
174
+ return new Promise(function (resolve) {
175
+ var timer;
176
+ var settled = false;
177
+ var finish = function (result) {
178
+ if (settled) {
179
+ return;
180
+ }
181
+ settled = true;
182
+ if (timer) {
183
+ clearTimeout(timer);
184
+ timer = undefined;
185
+ }
186
+ stream.removeListener('readable', onReadable);
187
+ stream.removeListener('end', onEnd);
188
+ stream.removeListener('error', onErrorOrClose);
189
+ stream.removeListener('close', onErrorOrClose);
190
+ resolve(result);
191
+ };
192
+ var onReadable = function () {
193
+ tryRead();
194
+ };
195
+ var onEnd = function () {
196
+ finish(null);
197
+ };
198
+ var onErrorOrClose = function () {
199
+ finish(null);
200
+ };
201
+ var tryRead = function () {
202
+ var data = stream.read(frameSize);
203
+ if (data !== null) {
204
+ finish(data.length === frameSize ? data : null);
205
+ }
206
+ };
207
+ tryRead();
208
+ if (settled) {
209
+ return;
210
+ }
211
+ if (timeoutMs > 0) {
212
+ timer = setTimeout(function () {
213
+ console.error('readVideoFrame timeout:', timeoutMs);
214
+ finish(null);
215
+ }, timeoutMs);
216
+ }
217
+ stream.on('readable', onReadable);
218
+ stream.once('end', onEnd);
219
+ stream.once('error', onErrorOrClose);
220
+ stream.once('close', onErrorOrClose);
221
+ });
222
+ };
223
+ exports.getProgress = function (onProgress) {
224
+ return function (stderrOutput) {
225
+ var matchResult = stderrOutput.toString().match(/frame=[ ]+(\d+)/);
226
+ if (!matchResult) {
227
+ return;
228
+ }
229
+ var currentFrame = +matchResult[1];
230
+ if (isNaN(currentFrame)) {
231
+ return;
232
+ }
233
+ onProgress(currentFrame);
234
+ };
235
+ };
236
+ exports.calculateProgress = function (framesCount, callback) {
237
+ return function (currentFrame) {
238
+ return callback(+(currentFrame / framesCount * 100).toFixed(2));
239
+ };
240
+ };
241
+ exports.waitDrain = function (stream, processStream, timeoutMs) {
242
+ if (timeoutMs === void 0) { timeoutMs = config_1.defaults.timeouts.waitDrain; }
243
+ return new Promise(function (resolve) {
244
+ var _a, _b, _c;
245
+ var cleanup = function () {
246
+ var _a, _b, _c;
247
+ if (timer) {
248
+ clearTimeout(timer);
249
+ timer = undefined;
250
+ }
251
+ stream.removeListener('drain', onDrain);
252
+ stream.removeListener('error', onErrorOrClose);
253
+ stream.removeListener('close', onErrorOrClose);
254
+ (_a = processStream) === null || _a === void 0 ? void 0 : _a.removeListener('error', onErrorOrClose);
255
+ (_b = processStream) === null || _b === void 0 ? void 0 : _b.removeListener('exit', onErrorOrClose);
256
+ (_c = processStream) === null || _c === void 0 ? void 0 : _c.removeListener('close', onErrorOrClose);
257
+ };
258
+ var onDrain = function () {
259
+ cleanup();
260
+ resolve(true);
261
+ };
262
+ var onErrorOrClose = function () {
263
+ cleanup();
264
+ resolve(false);
265
+ };
266
+ var timer;
267
+ if (timeoutMs > 0) {
268
+ timer = setTimeout(function () {
269
+ console.error('waitDrain timeout:', timeoutMs);
270
+ cleanup();
271
+ resolve(false);
272
+ }, timeoutMs);
273
+ }
274
+ stream.once('drain', onDrain);
275
+ stream.once('error', onErrorOrClose);
276
+ stream.once('close', onErrorOrClose);
277
+ (_a = processStream) === null || _a === void 0 ? void 0 : _a.once('error', onErrorOrClose);
278
+ (_b = processStream) === null || _b === void 0 ? void 0 : _b.once('exit', onErrorOrClose);
279
+ (_c = processStream) === null || _c === void 0 ? void 0 : _c.once('close', onErrorOrClose);
280
+ });
281
+ };
282
+ var resolveProcessExit = function (resolve, exitCode, reason) {
283
+ resolve(__assign({ exitCode: exitCode }, (reason && { reason: reason })));
284
+ };
285
+ exports.waitForProcessExit = function (processStream, timeoutMs) {
286
+ if (timeoutMs === void 0) { timeoutMs = config_1.defaults.timeouts.waitForProcessExit; }
287
+ return new Promise(function (resolve) {
288
+ var cleanup = function () {
289
+ if (timer) {
290
+ clearTimeout(timer);
291
+ timer = undefined;
292
+ }
293
+ processStream.removeListener('exit', onExit);
294
+ processStream.removeListener('close', onClose);
295
+ processStream.removeListener('error', onError);
296
+ };
297
+ var onExit = function (code) {
298
+ cleanup();
299
+ var exitCode = (code !== null && code !== void 0 ? code : 0);
300
+ resolveProcessExit(resolve, exitCode, exitCode !== 0 ? "ffmpeg exited with code " + exitCode : undefined);
301
+ };
302
+ var onClose = function (code) {
303
+ cleanup();
304
+ var exitCode = (code !== null && code !== void 0 ? code : 0);
305
+ resolveProcessExit(resolve, exitCode, exitCode !== 0 ? "ffmpeg exited with code " + exitCode : undefined);
306
+ };
307
+ var onError = function () {
308
+ cleanup();
309
+ resolveProcessExit(resolve, 1, 'ffmpeg process error');
310
+ };
311
+ var timer;
312
+ if (timeoutMs > 0) {
313
+ timer = setTimeout(function () {
314
+ console.error('waitForProcessExit timeout:', timeoutMs);
315
+ cleanup();
316
+ resolveProcessExit(resolve, 1, "waitForProcessExit timeout (" + timeoutMs + "ms)");
317
+ }, timeoutMs);
318
+ }
319
+ processStream.once('exit', onExit);
320
+ processStream.once('close', onClose);
321
+ processStream.once('error', onError);
322
+ });
323
+ };
324
+ var mergeSmallScenes = function (sceneChanges, videoDuration, minDuration) {
325
+ if (minDuration === void 0) { minDuration = 2; }
326
+ if (sceneChanges.length === 0)
327
+ return [];
328
+ var merged = [sceneChanges[0]];
329
+ for (var i = 1; i < sceneChanges.length; i++) {
330
+ var lastKept = merged[merged.length - 1];
331
+ var gap = sceneChanges[i].ptsTime - lastKept.ptsTime;
332
+ if (gap >= minDuration) {
333
+ merged.push(sceneChanges[i]);
334
+ }
335
+ }
336
+ var lastScene = merged[merged.length - 1];
337
+ if (videoDuration - lastScene.ptsTime < minDuration && merged.length > 1) {
338
+ merged.pop();
339
+ }
340
+ return merged;
341
+ };
342
+ exports.buildBeatSyncedSegments = function (beatFrameIndices, totalFrames, sceneChanges, videoDuration) {
343
+ if (sceneChanges.length === 0) {
344
+ return [
345
+ {
346
+ outputStartFrame: 0,
347
+ videoSeekSeconds: 0,
348
+ frameCount: totalFrames,
349
+ },
350
+ ];
351
+ }
352
+ var consolidated = mergeSmallScenes(sceneChanges, videoDuration);
353
+ var seekPositions;
354
+ if (consolidated.length >= 2) {
355
+ seekPositions = consolidated.map(function (sc) { return sc.ptsTime; });
356
+ }
357
+ else {
358
+ var count_1 = Math.max(10, Math.ceil(videoDuration / 3));
359
+ seekPositions = Array.from({ length: count_1 }, function (_, i) { return (i * videoDuration) / count_1; });
360
+ }
361
+ var boundaries = __spreadArrays([0], beatFrameIndices);
362
+ if (boundaries[boundaries.length - 1] !== totalFrames) {
363
+ boundaries.push(totalFrames);
364
+ }
365
+ var unique = Array.from(new Set(boundaries)).sort(function (a, b) { return a - b; });
366
+ var segments = [];
367
+ for (var i = 0; i < unique.length - 1; i++) {
368
+ segments.push({
369
+ outputStartFrame: unique[i],
370
+ videoSeekSeconds: seekPositions[i % seekPositions.length],
371
+ frameCount: unique[i + 1] - unique[i],
372
+ });
373
+ }
374
+ return segments;
375
+ };
376
+ exports.writeConcatFile = function (segments, videoPath, videoDuration, fps) {
377
+ var absVideoPath = path_1.resolve(videoPath).replace(/\\/g, '/');
378
+ var escaped = absVideoPath.replace(/'/g, "'\\''");
379
+ var content = '';
380
+ for (var _i = 0, segments_1 = segments; _i < segments_1.length; _i++) {
381
+ var seg = segments_1[_i];
382
+ var segDuration = seg.frameCount / fps;
383
+ var remaining = segDuration;
384
+ var pos = seg.videoSeekSeconds % videoDuration;
385
+ while (remaining > 0.001) {
386
+ var available = videoDuration - pos;
387
+ var take = Math.min(remaining, available);
388
+ content += "file '" + escaped + "'\n";
389
+ content += "inpoint " + pos.toFixed(6) + "\n";
390
+ content += "outpoint " + (pos + take).toFixed(6) + "\n";
391
+ content += "duration " + take.toFixed(6) + "\n\n";
392
+ remaining -= take;
393
+ pos = 0;
394
+ }
395
+ }
396
+ var concatPath = path_1.join(os_1.tmpdir(), "av-concat-" + Date.now() + ".txt");
397
+ fs_1.writeFileSync(concatPath, content, 'utf-8');
398
+ return concatPath;
399
+ };
400
+ exports.writeSubtitlesFile = function (subtitles) {
401
+ var subtitlePath = path_1.join(os_1.tmpdir(), "av-subtitles-" + Date.now() + ".srt");
402
+ fs_1.writeFileSync(subtitlePath, subtitles, 'utf-8');
403
+ return subtitlePath;
404
+ };
405
+ exports.spawnConcatVideoFrameReader = function (config) {
406
+ if (!ffmpeg_static_1.default) {
407
+ throw new Error('ffmpeg path not found');
408
+ }
409
+ var args = [
410
+ '-f', 'concat',
411
+ '-safe', '0',
412
+ '-fflags', '+genpts',
413
+ '-i', config.concatFilePath,
414
+ '-an',
415
+ '-avoid_negative_ts', 'make_zero',
416
+ '-r', "" + config.fps,
417
+ '-frames:v', "" + config.totalFrames,
418
+ ];
419
+ if (config.width && config.height) {
420
+ var filter = config.sourceWidth && config.sourceHeight
421
+ ? exports.buildVideoFilter(config.sourceWidth, config.sourceHeight, config.width, config.height)
422
+ : "scale=" + config.width + ":" + config.height;
423
+ args.push('-vf', filter);
424
+ }
425
+ args.push('-f', 'rawvideo', '-pix_fmt', 'bgr24', '-v', 'quiet', 'pipe:1');
426
+ return child_process_1.spawn(ffmpeg_static_1.default, args);
427
+ };
428
+ exports.cleanupTempFile = function (filePath) {
429
+ try {
430
+ fs_1.unlinkSync(filePath);
431
+ }
432
+ catch (_a) { }
433
+ };
434
+ exports.cleanupConcatFile = exports.cleanupTempFile;
431
435
  //# sourceMappingURL=video.js.map