nodejs-audio-visualizer 4.3.7 → 4.3.9

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