nodejs-audio-visualizer 4.2.4 → 4.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/video.js CHANGED
@@ -1,294 +1,318 @@
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.spawnFfmpegVideoWriter = function (config) {
19
- var _a, _b, _c, _d;
20
- if (!ffmpeg_static_1.default) {
21
- throw new Error('ffmpeg path not found');
22
- }
23
- var crf = config.crf || '23';
24
- var preset = config.preset || 'medium';
25
- var args = ['-y'];
26
- var seek = (_b = (_a = config.audioSegment) === null || _a === void 0 ? void 0 : _a.seekSeconds, (_b !== null && _b !== void 0 ? _b : config.audioSeekSeconds));
27
- var duration = (_d = (_c = config.audioSegment) === null || _c === void 0 ? void 0 : _c.durationSeconds, (_d !== null && _d !== void 0 ? _d : config.audioDurationSeconds));
28
- if (seek !== undefined && duration !== undefined) {
29
- args.push('-ss', String(seek), '-t', String(duration));
30
- }
31
- 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, config.videoFileName, '-r', "" + config.fps, '-i', '-');
32
- var ffmpeg = child_process_1.spawn(ffmpeg_static_1.default, args);
33
- ffmpeg.stdin.pipe(process.stdout);
34
- if (config.onStderr) {
35
- ffmpeg.stderr.on('data', config.onStderr);
36
- }
37
- return ffmpeg;
38
- };
39
- exports.detectSceneChanges = function (videoPath, threshold) {
40
- if (threshold === void 0) { threshold = 0.4; }
41
- return new Promise(function (resolve, reject) {
42
- if (!ffmpeg_static_1.default) {
43
- reject(new Error('ffmpeg path not found'));
44
- return;
45
- }
46
- var ffmpeg = child_process_1.spawn(ffmpeg_static_1.default, [
47
- '-i', videoPath,
48
- '-filter:v', "select='gt(scene," + threshold + ")',showinfo",
49
- '-f', 'null',
50
- '-'
51
- ]);
52
- var stderr = '';
53
- ffmpeg.stderr.on('data', function (data) { stderr += data.toString(); });
54
- ffmpeg.on('close', function () {
55
- var sceneChanges = [];
56
- for (var _i = 0, _a = stderr.split('\n'); _i < _a.length; _i++) {
57
- var line = _a[_i];
58
- if (!line.includes('Parsed_showinfo'))
59
- continue;
60
- var nMatch = line.match(/n:\s*(\d+)/);
61
- var ptsMatch = line.match(/pts:\s*(\d+)/);
62
- var ptsTimeMatch = line.match(/pts_time:\s*([\d.]+)/);
63
- if (nMatch && ptsTimeMatch) {
64
- sceneChanges.push({
65
- frameNumber: parseInt(nMatch[1]),
66
- pts: ptsMatch ? parseInt(ptsMatch[1]) : 0,
67
- ptsTime: parseFloat(ptsTimeMatch[1]),
68
- });
69
- }
70
- }
71
- resolve(sceneChanges);
72
- });
73
- ffmpeg.on('error', reject);
74
- });
75
- };
76
- exports.getVideoInfo = function (videoPath) {
77
- return new Promise(function (resolvePromise, reject) {
78
- if (!ffmpeg_static_1.default) {
79
- reject(new Error('ffmpeg path not found'));
80
- return;
81
- }
82
- var ffmpeg = child_process_1.spawn(ffmpeg_static_1.default, ['-i', videoPath, '-hide_banner']);
83
- var stderr = '';
84
- ffmpeg.stderr.on('data', function (data) { stderr += data.toString(); });
85
- ffmpeg.on('close', function () {
86
- var dimMatch = stderr.match(/Stream.*Video:.*?(\d{2,5})x(\d{2,5})/);
87
- if (!dimMatch) {
88
- reject(new Error("Could not determine video dimensions for: " + videoPath));
89
- return;
90
- }
91
- var durMatch = stderr.match(/Duration:\s*(\d+):(\d+):([\d.]+)/);
92
- var duration = durMatch
93
- ? parseInt(durMatch[1]) * 3600 + parseInt(durMatch[2]) * 60 + parseFloat(durMatch[3])
94
- : 0;
95
- resolvePromise({
96
- width: parseInt(dimMatch[1]),
97
- height: parseInt(dimMatch[2]),
98
- duration: duration,
99
- });
100
- });
101
- });
102
- };
103
- var isOrientationMismatch = function (sw, sh, tw, th) {
104
- return (sw > sh && tw < th) || (sw < sh && tw > th);
105
- };
106
- exports.buildVideoFilter = function (sourceWidth, sourceHeight, targetWidth, targetHeight) {
107
- if (!isOrientationMismatch(sourceWidth, sourceHeight, targetWidth, targetHeight)) {
108
- return "scale=" + targetWidth + ":" + targetHeight;
109
- }
110
- var targetAspect = targetWidth / targetHeight;
111
- var makeEven = function (n) { return Math.floor(n / 2) * 2; };
112
- var cropW, cropH, cropX, cropY;
113
- if (sourceWidth / sourceHeight > targetAspect) {
114
- cropW = makeEven(Math.round(sourceHeight * targetAspect));
115
- cropH = makeEven(sourceHeight);
116
- cropX = Math.floor((sourceWidth - cropW) / 2);
117
- cropY = Math.floor((sourceHeight - cropH) / 2);
118
- }
119
- else {
120
- cropW = makeEven(sourceWidth);
121
- cropH = makeEven(Math.round(sourceWidth / targetAspect));
122
- cropX = Math.floor((sourceWidth - cropW) / 2);
123
- cropY = Math.floor((sourceHeight - cropH) / 2);
124
- }
125
- return "crop=" + cropW + ":" + cropH + ":" + cropX + ":" + cropY + ",scale=" + targetWidth + ":" + targetHeight;
126
- };
127
- exports.spawnVideoFrameReader = function (config) {
128
- if (!ffmpeg_static_1.default) {
129
- throw new Error('ffmpeg path not found');
130
- }
131
- var args = [
132
- '-stream_loop', '-1',
133
- '-i', config.videoPath,
134
- '-r', "" + config.fps,
135
- '-frames:v', "" + config.totalFrames,
136
- ];
137
- if (config.width && config.height) {
138
- var filter = config.sourceWidth && config.sourceHeight
139
- ? exports.buildVideoFilter(config.sourceWidth, config.sourceHeight, config.width, config.height)
140
- : "scale=" + config.width + ":" + config.height;
141
- args.push('-vf', filter);
142
- }
143
- args.push('-f', 'rawvideo', '-pix_fmt', 'bgr24', '-v', 'quiet', 'pipe:1');
144
- return child_process_1.spawn(ffmpeg_static_1.default, args);
145
- };
146
- exports.readVideoFrame = function (stream, frameSize) {
147
- return new Promise(function (resolve) {
148
- var tryRead = function () {
149
- var data = stream.read(frameSize);
150
- if (data !== null) {
151
- resolve(data.length === frameSize ? data : null);
152
- return;
153
- }
154
- var onReadable = function () {
155
- cleanup();
156
- tryRead();
157
- };
158
- var onEnd = function () {
159
- cleanup();
160
- resolve(null);
161
- };
162
- var cleanup = function () {
163
- stream.removeListener('readable', onReadable);
164
- stream.removeListener('end', onEnd);
165
- };
166
- stream.once('readable', onReadable);
167
- stream.once('end', onEnd);
168
- };
169
- tryRead();
170
- });
171
- };
172
- exports.getProgress = function (onProgress) {
173
- return function (stderrOutput) {
174
- var matchResult = stderrOutput.toString().match(/frame=[ ]+(\d+)/);
175
- if (!matchResult) {
176
- return;
177
- }
178
- var currentFrame = +matchResult[1];
179
- if (isNaN(currentFrame)) {
180
- return;
181
- }
182
- onProgress(currentFrame);
183
- };
184
- };
185
- exports.calculateProgress = function (framesCount, callback) {
186
- return function (currentFrame) {
187
- return callback(+(currentFrame / framesCount * 100).toFixed(2));
188
- };
189
- };
190
- exports.waitDrain = function (stream) {
191
- return new Promise(function (resolve) { return stream.once('drain', resolve); });
192
- };
193
- var mergeSmallScenes = function (sceneChanges, videoDuration, minDuration) {
194
- if (minDuration === void 0) { minDuration = 2; }
195
- if (sceneChanges.length === 0)
196
- return [];
197
- var merged = [sceneChanges[0]];
198
- for (var i = 1; i < sceneChanges.length; i++) {
199
- var lastKept = merged[merged.length - 1];
200
- var gap = sceneChanges[i].ptsTime - lastKept.ptsTime;
201
- if (gap >= minDuration) {
202
- merged.push(sceneChanges[i]);
203
- }
204
- }
205
- var lastScene = merged[merged.length - 1];
206
- if (videoDuration - lastScene.ptsTime < minDuration && merged.length > 1) {
207
- merged.pop();
208
- }
209
- return merged;
210
- };
211
- exports.buildBeatSyncedSegments = function (beatFrameIndices, totalFrames, sceneChanges, videoDuration) {
212
- if (sceneChanges.length === 0) {
213
- return [
214
- {
215
- outputStartFrame: 0,
216
- videoSeekSeconds: 0,
217
- frameCount: totalFrames,
218
- },
219
- ];
220
- }
221
- var consolidated = mergeSmallScenes(sceneChanges, videoDuration);
222
- var seekPositions;
223
- if (consolidated.length >= 2) {
224
- seekPositions = consolidated.map(function (sc) { return sc.ptsTime; });
225
- }
226
- else {
227
- var count_1 = Math.max(10, Math.ceil(videoDuration / 3));
228
- seekPositions = Array.from({ length: count_1 }, function (_, i) { return (i * videoDuration) / count_1; });
229
- }
230
- var boundaries = __spreadArrays([0], beatFrameIndices);
231
- if (boundaries[boundaries.length - 1] !== totalFrames) {
232
- boundaries.push(totalFrames);
233
- }
234
- var unique = Array.from(new Set(boundaries)).sort(function (a, b) { return a - b; });
235
- var segments = [];
236
- for (var i = 0; i < unique.length - 1; i++) {
237
- segments.push({
238
- outputStartFrame: unique[i],
239
- videoSeekSeconds: seekPositions[i % seekPositions.length],
240
- frameCount: unique[i + 1] - unique[i],
241
- });
242
- }
243
- return segments;
244
- };
245
- exports.writeConcatFile = function (segments, videoPath, videoDuration, fps) {
246
- var absVideoPath = path_1.resolve(videoPath).replace(/\\/g, '/');
247
- var escaped = absVideoPath.replace(/'/g, "'\\''");
248
- var content = '';
249
- for (var _i = 0, segments_1 = segments; _i < segments_1.length; _i++) {
250
- var seg = segments_1[_i];
251
- var segDuration = seg.frameCount / fps;
252
- var remaining = segDuration;
253
- var pos = seg.videoSeekSeconds % videoDuration;
254
- while (remaining > 0.001) {
255
- var available = videoDuration - pos;
256
- var take = Math.min(remaining, available);
257
- content += "file '" + escaped + "'\n";
258
- content += "inpoint " + pos.toFixed(6) + "\n";
259
- content += "outpoint " + (pos + take).toFixed(6) + "\n\n";
260
- remaining -= take;
261
- pos = 0;
262
- }
263
- }
264
- var concatPath = path_1.join(os_1.tmpdir(), "av-concat-" + Date.now() + ".txt");
265
- fs_1.writeFileSync(concatPath, content, 'utf-8');
266
- return concatPath;
267
- };
268
- exports.spawnConcatVideoFrameReader = function (config) {
269
- if (!ffmpeg_static_1.default) {
270
- throw new Error('ffmpeg path not found');
271
- }
272
- var args = [
273
- '-f', 'concat',
274
- '-safe', '0',
275
- '-i', config.concatFilePath,
276
- '-r', "" + config.fps,
277
- '-frames:v', "" + config.totalFrames,
278
- ];
279
- if (config.width && config.height) {
280
- var filter = config.sourceWidth && config.sourceHeight
281
- ? exports.buildVideoFilter(config.sourceWidth, config.sourceHeight, config.width, config.height)
282
- : "scale=" + config.width + ":" + config.height;
283
- args.push('-vf', filter);
284
- }
285
- args.push('-f', 'rawvideo', '-pix_fmt', 'bgr24', '-v', 'quiet', 'pipe:1');
286
- return child_process_1.spawn(ffmpeg_static_1.default, args);
287
- };
288
- exports.cleanupConcatFile = function (filePath) {
289
- try {
290
- fs_1.unlinkSync(filePath);
291
- }
292
- catch (_a) { }
293
- };
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.spawnFfmpegVideoWriter = function (config) {
19
+ var _a, _b, _c, _d;
20
+ if (!ffmpeg_static_1.default) {
21
+ throw new Error('ffmpeg path not found');
22
+ }
23
+ var crf = config.crf || '23';
24
+ var preset = config.preset || 'medium';
25
+ var args = ['-y'];
26
+ var seek = (_b = (_a = config.audioSegment) === null || _a === void 0 ? void 0 : _a.seekSeconds, (_b !== null && _b !== void 0 ? _b : config.audioSeekSeconds));
27
+ var duration = (_d = (_c = config.audioSegment) === null || _c === void 0 ? void 0 : _c.durationSeconds, (_d !== null && _d !== void 0 ? _d : config.audioDurationSeconds));
28
+ if (seek !== undefined && duration !== undefined) {
29
+ args.push('-ss', String(seek), '-t', String(duration));
30
+ }
31
+ 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, config.videoFileName, '-r', "" + config.fps, '-i', '-');
32
+ var ffmpeg = child_process_1.spawn(ffmpeg_static_1.default, args);
33
+ if (config.onStderr) {
34
+ ffmpeg.stderr.on('data', config.onStderr);
35
+ }
36
+ return ffmpeg;
37
+ };
38
+ exports.detectSceneChanges = function (videoPath, threshold) {
39
+ if (threshold === void 0) { threshold = 0.4; }
40
+ return new Promise(function (resolve, reject) {
41
+ if (!ffmpeg_static_1.default) {
42
+ reject(new Error('ffmpeg path not found'));
43
+ return;
44
+ }
45
+ var ffmpeg = child_process_1.spawn(ffmpeg_static_1.default, [
46
+ '-i', videoPath,
47
+ '-filter:v', "select='gt(scene," + threshold + ")',showinfo",
48
+ '-f', 'null',
49
+ '-'
50
+ ]);
51
+ var stderr = '';
52
+ ffmpeg.stderr.on('data', function (data) { stderr += data.toString(); });
53
+ ffmpeg.on('close', function () {
54
+ var sceneChanges = [];
55
+ for (var _i = 0, _a = stderr.split('\n'); _i < _a.length; _i++) {
56
+ var line = _a[_i];
57
+ if (!line.includes('Parsed_showinfo'))
58
+ continue;
59
+ var nMatch = line.match(/n:\s*(\d+)/);
60
+ var ptsMatch = line.match(/pts:\s*(\d+)/);
61
+ var ptsTimeMatch = line.match(/pts_time:\s*([\d.]+)/);
62
+ if (nMatch && ptsTimeMatch) {
63
+ sceneChanges.push({
64
+ frameNumber: parseInt(nMatch[1]),
65
+ pts: ptsMatch ? parseInt(ptsMatch[1]) : 0,
66
+ ptsTime: parseFloat(ptsTimeMatch[1]),
67
+ });
68
+ }
69
+ }
70
+ resolve(sceneChanges);
71
+ });
72
+ ffmpeg.on('error', reject);
73
+ });
74
+ };
75
+ exports.getVideoInfo = function (videoPath) {
76
+ return new Promise(function (resolvePromise, reject) {
77
+ if (!ffmpeg_static_1.default) {
78
+ reject(new Error('ffmpeg path not found'));
79
+ return;
80
+ }
81
+ var ffmpeg = child_process_1.spawn(ffmpeg_static_1.default, ['-i', videoPath, '-hide_banner']);
82
+ var stderr = '';
83
+ ffmpeg.stderr.on('data', function (data) { stderr += data.toString(); });
84
+ ffmpeg.on('close', function () {
85
+ var dimMatch = stderr.match(/Stream.*Video:.*?(\d{2,5})x(\d{2,5})/);
86
+ if (!dimMatch) {
87
+ reject(new Error("Could not determine video dimensions for: " + videoPath));
88
+ return;
89
+ }
90
+ var durMatch = stderr.match(/Duration:\s*(\d+):(\d+):([\d.]+)/);
91
+ var duration = durMatch
92
+ ? parseInt(durMatch[1]) * 3600 + parseInt(durMatch[2]) * 60 + parseFloat(durMatch[3])
93
+ : 0;
94
+ resolvePromise({
95
+ width: parseInt(dimMatch[1]),
96
+ height: parseInt(dimMatch[2]),
97
+ duration: duration,
98
+ });
99
+ });
100
+ });
101
+ };
102
+ var isOrientationMismatch = function (sw, sh, tw, th) {
103
+ return (sw > sh && tw < th) || (sw < sh && tw > th);
104
+ };
105
+ exports.buildVideoFilter = function (sourceWidth, sourceHeight, targetWidth, targetHeight) {
106
+ if (!isOrientationMismatch(sourceWidth, sourceHeight, targetWidth, targetHeight)) {
107
+ return "scale=" + targetWidth + ":" + targetHeight;
108
+ }
109
+ var targetAspect = targetWidth / targetHeight;
110
+ var makeEven = function (n) { return Math.floor(n / 2) * 2; };
111
+ var cropW, cropH, cropX, cropY;
112
+ if (sourceWidth / sourceHeight > targetAspect) {
113
+ cropW = makeEven(Math.round(sourceHeight * targetAspect));
114
+ cropH = makeEven(sourceHeight);
115
+ cropX = Math.floor((sourceWidth - cropW) / 2);
116
+ cropY = Math.floor((sourceHeight - cropH) / 2);
117
+ }
118
+ else {
119
+ cropW = makeEven(sourceWidth);
120
+ cropH = makeEven(Math.round(sourceWidth / targetAspect));
121
+ cropX = Math.floor((sourceWidth - cropW) / 2);
122
+ cropY = Math.floor((sourceHeight - cropH) / 2);
123
+ }
124
+ return "crop=" + cropW + ":" + cropH + ":" + cropX + ":" + cropY + ",scale=" + targetWidth + ":" + targetHeight;
125
+ };
126
+ exports.spawnVideoFrameReader = function (config) {
127
+ if (!ffmpeg_static_1.default) {
128
+ throw new Error('ffmpeg path not found');
129
+ }
130
+ var args = [
131
+ '-stream_loop', '-1',
132
+ '-i', config.videoPath,
133
+ '-r', "" + config.fps,
134
+ '-frames:v', "" + config.totalFrames,
135
+ ];
136
+ if (config.width && config.height) {
137
+ var filter = config.sourceWidth && config.sourceHeight
138
+ ? exports.buildVideoFilter(config.sourceWidth, config.sourceHeight, config.width, config.height)
139
+ : "scale=" + config.width + ":" + config.height;
140
+ args.push('-vf', filter);
141
+ }
142
+ args.push('-f', 'rawvideo', '-pix_fmt', 'bgr24', '-v', 'quiet', 'pipe:1');
143
+ return child_process_1.spawn(ffmpeg_static_1.default, args);
144
+ };
145
+ exports.readVideoFrame = function (stream, frameSize) {
146
+ return new Promise(function (resolve) {
147
+ var tryRead = function () {
148
+ var data = stream.read(frameSize);
149
+ if (data !== null) {
150
+ resolve(data.length === frameSize ? data : null);
151
+ return;
152
+ }
153
+ var onReadable = function () {
154
+ cleanup();
155
+ tryRead();
156
+ };
157
+ var onEnd = function () {
158
+ cleanup();
159
+ resolve(null);
160
+ };
161
+ var cleanup = function () {
162
+ stream.removeListener('readable', onReadable);
163
+ stream.removeListener('end', onEnd);
164
+ };
165
+ stream.once('readable', onReadable);
166
+ stream.once('end', onEnd);
167
+ };
168
+ tryRead();
169
+ });
170
+ };
171
+ exports.getProgress = function (onProgress) {
172
+ return function (stderrOutput) {
173
+ var matchResult = stderrOutput.toString().match(/frame=[ ]+(\d+)/);
174
+ if (!matchResult) {
175
+ return;
176
+ }
177
+ var currentFrame = +matchResult[1];
178
+ if (isNaN(currentFrame)) {
179
+ return;
180
+ }
181
+ onProgress(currentFrame);
182
+ };
183
+ };
184
+ exports.calculateProgress = function (framesCount, callback) {
185
+ return function (currentFrame) {
186
+ return callback(+(currentFrame / framesCount * 100).toFixed(2));
187
+ };
188
+ };
189
+ exports.waitDrain = function (stream, processStream) {
190
+ return new Promise(function (resolve) {
191
+ var _a, _b, _c;
192
+ var cleanup = function () {
193
+ var _a, _b, _c;
194
+ stream.removeListener('drain', onDrain);
195
+ stream.removeListener('error', onErrorOrClose);
196
+ stream.removeListener('close', onErrorOrClose);
197
+ (_a = processStream) === null || _a === void 0 ? void 0 : _a.removeListener('error', onErrorOrClose);
198
+ (_b = processStream) === null || _b === void 0 ? void 0 : _b.removeListener('exit', onErrorOrClose);
199
+ (_c = processStream) === null || _c === void 0 ? void 0 : _c.removeListener('close', onErrorOrClose);
200
+ };
201
+ var onDrain = function () {
202
+ cleanup();
203
+ resolve(true);
204
+ };
205
+ var onErrorOrClose = function () {
206
+ cleanup();
207
+ resolve(false);
208
+ };
209
+ stream.once('drain', onDrain);
210
+ stream.once('error', onErrorOrClose);
211
+ stream.once('close', onErrorOrClose);
212
+ (_a = processStream) === null || _a === void 0 ? void 0 : _a.once('error', onErrorOrClose);
213
+ (_b = processStream) === null || _b === void 0 ? void 0 : _b.once('exit', onErrorOrClose);
214
+ (_c = processStream) === null || _c === void 0 ? void 0 : _c.once('close', onErrorOrClose);
215
+ });
216
+ };
217
+ var mergeSmallScenes = function (sceneChanges, videoDuration, minDuration) {
218
+ if (minDuration === void 0) { minDuration = 2; }
219
+ if (sceneChanges.length === 0)
220
+ return [];
221
+ var merged = [sceneChanges[0]];
222
+ for (var i = 1; i < sceneChanges.length; i++) {
223
+ var lastKept = merged[merged.length - 1];
224
+ var gap = sceneChanges[i].ptsTime - lastKept.ptsTime;
225
+ if (gap >= minDuration) {
226
+ merged.push(sceneChanges[i]);
227
+ }
228
+ }
229
+ var lastScene = merged[merged.length - 1];
230
+ if (videoDuration - lastScene.ptsTime < minDuration && merged.length > 1) {
231
+ merged.pop();
232
+ }
233
+ return merged;
234
+ };
235
+ exports.buildBeatSyncedSegments = function (beatFrameIndices, totalFrames, sceneChanges, videoDuration) {
236
+ if (sceneChanges.length === 0) {
237
+ return [
238
+ {
239
+ outputStartFrame: 0,
240
+ videoSeekSeconds: 0,
241
+ frameCount: totalFrames,
242
+ },
243
+ ];
244
+ }
245
+ var consolidated = mergeSmallScenes(sceneChanges, videoDuration);
246
+ var seekPositions;
247
+ if (consolidated.length >= 2) {
248
+ seekPositions = consolidated.map(function (sc) { return sc.ptsTime; });
249
+ }
250
+ else {
251
+ var count_1 = Math.max(10, Math.ceil(videoDuration / 3));
252
+ seekPositions = Array.from({ length: count_1 }, function (_, i) { return (i * videoDuration) / count_1; });
253
+ }
254
+ var boundaries = __spreadArrays([0], beatFrameIndices);
255
+ if (boundaries[boundaries.length - 1] !== totalFrames) {
256
+ boundaries.push(totalFrames);
257
+ }
258
+ var unique = Array.from(new Set(boundaries)).sort(function (a, b) { return a - b; });
259
+ var segments = [];
260
+ for (var i = 0; i < unique.length - 1; i++) {
261
+ segments.push({
262
+ outputStartFrame: unique[i],
263
+ videoSeekSeconds: seekPositions[i % seekPositions.length],
264
+ frameCount: unique[i + 1] - unique[i],
265
+ });
266
+ }
267
+ return segments;
268
+ };
269
+ exports.writeConcatFile = function (segments, videoPath, videoDuration, fps) {
270
+ var absVideoPath = path_1.resolve(videoPath).replace(/\\/g, '/');
271
+ var escaped = absVideoPath.replace(/'/g, "'\\''");
272
+ var content = '';
273
+ for (var _i = 0, segments_1 = segments; _i < segments_1.length; _i++) {
274
+ var seg = segments_1[_i];
275
+ var segDuration = seg.frameCount / fps;
276
+ var remaining = segDuration;
277
+ var pos = seg.videoSeekSeconds % videoDuration;
278
+ while (remaining > 0.001) {
279
+ var available = videoDuration - pos;
280
+ var take = Math.min(remaining, available);
281
+ content += "file '" + escaped + "'\n";
282
+ content += "inpoint " + pos.toFixed(6) + "\n";
283
+ content += "outpoint " + (pos + take).toFixed(6) + "\n\n";
284
+ remaining -= take;
285
+ pos = 0;
286
+ }
287
+ }
288
+ var concatPath = path_1.join(os_1.tmpdir(), "av-concat-" + Date.now() + ".txt");
289
+ fs_1.writeFileSync(concatPath, content, 'utf-8');
290
+ return concatPath;
291
+ };
292
+ exports.spawnConcatVideoFrameReader = function (config) {
293
+ if (!ffmpeg_static_1.default) {
294
+ throw new Error('ffmpeg path not found');
295
+ }
296
+ var args = [
297
+ '-f', 'concat',
298
+ '-safe', '0',
299
+ '-i', config.concatFilePath,
300
+ '-r', "" + config.fps,
301
+ '-frames:v', "" + config.totalFrames,
302
+ ];
303
+ if (config.width && config.height) {
304
+ var filter = config.sourceWidth && config.sourceHeight
305
+ ? exports.buildVideoFilter(config.sourceWidth, config.sourceHeight, config.width, config.height)
306
+ : "scale=" + config.width + ":" + config.height;
307
+ args.push('-vf', filter);
308
+ }
309
+ args.push('-f', 'rawvideo', '-pix_fmt', 'bgr24', '-v', 'quiet', 'pipe:1');
310
+ return child_process_1.spawn(ffmpeg_static_1.default, args);
311
+ };
312
+ exports.cleanupConcatFile = function (filePath) {
313
+ try {
314
+ fs_1.unlinkSync(filePath);
315
+ }
316
+ catch (_a) { }
317
+ };
294
318
  //# sourceMappingURL=video.js.map