nodejs-audio-visualizer 4.5.0 → 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 (46) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +80 -80
  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 -77
  10. package/dist/config.js +369 -369
  11. package/dist/dsp.d.ts +16 -16
  12. package/dist/dsp.js +76 -76
  13. package/dist/highlight.d.ts +32 -32
  14. package/dist/highlight.js +325 -325
  15. package/dist/image.d.ts +79 -79
  16. package/dist/image.js +458 -458
  17. package/dist/index.d.ts +79 -79
  18. package/dist/index.js +531 -531
  19. package/dist/subtitleConvert.d.ts +3 -3
  20. package/dist/subtitleConvert.js +61 -61
  21. package/dist/video.d.ts +80 -80
  22. package/dist/video.js +434 -426
  23. package/dist/video.js.map +1 -1
  24. package/dist/waitForEventLoop.d.ts +1 -1
  25. package/dist/waitForEventLoop.js +5 -5
  26. package/package.json +61 -61
  27. package/src/audio.ts +120 -120
  28. package/src/beats.ts +68 -68
  29. package/src/bpmEncoder.ts +97 -97
  30. package/src/config.ts +476 -476
  31. package/src/dsp.ts +95 -95
  32. package/src/highlight.ts +284 -284
  33. package/src/image.ts +602 -602
  34. package/src/index.ts +713 -713
  35. package/src/subtitleConvert.ts +63 -63
  36. package/src/tests/audio.test.ts +96 -96
  37. package/src/tests/config.test.ts +496 -496
  38. package/src/tests/dsp.test.ts +24 -24
  39. package/src/tests/highlight.test.ts +178 -178
  40. package/src/tests/image.test.ts +222 -222
  41. package/src/tests/index.test.ts +112 -112
  42. package/src/tests/subtitleConvert.test.ts +23 -23
  43. package/src/tests/video.test.ts +285 -216
  44. package/src/typings.d.ts +6 -6
  45. package/src/video.ts +550 -542
  46. package/src/waitForEventLoop.ts +2 -2
