nodejs-audio-visualizer 4.5.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) 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 +534 -531
  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 +80 -80
  23. package/dist/video.js +434 -426
  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 +476 -476
  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 +718 -713
  36. package/src/subtitleConvert.ts +63 -63
  37. package/src/tests/audio.test.ts +96 -96
  38. package/src/tests/config.test.ts +496 -496
  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 +180 -112
  43. package/src/tests/subtitleConvert.test.ts +23 -23
  44. package/src/tests/video.test.ts +285 -216
  45. package/src/typings.d.ts +6 -6
  46. package/src/video.ts +550 -542
  47. package/src/waitForEventLoop.ts +2 -2
package/dist/index.js CHANGED
@@ -1,532 +1,535 @@
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 __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
14
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
15
- return new (P || (P = Promise))(function (resolve, reject) {
16
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
17
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
18
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
19
- step((generator = generator.apply(thisArg, _arguments || [])).next());
20
- });
21
- };
22
- var __generator = (this && this.__generator) || function (thisArg, body) {
23
- var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
24
- return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
25
- function verb(n) { return function (v) { return step([n, v]); }; }
26
- function step(op) {
27
- if (f) throw new TypeError("Generator is already executing.");
28
- while (_) try {
29
- if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
30
- if (y = 0, t) op = [op[0] & 2, t.value];
31
- switch (op[0]) {
32
- case 0: case 1: t = op; break;
33
- case 4: _.label++; return { value: op[1], done: false };
34
- case 5: _.label++; y = op[1]; op = [0]; continue;
35
- case 7: op = _.ops.pop(); _.trys.pop(); continue;
36
- default:
37
- if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
38
- if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
39
- if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
40
- if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
41
- if (t[2]) _.ops.pop();
42
- _.trys.pop(); continue;
43
- }
44
- op = body.call(thisArg, _);
45
- } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
46
- if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
47
- }
48
- };
49
- var __importDefault = (this && this.__importDefault) || function (mod) {
50
- return (mod && mod.__esModule) ? mod : { "default": mod };
51
- };
52
- Object.defineProperty(exports, "__esModule", { value: true });
53
- var path_1 = __importDefault(require("path"));
54
- var fs_1 = require("fs");
55
- var config_1 = require("./config");
56
- var audio_1 = require("./audio");
57
- var image_1 = require("./image");
58
- var subtitleConvert_1 = require("./subtitleConvert");
59
- var video_1 = require("./video");
60
- var bpmEncoder_1 = require("./bpmEncoder");
61
- var beats_1 = require("./beats");
62
- var highlight_1 = require("./highlight");
63
- var waitForEventLoop_1 = require("./waitForEventLoop");
64
- var highlight_2 = require("./highlight");
65
- exports.computeHighlightSlice = highlight_2.computeHighlightSlice;
66
- exports.HIGHLIGHT_DURATION_SEC = highlight_2.HIGHLIGHT_DURATION_SEC;
67
- exports.PCM_FORMAT = {
68
- bit: 8,
69
- sign: 'u',
70
- parseFunction: audio_1.bufferToUInt8
71
- };
72
- var FFMPEG_FORMAT = "" + exports.PCM_FORMAT.sign + exports.PCM_FORMAT.bit;
73
- var PROCESSING_BUFFER_SIZE = Math.pow(2, 12);
74
- var sleep = function (timeout) {
75
- return new Promise(function (resolve) { return setTimeout(resolve, timeout); });
76
- };
77
- var createVisualizerFrameGenerator = function (config, backgroundWidth, backgroundHeight, defaultColor, spectrumBusMargin) {
78
- if (!config.outVideo.spectrum) {
79
- var createPolarVisualizerFrame_1 = image_1.createPolarVisualizerFrameGenerator();
80
- var polarX_1 = config_1.getPolarXAbsolute(config, backgroundWidth);
81
- var polarY_1 = config_1.getPolarYAbsolute(config, backgroundHeight);
82
- var polarInnerRadius_1 = config_1.getPolarInnerRadius(config);
83
- var polarMaxBarLength_1 = config_1.getPolarMaxBarLength(config);
84
- var polarBarWidth_1 = config_1.getPolarBarWidth(config);
85
- var polarColor_1 = config_1.getPolarColor(config) || image_1.invertColor(defaultColor);
86
- var polarEffect_1 = config_1.getPolarEffect(config);
87
- var polarOpacity_1 = config_1.getPolarOpacityParsed(config);
88
- return function (params) {
89
- return createPolarVisualizerFrame_1(__assign(__assign({}, params), { centerX: polarX_1, centerY: polarY_1, innerRadius: polarInnerRadius_1, maxBarLength: polarMaxBarLength_1, barWidth: polarBarWidth_1, color: polarColor_1, opacity: polarOpacity_1, spectrumEffect: polarEffect_1 }));
90
- };
91
- }
92
- var createSpectrumVisualizerFrame = image_1.createSpectrumVisualizerFrameGenerator();
93
- var spectrumWidth = config_1.getSpectrumWidthAbsolute(config, backgroundWidth);
94
- var spectrumHeight = config_1.getSpectrumHeightAbsolute(config, backgroundHeight);
95
- var spectrumX = config_1.getSpectrumXAbsolute(config, spectrumWidth, backgroundWidth);
96
- var spectrumY = config_1.getSpectrumYAbsolute(config, spectrumHeight, backgroundHeight);
97
- var spectrumRotation = config_1.getSpectrumRotation(config);
98
- var spectrumColor = config_1.getSpectrumColor(config) || image_1.invertColor(defaultColor);
99
- var spectrumEffect = config_1.getSpectrumEffect(config);
100
- var spectrumOpacity = config_1.getSpectrumOpacityParsed(config);
101
- return function (params) {
102
- return createSpectrumVisualizerFrame(__assign(__assign({}, params), { size: { width: spectrumWidth, height: spectrumHeight }, position: { x: spectrumX, y: spectrumY }, rotation: spectrumRotation, margin: spectrumBusMargin, color: spectrumColor, opacity: spectrumOpacity, spectrumEffect: spectrumEffect }));
103
- };
104
- };
105
- var PRE_PROCESS_PROGRESS_SHARE = 60;
106
- var POST_AUDIO_PROGRESS_SHARE = 10;
107
- var RENDER_PROGRESS_SHARE = 30;
108
- var preProcessAudio = function (audioBuffer, sampleRate, fps, framesCount, onPreProcessProgress) { return __awaiter(void 0, void 0, void 0, function () {
109
- var t, audioDataStep, processingBuffer, skipFramesCount, processSpectrum, detectBeat, spectrums, beatFrameIndices, beatEvents, nextPreProcessMilestone, i, currentFrameData, frameDataLength, frameDataTailStart, frameDataToProcess, audioDataParser, spectrum, beat, p;
110
- var _a, _b;
111
- return __generator(this, function (_c) {
112
- switch (_c.label) {
113
- case 0:
114
- if (framesCount === 0) {
115
- for (t = 1; t <= PRE_PROCESS_PROGRESS_SHARE; t += 1) {
116
- (_a = onPreProcessProgress) === null || _a === void 0 ? void 0 : _a(t);
117
- }
118
- return [2 /*return*/, { spectrums: [], beatFrameIndices: [], beatEvents: [] }];
119
- }
120
- audioDataStep = Math.trunc(audioBuffer.length / framesCount);
121
- processingBuffer = new Float32Array(PROCESSING_BUFFER_SIZE).fill(0);
122
- skipFramesCount = fps < 45 ? 1 : 2;
123
- processSpectrum = audio_1.createSpectrumsProcessor(sampleRate, skipFramesCount);
124
- detectBeat = beats_1.createBeatDetector(fps);
125
- spectrums = [];
126
- beatFrameIndices = [];
127
- beatEvents = [];
128
- nextPreProcessMilestone = 1;
129
- i = 0;
130
- _c.label = 1;
131
- case 1:
132
- if (!(i < framesCount)) return [3 /*break*/, 4];
133
- currentFrameData = exports.PCM_FORMAT.parseFunction(audioBuffer, i * audioDataStep, i * audioDataStep + audioDataStep);
134
- frameDataLength = Math.min(currentFrameData.length, PROCESSING_BUFFER_SIZE);
135
- frameDataTailStart = currentFrameData.length - frameDataLength;
136
- frameDataToProcess = currentFrameData.slice(frameDataTailStart);
137
- processingBuffer.copyWithin(0, frameDataLength);
138
- processingBuffer.set(frameDataToProcess, PROCESSING_BUFFER_SIZE - frameDataLength);
139
- audioDataParser = function () { return Array.from(processingBuffer); };
140
- spectrum = processSpectrum(audioDataParser);
141
- beat = detectBeat(spectrum);
142
- spectrums.push(spectrum);
143
- if (beat.isBeat) {
144
- beatFrameIndices.push(i);
145
- beatEvents.push({ frameIndex: i, intensity: beat.intensity });
146
- }
147
- p = PRE_PROCESS_PROGRESS_SHARE * (i + 1) / framesCount;
148
- while (nextPreProcessMilestone <= PRE_PROCESS_PROGRESS_SHARE
149
- && p >= nextPreProcessMilestone) {
150
- (_b = onPreProcessProgress) === null || _b === void 0 ? void 0 : _b(nextPreProcessMilestone);
151
- nextPreProcessMilestone += 1;
152
- }
153
- return [4 /*yield*/, waitForEventLoop_1.waitForEventLoop()];
154
- case 2:
155
- _c.sent();
156
- _c.label = 3;
157
- case 3:
158
- i++;
159
- return [3 /*break*/, 1];
160
- case 4: return [2 /*return*/, { spectrums: spectrums, beatFrameIndices: beatFrameIndices, beatEvents: beatEvents }];
161
- }
162
- });
163
- }); };
164
- function prepareBackgroundForRender(params) {
165
- var _a, _b, _c, _d, _e, _f;
166
- return __awaiter(this, void 0, void 0, function () {
167
- var useVideoBackground, backgroundVideoPath, backgroundImagePath, beatFrameIndices, framesCount, outputResolution, fps, autoEditVideo, readVideoFrameTimeout, videoInfo, sceneChanges, _g, backgroundWidth_1, backgroundHeight_1, videoFrameSize, encodeVideoFrame, segments, concatFilePath, videoFrameReader, firstFrame, defaultColor_1, staticBackgroundBuffer_1, backgroundImageBmpBuffer, backgroundImage, backgroundWidth, backgroundHeight, defaultColor, bpmEncoder, staticBackgroundBuffer;
168
- return __generator(this, function (_h) {
169
- switch (_h.label) {
170
- case 0:
171
- useVideoBackground = params.useVideoBackground, backgroundVideoPath = params.backgroundVideoPath, backgroundImagePath = params.backgroundImagePath, beatFrameIndices = params.beatFrameIndices, framesCount = params.framesCount, outputResolution = params.outputResolution, fps = params.fps, autoEditVideo = params.autoEditVideo, readVideoFrameTimeout = params.readVideoFrameTimeout;
172
- if (!(useVideoBackground && backgroundVideoPath)) return [3 /*break*/, 6];
173
- return [4 /*yield*/, video_1.getVideoInfo(backgroundVideoPath)];
174
- case 1:
175
- videoInfo = _h.sent();
176
- if (!autoEditVideo) return [3 /*break*/, 3];
177
- return [4 /*yield*/, video_1.detectSceneChanges(backgroundVideoPath)];
178
- case 2:
179
- _g = _h.sent();
180
- return [3 /*break*/, 4];
181
- case 3:
182
- _g = [];
183
- _h.label = 4;
184
- case 4:
185
- sceneChanges = _g;
186
- backgroundWidth_1 = (_b = (_a = outputResolution) === null || _a === void 0 ? void 0 : _a.width, (_b !== null && _b !== void 0 ? _b : videoInfo.width));
187
- backgroundHeight_1 = (_d = (_c = outputResolution) === null || _c === void 0 ? void 0 : _c.height, (_d !== null && _d !== void 0 ? _d : videoInfo.height));
188
- videoFrameSize = backgroundWidth_1 * backgroundHeight_1 * 3;
189
- encodeVideoFrame = bpmEncoder_1.createBgrFrameEncoder({ width: backgroundWidth_1, height: backgroundHeight_1 });
190
- segments = video_1.buildBeatSyncedSegments(beatFrameIndices, framesCount, sceneChanges, videoInfo.duration);
191
- concatFilePath = video_1.writeConcatFile(segments, backgroundVideoPath, videoInfo.duration, fps);
192
- videoFrameReader = video_1.spawnConcatVideoFrameReader(__assign({ concatFilePath: concatFilePath,
193
- fps: fps, totalFrames: framesCount }, (outputResolution && {
194
- width: backgroundWidth_1,
195
- height: backgroundHeight_1,
196
- sourceWidth: videoInfo.width,
197
- sourceHeight: videoInfo.height,
198
- })));
199
- return [4 /*yield*/, video_1.readVideoFrame(videoFrameReader.stdout, videoFrameSize, readVideoFrameTimeout)];
200
- case 5:
201
- firstFrame = _h.sent();
202
- if (!firstFrame) {
203
- throw new Error("Could not read frames from video: " + backgroundVideoPath);
204
- }
205
- defaultColor_1 = image_1.getVideoFrameColor(firstFrame, backgroundWidth_1, backgroundHeight_1);
206
- staticBackgroundBuffer_1 = encodeVideoFrame(firstFrame);
207
- return [2 /*return*/, {
208
- backgroundWidth: backgroundWidth_1,
209
- backgroundHeight: backgroundHeight_1,
210
- defaultColor: defaultColor_1,
211
- staticBackgroundBuffer: staticBackgroundBuffer_1,
212
- videoFrameReader: videoFrameReader,
213
- videoFrameSize: videoFrameSize,
214
- encodeVideoFrame: encodeVideoFrame,
215
- concatFilePath: concatFilePath,
216
- }];
217
- case 6:
218
- if (!backgroundImagePath) {
219
- throw new Error('Background image path is required when not using video background.');
220
- }
221
- return [4 /*yield*/, image_1.convertToBmp(backgroundImagePath, (_e = outputResolution) === null || _e === void 0 ? void 0 : _e.width, (_f = outputResolution) === null || _f === void 0 ? void 0 : _f.height)];
222
- case 7:
223
- backgroundImageBmpBuffer = _h.sent();
224
- backgroundImage = image_1.parseImage(backgroundImageBmpBuffer);
225
- backgroundWidth = backgroundImage.width;
226
- backgroundHeight = backgroundImage.height;
227
- defaultColor = image_1.getImageColor(backgroundImage);
228
- bpmEncoder = bpmEncoder_1.createBpmEncoder({ width: backgroundWidth, height: backgroundHeight });
229
- staticBackgroundBuffer = bpmEncoder(backgroundImage.data);
230
- return [2 /*return*/, {
231
- backgroundWidth: backgroundWidth,
232
- backgroundHeight: backgroundHeight,
233
- defaultColor: defaultColor,
234
- staticBackgroundBuffer: staticBackgroundBuffer,
235
- videoFrameSize: 0,
236
- }];
237
- }
238
- });
239
- });
240
- }
241
- function resolveBackgroundFrameBuffer(params) {
242
- return __awaiter(this, void 0, void 0, function () {
243
- var frameIndex, useVideoBackground, videoFrameReader, videoFrameSize, encodeVideoFrame, staticBackgroundBuffer, readVideoFrameTimeout, videoFrame;
244
- return __generator(this, function (_a) {
245
- switch (_a.label) {
246
- case 0:
247
- frameIndex = params.frameIndex, useVideoBackground = params.useVideoBackground, videoFrameReader = params.videoFrameReader, videoFrameSize = params.videoFrameSize, encodeVideoFrame = params.encodeVideoFrame, staticBackgroundBuffer = params.staticBackgroundBuffer, readVideoFrameTimeout = params.readVideoFrameTimeout;
248
- if (!(useVideoBackground && videoFrameReader && encodeVideoFrame)) return [3 /*break*/, 2];
249
- if (frameIndex === 0) {
250
- return [2 /*return*/, { frameBuffer: staticBackgroundBuffer, frameReadFailed: false }];
251
- }
252
- return [4 /*yield*/, video_1.readVideoFrame(videoFrameReader.stdout, videoFrameSize, readVideoFrameTimeout)];
253
- case 1:
254
- videoFrame = _a.sent();
255
- if (videoFrame) {
256
- return [2 /*return*/, { frameBuffer: encodeVideoFrame(videoFrame), frameReadFailed: false }];
257
- }
258
- return [2 /*return*/, { frameBuffer: staticBackgroundBuffer, frameReadFailed: false }];
259
- case 2: return [2 /*return*/, { frameBuffer: staticBackgroundBuffer, frameReadFailed: false }];
260
- }
261
- });
262
- });
263
- }
264
- exports.renderAudioVisualizer = function (config, onProgress, shouldStop) {
265
- return new Promise(function (resolve) { return __awaiter(void 0, void 0, void 0, function () {
266
- var audioFilePath, outVideoPath, subtitleSpec, subtitleFilePath, subtitleFileIsTemporary, subtitleAlignmentAss, absPath, ext, raw, backgroundVideoPath, backgroundImagePath, useVideoBackground, audioReader, audioBuffer, sampleRate, spectrumBusMargin, FPS, ffmpeg_cfr, ffmpeg_preset, frame_processing_delay, videoTimeouts, audioDuration, framesCount, outputResolution, maxProgressReported, reportProgress, preprocessed, autoHighlight, spectrumsForRender, beatIndicesForRender, framesCountForRender, highlightSlice, separateHighlightFiles, passes, totalPassFrames, totalRenderMilestones, renderMilestonesCompleted, reportRenderProgress, lastExitCode, exitReason, outputVideoFiles, _i, passes_1, pass, _a, backgroundWidth, backgroundHeight, defaultColor, staticBackgroundBuffer, videoFrameReader, videoFrameSize, encodeVideoFrame, concatFilePath, createVisualizerFrame, ffmpegVideoWriter, exitPromise, stoppedEarly, i, spectrum, _b, backgroundImageBuffer, frameReadFailed, commonVisualizerFrameProps, frameImage, isFrameProcessed, isDrained, _c, exitCode, ffmpegExitReason;
267
- return __generator(this, function (_d) {
268
- switch (_d.label) {
269
- case 0:
270
- if (config.outVideo.spectrum && config.outVideo.polar) {
271
- throw new Error('Cannot use both "spectrum" and "polar" options. Please specify only one visualizer type.');
272
- }
273
- if (!config.image && !config.video) {
274
- throw new Error('Either "image" or "video" must be specified as the background source.');
275
- }
276
- if (config.image && config.video) {
277
- throw new Error('Cannot use both "image" and "video" options. Please specify only one background source.');
278
- }
279
- audioFilePath = config_1.getAudioFilePath(config);
280
- outVideoPath = config_1.getOutVideoPath(config);
281
- subtitleSpec = config_1.getSubtitleRenderSpec(config);
282
- subtitleFileIsTemporary = false;
283
- subtitleAlignmentAss = 2;
284
- if (subtitleSpec) {
285
- subtitleAlignmentAss = config_1.subtitleAlignmentToAss(subtitleSpec.alignment);
286
- if (subtitleSpec.source.kind === 'file') {
287
- absPath = subtitleSpec.source.path;
288
- ext = path_1.default.extname(absPath).toLowerCase();
289
- if (ext === '.lrc') {
290
- raw = fs_1.readFileSync(absPath, 'utf-8');
291
- subtitleFilePath = video_1.writeSubtitlesFile(subtitleConvert_1.lrcToSrt(raw));
292
- subtitleFileIsTemporary = true;
293
- }
294
- else {
295
- subtitleFilePath = absPath;
296
- }
297
- }
298
- else {
299
- subtitleFilePath = video_1.writeSubtitlesFile(subtitleConvert_1.normalizeInlineSubtitlesToSrt(subtitleSpec.source.text));
300
- subtitleFileIsTemporary = true;
301
- }
302
- }
303
- backgroundVideoPath = config_1.getBackgroundVideoPath(config);
304
- backgroundImagePath = config_1.getBackgroundImagePath(config);
305
- useVideoBackground = !!backgroundVideoPath;
306
- return [4 /*yield*/, audio_1.createAudioBuffer(audioFilePath, FFMPEG_FORMAT)];
307
- case 1:
308
- audioReader = _d.sent();
309
- audioBuffer = audioReader.audioBuffer;
310
- sampleRate = audioReader.sampleRate;
311
- if (!sampleRate) {
312
- throw new Error('ffmpeg didn\'t show audio sample rate');
313
- }
314
- spectrumBusMargin = config_1.getSpectrumBusMargin();
315
- FPS = config_1.getFPS(config);
316
- ffmpeg_cfr = config_1.getFfmpeg_cfr(config);
317
- ffmpeg_preset = config_1.getFfmpeg_preset(config);
318
- frame_processing_delay = config_1.getFrame_processing_delay(config);
319
- videoTimeouts = config_1.getVideoTimeouts(config);
320
- audioDuration = audioBuffer.length / sampleRate;
321
- framesCount = Math.trunc(audioDuration * FPS);
322
- outputResolution = config_1.getOutputResolution(config);
323
- maxProgressReported = -1;
324
- reportProgress = function (progress) {
325
- if (!onProgress) {
326
- return;
327
- }
328
- var normalized = +Math.min(100, Math.max(0, progress)).toFixed(2);
329
- if (normalized <= maxProgressReported) {
330
- return;
331
- }
332
- maxProgressReported = normalized;
333
- onProgress(normalized);
334
- };
335
- reportProgress(0);
336
- return [4 /*yield*/, preProcessAudio(audioBuffer, sampleRate, FPS, framesCount, function (progress) {
337
- reportProgress(progress);
338
- })];
339
- case 2:
340
- preprocessed = _d.sent();
341
- reportProgress(PRE_PROCESS_PROGRESS_SHARE);
342
- autoHighlight = config_1.getAudioAutoHighlight(config);
343
- spectrumsForRender = preprocessed.spectrums;
344
- beatIndicesForRender = preprocessed.beatFrameIndices;
345
- framesCountForRender = framesCount;
346
- if (!autoHighlight) return [3 /*break*/, 4];
347
- reportProgress(PRE_PROCESS_PROGRESS_SHARE + 1);
348
- return [4 /*yield*/, highlight_1.computeHighlightSlice(FPS, framesCount, preprocessed.spectrums, preprocessed.beatEvents, config_1.getAudioAutoHighlightCount(config))];
349
- case 3:
350
- highlightSlice = _d.sent();
351
- spectrumsForRender = highlightSlice.spectrums;
352
- beatIndicesForRender = highlightSlice.beatFrameIndices;
353
- framesCountForRender = highlightSlice.highlightFrames;
354
- reportProgress(PRE_PROCESS_PROGRESS_SHARE + POST_AUDIO_PROGRESS_SHARE);
355
- return [3 /*break*/, 5];
356
- case 4:
357
- reportProgress(PRE_PROCESS_PROGRESS_SHARE + POST_AUDIO_PROGRESS_SHARE);
358
- _d.label = 5;
359
- case 5:
360
- separateHighlightFiles = autoHighlight &&
361
- highlightSlice &&
362
- highlightSlice.runs.length > 1;
363
- passes = separateHighlightFiles && highlightSlice
364
- ? highlightSlice.runs.map(function (run, i) {
365
- var dir = path_1.default.dirname(outVideoPath);
366
- var ext = path_1.default.extname(outVideoPath);
367
- var base = path_1.default.basename(outVideoPath, ext);
368
- var numberedPath = path_1.default.resolve(dir, base + "-" + (i + 1) + ext);
369
- return {
370
- outPath: numberedPath,
371
- spectrums: run.spectrums,
372
- beatIndices: run.beatFrameIndices,
373
- frameCount: run.highlightFrames,
374
- audioSegment: run.audioSegment,
375
- };
376
- })
377
- : [
378
- {
379
- outPath: outVideoPath,
380
- spectrums: spectrumsForRender,
381
- beatIndices: beatIndicesForRender,
382
- frameCount: framesCountForRender,
383
- audioSegment: autoHighlight &&
384
- highlightSlice &&
385
- highlightSlice.audioSegments.length > 0
386
- ? highlightSlice.audioSegments[0]
387
- : undefined,
388
- },
389
- ];
390
- totalPassFrames = passes.reduce(function (sum, pass) { return sum + pass.frameCount; }, 0);
391
- totalRenderMilestones = totalPassFrames + (passes.length * 2);
392
- renderMilestonesCompleted = 0;
393
- reportRenderProgress = function (milestoneIncrement) {
394
- if (milestoneIncrement === void 0) { milestoneIncrement = 1; }
395
- renderMilestonesCompleted += milestoneIncrement;
396
- reportProgress(PRE_PROCESS_PROGRESS_SHARE
397
- + POST_AUDIO_PROGRESS_SHARE
398
- + (RENDER_PROGRESS_SHARE * renderMilestonesCompleted / Math.max(1, totalRenderMilestones)));
399
- };
400
- lastExitCode = 0;
401
- outputVideoFiles = [];
402
- _d.label = 6;
403
- case 6:
404
- _d.trys.push([6, , 21, 22]);
405
- _i = 0, passes_1 = passes;
406
- _d.label = 7;
407
- case 7:
408
- if (!(_i < passes_1.length)) return [3 /*break*/, 20];
409
- pass = passes_1[_i];
410
- return [4 /*yield*/, prepareBackgroundForRender({
411
- useVideoBackground: useVideoBackground,
412
- backgroundVideoPath: backgroundVideoPath,
413
- backgroundImagePath: backgroundImagePath,
414
- beatFrameIndices: pass.beatIndices,
415
- framesCount: pass.frameCount,
416
- outputResolution: outputResolution,
417
- fps: FPS,
418
- autoEditVideo: config_1.getAutoEditVideo(config),
419
- readVideoFrameTimeout: videoTimeouts.readVideoFrame,
420
- })];
421
- case 8:
422
- _a = _d.sent(), backgroundWidth = _a.backgroundWidth, backgroundHeight = _a.backgroundHeight, defaultColor = _a.defaultColor, staticBackgroundBuffer = _a.staticBackgroundBuffer, videoFrameReader = _a.videoFrameReader, videoFrameSize = _a.videoFrameSize, encodeVideoFrame = _a.encodeVideoFrame, concatFilePath = _a.concatFilePath;
423
- reportRenderProgress();
424
- createVisualizerFrame = createVisualizerFrameGenerator(config, backgroundWidth, backgroundHeight, defaultColor, spectrumBusMargin);
425
- ffmpegVideoWriter = video_1.spawnFfmpegVideoWriter(__assign(__assign(__assign(__assign(__assign({ audioFilename: audioFilePath, videoFileName: pass.outPath }, (subtitleFilePath && {
426
- subtitleFilename: subtitleFilePath,
427
- subtitleAlignmentAss: subtitleAlignmentAss,
428
- })), { fps: FPS }), (pass.audioSegment && { audioSegment: pass.audioSegment })), (ffmpeg_cfr && { crf: ffmpeg_cfr })), (ffmpeg_preset && { preset: ffmpeg_preset })));
429
- exitPromise = video_1.waitForProcessExit(ffmpegVideoWriter, videoTimeouts.waitForProcessExit);
430
- stoppedEarly = false;
431
- i = 0;
432
- _d.label = 9;
433
- case 9:
434
- if (!(i < pass.frameCount)) return [3 /*break*/, 17];
435
- spectrum = pass.spectrums[i];
436
- return [4 /*yield*/, resolveBackgroundFrameBuffer({
437
- frameIndex: i,
438
- useVideoBackground: useVideoBackground,
439
- videoFrameReader: videoFrameReader,
440
- videoFrameSize: videoFrameSize,
441
- encodeVideoFrame: encodeVideoFrame,
442
- staticBackgroundBuffer: staticBackgroundBuffer,
443
- readVideoFrameTimeout: videoTimeouts.readVideoFrame,
444
- })];
445
- case 10:
446
- _b = _d.sent(), backgroundImageBuffer = _b.frameBuffer, frameReadFailed = _b.frameReadFailed;
447
- commonVisualizerFrameProps = {
448
- backgroundImageBuffer: backgroundImageBuffer,
449
- spectrum: spectrum,
450
- };
451
- frameImage = createVisualizerFrame(commonVisualizerFrameProps);
452
- isFrameProcessed = ffmpegVideoWriter.stdin.write(frameImage.data);
453
- if (!!isFrameProcessed) return [3 /*break*/, 12];
454
- return [4 /*yield*/, video_1.waitDrain(ffmpegVideoWriter.stdin, ffmpegVideoWriter, videoTimeouts.waitDrain)];
455
- case 11:
456
- isDrained = _d.sent();
457
- if (!isDrained) {
458
- stoppedEarly = true;
459
- exitReason = 'ffmpeg stdin drain failed';
460
- return [3 /*break*/, 17];
461
- }
462
- _d.label = 12;
463
- case 12:
464
- if (shouldStop && shouldStop()) {
465
- stoppedEarly = true;
466
- exitReason = 'render stopped by shouldStop callback';
467
- return [3 /*break*/, 17];
468
- }
469
- if (!frame_processing_delay) return [3 /*break*/, 14];
470
- return [4 /*yield*/, sleep(frame_processing_delay)];
471
- case 13:
472
- _d.sent();
473
- _d.label = 14;
474
- case 14: return [4 /*yield*/, waitForEventLoop_1.waitForEventLoop()];
475
- case 15:
476
- _d.sent();
477
- reportRenderProgress();
478
- _d.label = 16;
479
- case 16:
480
- i++;
481
- return [3 /*break*/, 9];
482
- case 17:
483
- if (videoFrameReader) {
484
- videoFrameReader.kill();
485
- }
486
- if (concatFilePath) {
487
- video_1.cleanupConcatFile(concatFilePath);
488
- }
489
- ffmpegVideoWriter.stdin.end();
490
- return [4 /*yield*/, exitPromise];
491
- case 18:
492
- _c = _d.sent(), exitCode = _c.exitCode, ffmpegExitReason = _c.reason;
493
- lastExitCode = exitCode;
494
- if (!exitReason && ffmpegExitReason) {
495
- exitReason = ffmpegExitReason;
496
- }
497
- if (lastExitCode === 0 && !stoppedEarly) {
498
- reportRenderProgress();
499
- }
500
- if (lastExitCode === 0 && !stoppedEarly) {
501
- outputVideoFiles.push(pass.outPath);
502
- }
503
- if (stoppedEarly || lastExitCode !== 0) {
504
- if (exitReason && lastExitCode === 0) {
505
- lastExitCode = 1;
506
- }
507
- return [3 /*break*/, 20];
508
- }
509
- _d.label = 19;
510
- case 19:
511
- _i++;
512
- return [3 /*break*/, 7];
513
- case 20:
514
- if (lastExitCode === 0 && outputVideoFiles.length === passes.length) {
515
- reportProgress(100);
516
- }
517
- if (exitReason) {
518
- console.error(exitReason);
519
- }
520
- resolve({ exitCode: lastExitCode, reason: exitReason, outputVideoFiles: outputVideoFiles });
521
- return [3 /*break*/, 22];
522
- case 21:
523
- if (subtitleFilePath && subtitleFileIsTemporary) {
524
- video_1.cleanupTempFile(subtitleFilePath);
525
- }
526
- return [7 /*endfinally*/];
527
- case 22: return [2 /*return*/];
528
- }
529
- });
530
- }); });
531
- };
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 __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
14
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
15
+ return new (P || (P = Promise))(function (resolve, reject) {
16
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
17
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
18
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
19
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
20
+ });
21
+ };
22
+ var __generator = (this && this.__generator) || function (thisArg, body) {
23
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
24
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
25
+ function verb(n) { return function (v) { return step([n, v]); }; }
26
+ function step(op) {
27
+ if (f) throw new TypeError("Generator is already executing.");
28
+ while (_) try {
29
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
30
+ if (y = 0, t) op = [op[0] & 2, t.value];
31
+ switch (op[0]) {
32
+ case 0: case 1: t = op; break;
33
+ case 4: _.label++; return { value: op[1], done: false };
34
+ case 5: _.label++; y = op[1]; op = [0]; continue;
35
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
36
+ default:
37
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
38
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
39
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
40
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
41
+ if (t[2]) _.ops.pop();
42
+ _.trys.pop(); continue;
43
+ }
44
+ op = body.call(thisArg, _);
45
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
46
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
47
+ }
48
+ };
49
+ var __importDefault = (this && this.__importDefault) || function (mod) {
50
+ return (mod && mod.__esModule) ? mod : { "default": mod };
51
+ };
52
+ Object.defineProperty(exports, "__esModule", { value: true });
53
+ var path_1 = __importDefault(require("path"));
54
+ var fs_1 = require("fs");
55
+ var config_1 = require("./config");
56
+ var audio_1 = require("./audio");
57
+ var image_1 = require("./image");
58
+ var subtitleConvert_1 = require("./subtitleConvert");
59
+ var video_1 = require("./video");
60
+ var bpmEncoder_1 = require("./bpmEncoder");
61
+ var beats_1 = require("./beats");
62
+ var highlight_1 = require("./highlight");
63
+ var waitForEventLoop_1 = require("./waitForEventLoop");
64
+ var highlight_2 = require("./highlight");
65
+ exports.computeHighlightSlice = highlight_2.computeHighlightSlice;
66
+ exports.HIGHLIGHT_DURATION_SEC = highlight_2.HIGHLIGHT_DURATION_SEC;
67
+ exports.PCM_FORMAT = {
68
+ bit: 8,
69
+ sign: 'u',
70
+ parseFunction: audio_1.bufferToUInt8
71
+ };
72
+ var FFMPEG_FORMAT = "" + exports.PCM_FORMAT.sign + exports.PCM_FORMAT.bit;
73
+ var PROCESSING_BUFFER_SIZE = Math.pow(2, 12);
74
+ var sleep = function (timeout) {
75
+ return new Promise(function (resolve) { return setTimeout(resolve, timeout); });
76
+ };
77
+ var createVisualizerFrameGenerator = function (config, backgroundWidth, backgroundHeight, defaultColor, spectrumBusMargin) {
78
+ if (config.outVideo.spectrum) {
79
+ var createSpectrumVisualizerFrame_1 = image_1.createSpectrumVisualizerFrameGenerator();
80
+ var spectrumWidth_1 = config_1.getSpectrumWidthAbsolute(config, backgroundWidth);
81
+ var spectrumHeight_1 = config_1.getSpectrumHeightAbsolute(config, backgroundHeight);
82
+ var spectrumX_1 = config_1.getSpectrumXAbsolute(config, spectrumWidth_1, backgroundWidth);
83
+ var spectrumY_1 = config_1.getSpectrumYAbsolute(config, spectrumHeight_1, backgroundHeight);
84
+ var spectrumRotation_1 = config_1.getSpectrumRotation(config);
85
+ var spectrumColor_1 = config_1.getSpectrumColor(config) || image_1.invertColor(defaultColor);
86
+ var spectrumEffect_1 = config_1.getSpectrumEffect(config);
87
+ var spectrumOpacity_1 = config_1.getSpectrumOpacityParsed(config);
88
+ return function (params) {
89
+ return createSpectrumVisualizerFrame_1(__assign(__assign({}, params), { size: { width: spectrumWidth_1, height: spectrumHeight_1 }, position: { x: spectrumX_1, y: spectrumY_1 }, rotation: spectrumRotation_1, margin: spectrumBusMargin, color: spectrumColor_1, opacity: spectrumOpacity_1, spectrumEffect: spectrumEffect_1 }));
90
+ };
91
+ }
92
+ if (config.outVideo.polar) {
93
+ var createPolarVisualizerFrame_1 = image_1.createPolarVisualizerFrameGenerator();
94
+ var polarX_1 = config_1.getPolarXAbsolute(config, backgroundWidth);
95
+ var polarY_1 = config_1.getPolarYAbsolute(config, backgroundHeight);
96
+ var polarInnerRadius_1 = config_1.getPolarInnerRadius(config);
97
+ var polarMaxBarLength_1 = config_1.getPolarMaxBarLength(config);
98
+ var polarBarWidth_1 = config_1.getPolarBarWidth(config);
99
+ var polarColor_1 = config_1.getPolarColor(config) || image_1.invertColor(defaultColor);
100
+ var polarEffect_1 = config_1.getPolarEffect(config);
101
+ var polarOpacity_1 = config_1.getPolarOpacityParsed(config);
102
+ return function (params) {
103
+ return createPolarVisualizerFrame_1(__assign(__assign({}, params), { centerX: polarX_1, centerY: polarY_1, innerRadius: polarInnerRadius_1, maxBarLength: polarMaxBarLength_1, barWidth: polarBarWidth_1, color: polarColor_1, opacity: polarOpacity_1, spectrumEffect: polarEffect_1 }));
104
+ };
105
+ }
106
+ return function (params) { return params.backgroundImageBuffer; };
107
+ };
108
+ var PRE_PROCESS_PROGRESS_SHARE = 60;
109
+ var POST_AUDIO_PROGRESS_SHARE = 10;
110
+ var RENDER_PROGRESS_SHARE = 30;
111
+ var preProcessAudio = function (audioBuffer, sampleRate, fps, framesCount, onPreProcessProgress) { return __awaiter(void 0, void 0, void 0, function () {
112
+ var t, audioDataStep, processingBuffer, skipFramesCount, processSpectrum, detectBeat, spectrums, beatFrameIndices, beatEvents, nextPreProcessMilestone, i, currentFrameData, frameDataLength, frameDataTailStart, frameDataToProcess, audioDataParser, spectrum, beat, p;
113
+ var _a, _b;
114
+ return __generator(this, function (_c) {
115
+ switch (_c.label) {
116
+ case 0:
117
+ if (framesCount === 0) {
118
+ for (t = 1; t <= PRE_PROCESS_PROGRESS_SHARE; t += 1) {
119
+ (_a = onPreProcessProgress) === null || _a === void 0 ? void 0 : _a(t);
120
+ }
121
+ return [2 /*return*/, { spectrums: [], beatFrameIndices: [], beatEvents: [] }];
122
+ }
123
+ audioDataStep = Math.trunc(audioBuffer.length / framesCount);
124
+ processingBuffer = new Float32Array(PROCESSING_BUFFER_SIZE).fill(0);
125
+ skipFramesCount = fps < 45 ? 1 : 2;
126
+ processSpectrum = audio_1.createSpectrumsProcessor(sampleRate, skipFramesCount);
127
+ detectBeat = beats_1.createBeatDetector(fps);
128
+ spectrums = [];
129
+ beatFrameIndices = [];
130
+ beatEvents = [];
131
+ nextPreProcessMilestone = 1;
132
+ i = 0;
133
+ _c.label = 1;
134
+ case 1:
135
+ if (!(i < framesCount)) return [3 /*break*/, 4];
136
+ currentFrameData = exports.PCM_FORMAT.parseFunction(audioBuffer, i * audioDataStep, i * audioDataStep + audioDataStep);
137
+ frameDataLength = Math.min(currentFrameData.length, PROCESSING_BUFFER_SIZE);
138
+ frameDataTailStart = currentFrameData.length - frameDataLength;
139
+ frameDataToProcess = currentFrameData.slice(frameDataTailStart);
140
+ processingBuffer.copyWithin(0, frameDataLength);
141
+ processingBuffer.set(frameDataToProcess, PROCESSING_BUFFER_SIZE - frameDataLength);
142
+ audioDataParser = function () { return Array.from(processingBuffer); };
143
+ spectrum = processSpectrum(audioDataParser);
144
+ beat = detectBeat(spectrum);
145
+ spectrums.push(spectrum);
146
+ if (beat.isBeat) {
147
+ beatFrameIndices.push(i);
148
+ beatEvents.push({ frameIndex: i, intensity: beat.intensity });
149
+ }
150
+ p = PRE_PROCESS_PROGRESS_SHARE * (i + 1) / framesCount;
151
+ while (nextPreProcessMilestone <= PRE_PROCESS_PROGRESS_SHARE
152
+ && p >= nextPreProcessMilestone) {
153
+ (_b = onPreProcessProgress) === null || _b === void 0 ? void 0 : _b(nextPreProcessMilestone);
154
+ nextPreProcessMilestone += 1;
155
+ }
156
+ return [4 /*yield*/, waitForEventLoop_1.waitForEventLoop()];
157
+ case 2:
158
+ _c.sent();
159
+ _c.label = 3;
160
+ case 3:
161
+ i++;
162
+ return [3 /*break*/, 1];
163
+ case 4: return [2 /*return*/, { spectrums: spectrums, beatFrameIndices: beatFrameIndices, beatEvents: beatEvents }];
164
+ }
165
+ });
166
+ }); };
167
+ function prepareBackgroundForRender(params) {
168
+ var _a, _b, _c, _d, _e, _f;
169
+ return __awaiter(this, void 0, void 0, function () {
170
+ var useVideoBackground, backgroundVideoPath, backgroundImagePath, beatFrameIndices, framesCount, outputResolution, fps, autoEditVideo, readVideoFrameTimeout, videoInfo, sceneChanges, _g, backgroundWidth_1, backgroundHeight_1, videoFrameSize, encodeVideoFrame, segments, concatFilePath, videoFrameReader, firstFrame, defaultColor_1, staticBackgroundBuffer_1, backgroundImageBmpBuffer, backgroundImage, backgroundWidth, backgroundHeight, defaultColor, bpmEncoder, staticBackgroundBuffer;
171
+ return __generator(this, function (_h) {
172
+ switch (_h.label) {
173
+ case 0:
174
+ useVideoBackground = params.useVideoBackground, backgroundVideoPath = params.backgroundVideoPath, backgroundImagePath = params.backgroundImagePath, beatFrameIndices = params.beatFrameIndices, framesCount = params.framesCount, outputResolution = params.outputResolution, fps = params.fps, autoEditVideo = params.autoEditVideo, readVideoFrameTimeout = params.readVideoFrameTimeout;
175
+ if (!(useVideoBackground && backgroundVideoPath)) return [3 /*break*/, 6];
176
+ return [4 /*yield*/, video_1.getVideoInfo(backgroundVideoPath)];
177
+ case 1:
178
+ videoInfo = _h.sent();
179
+ if (!autoEditVideo) return [3 /*break*/, 3];
180
+ return [4 /*yield*/, video_1.detectSceneChanges(backgroundVideoPath)];
181
+ case 2:
182
+ _g = _h.sent();
183
+ return [3 /*break*/, 4];
184
+ case 3:
185
+ _g = [];
186
+ _h.label = 4;
187
+ case 4:
188
+ sceneChanges = _g;
189
+ backgroundWidth_1 = (_b = (_a = outputResolution) === null || _a === void 0 ? void 0 : _a.width, (_b !== null && _b !== void 0 ? _b : videoInfo.width));
190
+ backgroundHeight_1 = (_d = (_c = outputResolution) === null || _c === void 0 ? void 0 : _c.height, (_d !== null && _d !== void 0 ? _d : videoInfo.height));
191
+ videoFrameSize = backgroundWidth_1 * backgroundHeight_1 * 3;
192
+ encodeVideoFrame = bpmEncoder_1.createBgrFrameEncoder({ width: backgroundWidth_1, height: backgroundHeight_1 });
193
+ segments = video_1.buildBeatSyncedSegments(beatFrameIndices, framesCount, sceneChanges, videoInfo.duration);
194
+ concatFilePath = video_1.writeConcatFile(segments, backgroundVideoPath, videoInfo.duration, fps);
195
+ videoFrameReader = video_1.spawnConcatVideoFrameReader(__assign({ concatFilePath: concatFilePath,
196
+ fps: fps, totalFrames: framesCount }, (outputResolution && {
197
+ width: backgroundWidth_1,
198
+ height: backgroundHeight_1,
199
+ sourceWidth: videoInfo.width,
200
+ sourceHeight: videoInfo.height,
201
+ })));
202
+ return [4 /*yield*/, video_1.readVideoFrame(videoFrameReader.stdout, videoFrameSize, readVideoFrameTimeout)];
203
+ case 5:
204
+ firstFrame = _h.sent();
205
+ if (!firstFrame) {
206
+ throw new Error("Could not read frames from video: " + backgroundVideoPath);
207
+ }
208
+ defaultColor_1 = image_1.getVideoFrameColor(firstFrame, backgroundWidth_1, backgroundHeight_1);
209
+ staticBackgroundBuffer_1 = encodeVideoFrame(firstFrame);
210
+ return [2 /*return*/, {
211
+ backgroundWidth: backgroundWidth_1,
212
+ backgroundHeight: backgroundHeight_1,
213
+ defaultColor: defaultColor_1,
214
+ staticBackgroundBuffer: staticBackgroundBuffer_1,
215
+ videoFrameReader: videoFrameReader,
216
+ videoFrameSize: videoFrameSize,
217
+ encodeVideoFrame: encodeVideoFrame,
218
+ concatFilePath: concatFilePath,
219
+ }];
220
+ case 6:
221
+ if (!backgroundImagePath) {
222
+ throw new Error('Background image path is required when not using video background.');
223
+ }
224
+ return [4 /*yield*/, image_1.convertToBmp(backgroundImagePath, (_e = outputResolution) === null || _e === void 0 ? void 0 : _e.width, (_f = outputResolution) === null || _f === void 0 ? void 0 : _f.height)];
225
+ case 7:
226
+ backgroundImageBmpBuffer = _h.sent();
227
+ backgroundImage = image_1.parseImage(backgroundImageBmpBuffer);
228
+ backgroundWidth = backgroundImage.width;
229
+ backgroundHeight = backgroundImage.height;
230
+ defaultColor = image_1.getImageColor(backgroundImage);
231
+ bpmEncoder = bpmEncoder_1.createBpmEncoder({ width: backgroundWidth, height: backgroundHeight });
232
+ staticBackgroundBuffer = bpmEncoder(backgroundImage.data);
233
+ return [2 /*return*/, {
234
+ backgroundWidth: backgroundWidth,
235
+ backgroundHeight: backgroundHeight,
236
+ defaultColor: defaultColor,
237
+ staticBackgroundBuffer: staticBackgroundBuffer,
238
+ videoFrameSize: 0,
239
+ }];
240
+ }
241
+ });
242
+ });
243
+ }
244
+ function resolveBackgroundFrameBuffer(params) {
245
+ return __awaiter(this, void 0, void 0, function () {
246
+ var frameIndex, useVideoBackground, videoFrameReader, videoFrameSize, encodeVideoFrame, staticBackgroundBuffer, readVideoFrameTimeout, videoFrame;
247
+ return __generator(this, function (_a) {
248
+ switch (_a.label) {
249
+ case 0:
250
+ frameIndex = params.frameIndex, useVideoBackground = params.useVideoBackground, videoFrameReader = params.videoFrameReader, videoFrameSize = params.videoFrameSize, encodeVideoFrame = params.encodeVideoFrame, staticBackgroundBuffer = params.staticBackgroundBuffer, readVideoFrameTimeout = params.readVideoFrameTimeout;
251
+ if (!(useVideoBackground && videoFrameReader && encodeVideoFrame)) return [3 /*break*/, 2];
252
+ if (frameIndex === 0) {
253
+ return [2 /*return*/, { frameBuffer: staticBackgroundBuffer, frameReadFailed: false }];
254
+ }
255
+ return [4 /*yield*/, video_1.readVideoFrame(videoFrameReader.stdout, videoFrameSize, readVideoFrameTimeout)];
256
+ case 1:
257
+ videoFrame = _a.sent();
258
+ if (videoFrame) {
259
+ return [2 /*return*/, { frameBuffer: encodeVideoFrame(videoFrame), frameReadFailed: false }];
260
+ }
261
+ return [2 /*return*/, { frameBuffer: staticBackgroundBuffer, frameReadFailed: false }];
262
+ case 2: return [2 /*return*/, { frameBuffer: staticBackgroundBuffer, frameReadFailed: false }];
263
+ }
264
+ });
265
+ });
266
+ }
267
+ exports.renderAudioVisualizer = function (config, onProgress, shouldStop) {
268
+ return new Promise(function (resolve) { return __awaiter(void 0, void 0, void 0, function () {
269
+ var audioFilePath, outVideoPath, subtitleSpec, subtitleFilePath, subtitleFileIsTemporary, subtitleAlignmentAss, absPath, ext, raw, backgroundVideoPath, backgroundImagePath, useVideoBackground, audioReader, audioBuffer, sampleRate, spectrumBusMargin, FPS, ffmpeg_cfr, ffmpeg_preset, frame_processing_delay, videoTimeouts, audioDuration, framesCount, outputResolution, maxProgressReported, reportProgress, preprocessed, autoHighlight, spectrumsForRender, beatIndicesForRender, framesCountForRender, highlightSlice, separateHighlightFiles, passes, totalPassFrames, totalRenderMilestones, renderMilestonesCompleted, reportRenderProgress, lastExitCode, exitReason, outputVideoFiles, _i, passes_1, pass, _a, backgroundWidth, backgroundHeight, defaultColor, staticBackgroundBuffer, videoFrameReader, videoFrameSize, encodeVideoFrame, concatFilePath, createVisualizerFrame, ffmpegVideoWriter, exitPromise, stoppedEarly, i, spectrum, _b, backgroundImageBuffer, frameReadFailed, commonVisualizerFrameProps, frameImage, isFrameProcessed, isDrained, _c, exitCode, ffmpegExitReason;
270
+ return __generator(this, function (_d) {
271
+ switch (_d.label) {
272
+ case 0:
273
+ if (config.outVideo.spectrum && config.outVideo.polar) {
274
+ throw new Error('Cannot use both "spectrum" and "polar" options. Please specify only one visualizer type.');
275
+ }
276
+ if (!config.image && !config.video) {
277
+ throw new Error('Either "image" or "video" must be specified as the background source.');
278
+ }
279
+ if (config.image && config.video) {
280
+ throw new Error('Cannot use both "image" and "video" options. Please specify only one background source.');
281
+ }
282
+ audioFilePath = config_1.getAudioFilePath(config);
283
+ outVideoPath = config_1.getOutVideoPath(config);
284
+ subtitleSpec = config_1.getSubtitleRenderSpec(config);
285
+ subtitleFileIsTemporary = false;
286
+ subtitleAlignmentAss = 2;
287
+ if (subtitleSpec) {
288
+ subtitleAlignmentAss = config_1.subtitleAlignmentToAss(subtitleSpec.alignment);
289
+ if (subtitleSpec.source.kind === 'file') {
290
+ absPath = subtitleSpec.source.path;
291
+ ext = path_1.default.extname(absPath).toLowerCase();
292
+ if (ext === '.lrc') {
293
+ raw = fs_1.readFileSync(absPath, 'utf-8');
294
+ subtitleFilePath = video_1.writeSubtitlesFile(subtitleConvert_1.lrcToSrt(raw));
295
+ subtitleFileIsTemporary = true;
296
+ }
297
+ else {
298
+ subtitleFilePath = absPath;
299
+ }
300
+ }
301
+ else {
302
+ subtitleFilePath = video_1.writeSubtitlesFile(subtitleConvert_1.normalizeInlineSubtitlesToSrt(subtitleSpec.source.text));
303
+ subtitleFileIsTemporary = true;
304
+ }
305
+ }
306
+ backgroundVideoPath = config_1.getBackgroundVideoPath(config);
307
+ backgroundImagePath = config_1.getBackgroundImagePath(config);
308
+ useVideoBackground = !!backgroundVideoPath;
309
+ return [4 /*yield*/, audio_1.createAudioBuffer(audioFilePath, FFMPEG_FORMAT)];
310
+ case 1:
311
+ audioReader = _d.sent();
312
+ audioBuffer = audioReader.audioBuffer;
313
+ sampleRate = audioReader.sampleRate;
314
+ if (!sampleRate) {
315
+ throw new Error('ffmpeg didn\'t show audio sample rate');
316
+ }
317
+ spectrumBusMargin = config_1.getSpectrumBusMargin();
318
+ FPS = config_1.getFPS(config);
319
+ ffmpeg_cfr = config_1.getFfmpeg_cfr(config);
320
+ ffmpeg_preset = config_1.getFfmpeg_preset(config);
321
+ frame_processing_delay = config_1.getFrame_processing_delay(config);
322
+ videoTimeouts = config_1.getVideoTimeouts(config);
323
+ audioDuration = audioBuffer.length / sampleRate;
324
+ framesCount = Math.trunc(audioDuration * FPS);
325
+ outputResolution = config_1.getOutputResolution(config);
326
+ maxProgressReported = -1;
327
+ reportProgress = function (progress) {
328
+ if (!onProgress) {
329
+ return;
330
+ }
331
+ var normalized = +Math.min(100, Math.max(0, progress)).toFixed(2);
332
+ if (normalized <= maxProgressReported) {
333
+ return;
334
+ }
335
+ maxProgressReported = normalized;
336
+ onProgress(normalized);
337
+ };
338
+ reportProgress(0);
339
+ return [4 /*yield*/, preProcessAudio(audioBuffer, sampleRate, FPS, framesCount, function (progress) {
340
+ reportProgress(progress);
341
+ })];
342
+ case 2:
343
+ preprocessed = _d.sent();
344
+ reportProgress(PRE_PROCESS_PROGRESS_SHARE);
345
+ autoHighlight = config_1.getAudioAutoHighlight(config);
346
+ spectrumsForRender = preprocessed.spectrums;
347
+ beatIndicesForRender = preprocessed.beatFrameIndices;
348
+ framesCountForRender = framesCount;
349
+ if (!autoHighlight) return [3 /*break*/, 4];
350
+ reportProgress(PRE_PROCESS_PROGRESS_SHARE + 1);
351
+ return [4 /*yield*/, highlight_1.computeHighlightSlice(FPS, framesCount, preprocessed.spectrums, preprocessed.beatEvents, config_1.getAudioAutoHighlightCount(config))];
352
+ case 3:
353
+ highlightSlice = _d.sent();
354
+ spectrumsForRender = highlightSlice.spectrums;
355
+ beatIndicesForRender = highlightSlice.beatFrameIndices;
356
+ framesCountForRender = highlightSlice.highlightFrames;
357
+ reportProgress(PRE_PROCESS_PROGRESS_SHARE + POST_AUDIO_PROGRESS_SHARE);
358
+ return [3 /*break*/, 5];
359
+ case 4:
360
+ reportProgress(PRE_PROCESS_PROGRESS_SHARE + POST_AUDIO_PROGRESS_SHARE);
361
+ _d.label = 5;
362
+ case 5:
363
+ separateHighlightFiles = autoHighlight &&
364
+ highlightSlice &&
365
+ highlightSlice.runs.length > 1;
366
+ passes = separateHighlightFiles && highlightSlice
367
+ ? highlightSlice.runs.map(function (run, i) {
368
+ var dir = path_1.default.dirname(outVideoPath);
369
+ var ext = path_1.default.extname(outVideoPath);
370
+ var base = path_1.default.basename(outVideoPath, ext);
371
+ var numberedPath = path_1.default.resolve(dir, base + "-" + (i + 1) + ext);
372
+ return {
373
+ outPath: numberedPath,
374
+ spectrums: run.spectrums,
375
+ beatIndices: run.beatFrameIndices,
376
+ frameCount: run.highlightFrames,
377
+ audioSegment: run.audioSegment,
378
+ };
379
+ })
380
+ : [
381
+ {
382
+ outPath: outVideoPath,
383
+ spectrums: spectrumsForRender,
384
+ beatIndices: beatIndicesForRender,
385
+ frameCount: framesCountForRender,
386
+ audioSegment: autoHighlight &&
387
+ highlightSlice &&
388
+ highlightSlice.audioSegments.length > 0
389
+ ? highlightSlice.audioSegments[0]
390
+ : undefined,
391
+ },
392
+ ];
393
+ totalPassFrames = passes.reduce(function (sum, pass) { return sum + pass.frameCount; }, 0);
394
+ totalRenderMilestones = totalPassFrames + (passes.length * 2);
395
+ renderMilestonesCompleted = 0;
396
+ reportRenderProgress = function (milestoneIncrement) {
397
+ if (milestoneIncrement === void 0) { milestoneIncrement = 1; }
398
+ renderMilestonesCompleted += milestoneIncrement;
399
+ reportProgress(PRE_PROCESS_PROGRESS_SHARE
400
+ + POST_AUDIO_PROGRESS_SHARE
401
+ + (RENDER_PROGRESS_SHARE * renderMilestonesCompleted / Math.max(1, totalRenderMilestones)));
402
+ };
403
+ lastExitCode = 0;
404
+ outputVideoFiles = [];
405
+ _d.label = 6;
406
+ case 6:
407
+ _d.trys.push([6, , 21, 22]);
408
+ _i = 0, passes_1 = passes;
409
+ _d.label = 7;
410
+ case 7:
411
+ if (!(_i < passes_1.length)) return [3 /*break*/, 20];
412
+ pass = passes_1[_i];
413
+ return [4 /*yield*/, prepareBackgroundForRender({
414
+ useVideoBackground: useVideoBackground,
415
+ backgroundVideoPath: backgroundVideoPath,
416
+ backgroundImagePath: backgroundImagePath,
417
+ beatFrameIndices: pass.beatIndices,
418
+ framesCount: pass.frameCount,
419
+ outputResolution: outputResolution,
420
+ fps: FPS,
421
+ autoEditVideo: config_1.getAutoEditVideo(config),
422
+ readVideoFrameTimeout: videoTimeouts.readVideoFrame,
423
+ })];
424
+ case 8:
425
+ _a = _d.sent(), backgroundWidth = _a.backgroundWidth, backgroundHeight = _a.backgroundHeight, defaultColor = _a.defaultColor, staticBackgroundBuffer = _a.staticBackgroundBuffer, videoFrameReader = _a.videoFrameReader, videoFrameSize = _a.videoFrameSize, encodeVideoFrame = _a.encodeVideoFrame, concatFilePath = _a.concatFilePath;
426
+ reportRenderProgress();
427
+ createVisualizerFrame = createVisualizerFrameGenerator(config, backgroundWidth, backgroundHeight, defaultColor, spectrumBusMargin);
428
+ ffmpegVideoWriter = video_1.spawnFfmpegVideoWriter(__assign(__assign(__assign(__assign(__assign({ audioFilename: audioFilePath, videoFileName: pass.outPath }, (subtitleFilePath && {
429
+ subtitleFilename: subtitleFilePath,
430
+ subtitleAlignmentAss: subtitleAlignmentAss,
431
+ })), { fps: FPS }), (pass.audioSegment && { audioSegment: pass.audioSegment })), (ffmpeg_cfr && { crf: ffmpeg_cfr })), (ffmpeg_preset && { preset: ffmpeg_preset })));
432
+ exitPromise = video_1.waitForProcessExit(ffmpegVideoWriter, videoTimeouts.waitForProcessExit);
433
+ stoppedEarly = false;
434
+ i = 0;
435
+ _d.label = 9;
436
+ case 9:
437
+ if (!(i < pass.frameCount)) return [3 /*break*/, 17];
438
+ spectrum = pass.spectrums[i];
439
+ return [4 /*yield*/, resolveBackgroundFrameBuffer({
440
+ frameIndex: i,
441
+ useVideoBackground: useVideoBackground,
442
+ videoFrameReader: videoFrameReader,
443
+ videoFrameSize: videoFrameSize,
444
+ encodeVideoFrame: encodeVideoFrame,
445
+ staticBackgroundBuffer: staticBackgroundBuffer,
446
+ readVideoFrameTimeout: videoTimeouts.readVideoFrame,
447
+ })];
448
+ case 10:
449
+ _b = _d.sent(), backgroundImageBuffer = _b.frameBuffer, frameReadFailed = _b.frameReadFailed;
450
+ commonVisualizerFrameProps = {
451
+ backgroundImageBuffer: backgroundImageBuffer,
452
+ spectrum: spectrum,
453
+ };
454
+ frameImage = createVisualizerFrame(commonVisualizerFrameProps);
455
+ isFrameProcessed = ffmpegVideoWriter.stdin.write(frameImage.data);
456
+ if (!!isFrameProcessed) return [3 /*break*/, 12];
457
+ return [4 /*yield*/, video_1.waitDrain(ffmpegVideoWriter.stdin, ffmpegVideoWriter, videoTimeouts.waitDrain)];
458
+ case 11:
459
+ isDrained = _d.sent();
460
+ if (!isDrained) {
461
+ stoppedEarly = true;
462
+ exitReason = 'ffmpeg stdin drain failed';
463
+ return [3 /*break*/, 17];
464
+ }
465
+ _d.label = 12;
466
+ case 12:
467
+ if (shouldStop && shouldStop()) {
468
+ stoppedEarly = true;
469
+ exitReason = 'render stopped by shouldStop callback';
470
+ return [3 /*break*/, 17];
471
+ }
472
+ if (!frame_processing_delay) return [3 /*break*/, 14];
473
+ return [4 /*yield*/, sleep(frame_processing_delay)];
474
+ case 13:
475
+ _d.sent();
476
+ _d.label = 14;
477
+ case 14: return [4 /*yield*/, waitForEventLoop_1.waitForEventLoop()];
478
+ case 15:
479
+ _d.sent();
480
+ reportRenderProgress();
481
+ _d.label = 16;
482
+ case 16:
483
+ i++;
484
+ return [3 /*break*/, 9];
485
+ case 17:
486
+ if (videoFrameReader) {
487
+ videoFrameReader.kill();
488
+ }
489
+ if (concatFilePath) {
490
+ video_1.cleanupConcatFile(concatFilePath);
491
+ }
492
+ ffmpegVideoWriter.stdin.end();
493
+ return [4 /*yield*/, exitPromise];
494
+ case 18:
495
+ _c = _d.sent(), exitCode = _c.exitCode, ffmpegExitReason = _c.reason;
496
+ lastExitCode = exitCode;
497
+ if (!exitReason && ffmpegExitReason) {
498
+ exitReason = ffmpegExitReason;
499
+ }
500
+ if (lastExitCode === 0 && !stoppedEarly) {
501
+ reportRenderProgress();
502
+ }
503
+ if (lastExitCode === 0 && !stoppedEarly) {
504
+ outputVideoFiles.push(pass.outPath);
505
+ }
506
+ if (stoppedEarly || lastExitCode !== 0) {
507
+ if (exitReason && lastExitCode === 0) {
508
+ lastExitCode = 1;
509
+ }
510
+ return [3 /*break*/, 20];
511
+ }
512
+ _d.label = 19;
513
+ case 19:
514
+ _i++;
515
+ return [3 /*break*/, 7];
516
+ case 20:
517
+ if (lastExitCode === 0 && outputVideoFiles.length === passes.length) {
518
+ reportProgress(100);
519
+ }
520
+ if (exitReason) {
521
+ console.error(exitReason);
522
+ }
523
+ resolve({ exitCode: lastExitCode, reason: exitReason, outputVideoFiles: outputVideoFiles });
524
+ return [3 /*break*/, 22];
525
+ case 21:
526
+ if (subtitleFilePath && subtitleFileIsTemporary) {
527
+ video_1.cleanupTempFile(subtitleFilePath);
528
+ }
529
+ return [7 /*endfinally*/];
530
+ case 22: return [2 /*return*/];
531
+ }
532
+ });
533
+ }); });
534
+ };
532
535
  //# sourceMappingURL=index.js.map