package/dist/video.js CHANGED
@@ -1,427 +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 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 cleanup = function () {
177
- if (timer) {
178
- clearTimeout(timer);
179
- timer = undefined;
180
- }
181
- stream.removeListener('readable', onReadable);
182
- stream.removeListener('end', onEnd);
183
- stream.removeListener('error', onErrorOrClose);
184
- stream.removeListener('close', onErrorOrClose);
185
- };
186
- var onReadable = function () {
187
- tryRead();
188
- };
189
- var onEnd = function () {
190
- cleanup();
191
- resolve(null);
192
- };
193
- var onErrorOrClose = function () {
194
- cleanup();
195
- resolve(null);
196
- };
197
- var tryRead = function () {
198
- var data = stream.read(frameSize);
199
- if (data !== null) {
200
- cleanup();
201
- resolve(data.length === frameSize ? data : null);
202
- return;
203
- }
204
- if (timeoutMs > 0) {
205
- timer = setTimeout(function () {
206
- console.error('readVideoFrame timeout:', timeoutMs);
207
- cleanup();
208
- resolve(null);
209
- }, timeoutMs);
210
- }
211
- stream.once('readable', onReadable);
212
- stream.once('end', onEnd);
213
- stream.once('error', onErrorOrClose);
214
- stream.once('close', onErrorOrClose);
215
- };
216
- tryRead();
217
- });
218
- };
219
- exports.getProgress = function (onProgress) {
220
- return function (stderrOutput) {
221
- var matchResult = stderrOutput.toString().match(/frame=[ ]+(\d+)/);
222
- if (!matchResult) {
223
- return;
224
- }
225
- var currentFrame = +matchResult[1];
226
- if (isNaN(currentFrame)) {
227
- return;
228
- }
229
- onProgress(currentFrame);
230
- };
231
- };
232
- exports.calculateProgress = function (framesCount, callback) {
233
- return function (currentFrame) {
234
- return callback(+(currentFrame / framesCount * 100).toFixed(2));
235
- };
236
- };
237
- exports.waitDrain = function (stream, processStream, timeoutMs) {
238
- if (timeoutMs === void 0) { timeoutMs = config_1.defaults.timeouts.waitDrain; }
239
- return new Promise(function (resolve) {
240
- var _a, _b, _c;
241
- var cleanup = function () {
242
- var _a, _b, _c;
243
- if (timer) {
244
- clearTimeout(timer);
245
- timer = undefined;
246
- }
247
- stream.removeListener('drain', onDrain);
248
- stream.removeListener('error', onErrorOrClose);
249
- stream.removeListener('close', onErrorOrClose);
250
- (_a = processStream) === null || _a === void 0 ? void 0 : _a.removeListener('error', onErrorOrClose);
251
- (_b = processStream) === null || _b === void 0 ? void 0 : _b.removeListener('exit', onErrorOrClose);
252
- (_c = processStream) === null || _c === void 0 ? void 0 : _c.removeListener('close', onErrorOrClose);
253
- };
254
- var onDrain = function () {
255
- cleanup();
256
- resolve(true);
257
- };
258
- var onErrorOrClose = function () {
259
- cleanup();
260
- resolve(false);
261
- };
262
- var timer;
263
- if (timeoutMs > 0) {
264
- timer = setTimeout(function () {
265
- console.error('waitDrain timeout:', timeoutMs);
266
- cleanup();
267
- resolve(false);
268
- }, timeoutMs);
269
- }
270
- stream.once('drain', onDrain);
271
- stream.once('error', onErrorOrClose);
272
- stream.once('close', onErrorOrClose);
273
- (_a = processStream) === null || _a === void 0 ? void 0 : _a.once('error', onErrorOrClose);
274
- (_b = processStream) === null || _b === void 0 ? void 0 : _b.once('exit', onErrorOrClose);
275
- (_c = processStream) === null || _c === void 0 ? void 0 : _c.once('close', onErrorOrClose);
276
- });
277
- };
278
- var resolveProcessExit = function (resolve, exitCode, reason) {
279
- resolve(__assign({ exitCode: exitCode }, (reason && { reason: reason })));
280
- };
281
- exports.waitForProcessExit = function (processStream, timeoutMs) {
282
- if (timeoutMs === void 0) { timeoutMs = config_1.defaults.timeouts.waitForProcessExit; }
283
- return new Promise(function (resolve) {
284
- var cleanup = function () {
285
- if (timer) {
286
- clearTimeout(timer);
287
- timer = undefined;
288
- }
289
- processStream.removeListener('exit', onExit);
290
- processStream.removeListener('close', onClose);
291
- processStream.removeListener('error', onError);
292
- };
293
- var onExit = function (code) {
294
- cleanup();
295
- var exitCode = (code !== null && code !== void 0 ? code : 0);
296
- resolveProcessExit(resolve, exitCode, exitCode !== 0 ? "ffmpeg exited with code " + exitCode : undefined);
297
- };
298
- var onClose = function (code) {
299
- cleanup();
300
- var exitCode = (code !== null && code !== void 0 ? code : 0);
301
- resolveProcessExit(resolve, exitCode, exitCode !== 0 ? "ffmpeg exited with code " + exitCode : undefined);
302
- };
303
- var onError = function () {
304
- cleanup();
305
- resolveProcessExit(resolve, 1, 'ffmpeg process error');
306
- };
307
- var timer;
308
- if (timeoutMs > 0) {
309
- timer = setTimeout(function () {
310
- console.error('waitForProcessExit timeout:', timeoutMs);
311
- cleanup();
312
- resolveProcessExit(resolve, 1, "waitForProcessExit timeout (" + timeoutMs + "ms)");
313
- }, timeoutMs);
314
- }
315
- processStream.once('exit', onExit);
316
- processStream.once('close', onClose);
317
- processStream.once('error', onError);
318
- });
319
- };
320
- var mergeSmallScenes = function (sceneChanges, videoDuration, minDuration) {
321
- if (minDuration === void 0) { minDuration = 2; }
322
- if (sceneChanges.length === 0)
323
- return [];
324
- var merged = [sceneChanges[0]];
325
- for (var i = 1; i < sceneChanges.length; i++) {
326
- var lastKept = merged[merged.length - 1];
327
- var gap = sceneChanges[i].ptsTime - lastKept.ptsTime;
328
- if (gap >= minDuration) {
329
- merged.push(sceneChanges[i]);
330
- }
331
- }
332
- var lastScene = merged[merged.length - 1];
333
- if (videoDuration - lastScene.ptsTime < minDuration && merged.length > 1) {
334
- merged.pop();
335
- }
336
- return merged;
337
- };
338
- exports.buildBeatSyncedSegments = function (beatFrameIndices, totalFrames, sceneChanges, videoDuration) {
339
- if (sceneChanges.length === 0) {
340
- return [
341
- {
342
- outputStartFrame: 0,
343
- videoSeekSeconds: 0,
344
- frameCount: totalFrames,
345
- },
346
- ];
347
- }
348
- var consolidated = mergeSmallScenes(sceneChanges, videoDuration);
349
- var seekPositions;
350
- if (consolidated.length >= 2) {
351
- seekPositions = consolidated.map(function (sc) { return sc.ptsTime; });
352
- }
353
- else {
354
- var count_1 = Math.max(10, Math.ceil(videoDuration / 3));
355
- seekPositions = Array.from({ length: count_1 }, function (_, i) { return (i * videoDuration) / count_1; });
356
- }
357
- var boundaries = __spreadArrays([0], beatFrameIndices);
358
- if (boundaries[boundaries.length - 1] !== totalFrames) {
359
- boundaries.push(totalFrames);
360
- }
361
- var unique = Array.from(new Set(boundaries)).sort(function (a, b) { return a - b; });
362
- var segments = [];
363
- for (var i = 0; i < unique.length - 1; i++) {
364
- segments.push({
365
- outputStartFrame: unique[i],
366
- videoSeekSeconds: seekPositions[i % seekPositions.length],
367
- frameCount: unique[i + 1] - unique[i],
368
- });
369
- }
370
- return segments;
371
- };
372
- exports.writeConcatFile = function (segments, videoPath, videoDuration, fps) {
373
- var absVideoPath = path_1.resolve(videoPath).replace(/\\/g, '/');
374
- var escaped = absVideoPath.replace(/'/g, "'\\''");
375
- var content = '';
376
- for (var _i = 0, segments_1 = segments; _i < segments_1.length; _i++) {
377
- var seg = segments_1[_i];
378
- var segDuration = seg.frameCount / fps;
379
- var remaining = segDuration;
380
- var pos = seg.videoSeekSeconds % videoDuration;
381
- while (remaining > 0.001) {
382
- var available = videoDuration - pos;
383
- var take = Math.min(remaining, available);
384
- content += "file '" + escaped + "'\n";
385
- content += "inpoint " + pos.toFixed(6) + "\n";
386
- content += "outpoint " + (pos + take).toFixed(6) + "\n\n";
387
- remaining -= take;
388
- pos = 0;
389
- }
390
- }
391
- var concatPath = path_1.join(os_1.tmpdir(), "av-concat-" + Date.now() + ".txt");
392
- fs_1.writeFileSync(concatPath, content, 'utf-8');
393
- return concatPath;
394
- };
395
- exports.writeSubtitlesFile = function (subtitles) {
396
- var subtitlePath = path_1.join(os_1.tmpdir(), "av-subtitles-" + Date.now() + ".srt");
397
- fs_1.writeFileSync(subtitlePath, subtitles, 'utf-8');
398
- return subtitlePath;
399
- };
400
- exports.spawnConcatVideoFrameReader = function (config) {
401
- if (!ffmpeg_static_1.default) {
402
- throw new Error('ffmpeg path not found');
403
- }
404
- var args = [
405
- '-f', 'concat',
406
- '-safe', '0',
407
- '-i', config.concatFilePath,
408
- '-r', "" + config.fps,
409
- '-frames:v', "" + config.totalFrames,
410
- ];
411
- if (config.width && config.height) {
412
- var filter = config.sourceWidth && config.sourceHeight
413
- ? exports.buildVideoFilter(config.sourceWidth, config.sourceHeight, config.width, config.height)
414
- : "scale=" + config.width + ":" + config.height;
415
- args.push('-vf', filter);
416
- }
417
- args.push('-f', 'rawvideo', '-pix_fmt', 'bgr24', '-v', 'quiet', 'pipe:1');
418
- return child_process_1.spawn(ffmpeg_static_1.default, args);
419
- };
420
- exports.cleanupTempFile = function (filePath) {
421
- try {
422
- fs_1.unlinkSync(filePath);
423
- }
424
- catch (_a) { }
425
- };
426
- 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;
427
435
  //# sourceMappingURL=video.js.map