nodejs-audio-visualizer 4.4.12 → 4.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +80 -75
  3. package/dist/audio.d.ts +12 -12
  4. package/dist/audio.js +120 -120
  5. package/dist/beats.d.ts +19 -19
  6. package/dist/beats.js +43 -43
  7. package/dist/bpmEncoder.d.ts +14 -14
  8. package/dist/bpmEncoder.js +97 -97
  9. package/dist/config.d.ts +77 -67
  10. package/dist/config.js +369 -356
  11. package/dist/config.js.map +1 -1
  12. package/dist/dsp.d.ts +16 -16
  13. package/dist/dsp.js +76 -76
  14. package/dist/highlight.d.ts +32 -32
  15. package/dist/highlight.js +325 -325
  16. package/dist/image.d.ts +79 -79
  17. package/dist/image.js +458 -458
  18. package/dist/index.d.ts +79 -74
  19. package/dist/index.js +531 -528
  20. package/dist/index.js.map +1 -1
  21. package/dist/subtitleConvert.d.ts +3 -3
  22. package/dist/subtitleConvert.js +61 -61
  23. package/dist/video.d.ts +80 -80
  24. package/dist/video.js +434 -430
  25. package/dist/video.js.map +1 -1
  26. package/dist/waitForEventLoop.d.ts +1 -1
  27. package/dist/waitForEventLoop.js +5 -5
  28. package/package.json +61 -61
  29. package/src/audio.ts +120 -120
  30. package/src/beats.ts +68 -68
  31. package/src/bpmEncoder.ts +97 -97
  32. package/src/config.ts +476 -462
  33. package/src/dsp.ts +95 -95
  34. package/src/highlight.ts +284 -284
  35. package/src/image.ts +602 -602
  36. package/src/index.ts +713 -685
  37. package/src/subtitleConvert.ts +63 -63
  38. package/src/tests/audio.test.ts +96 -96
  39. package/src/tests/config.test.ts +496 -475
  40. package/src/tests/dsp.test.ts +24 -24
  41. package/src/tests/highlight.test.ts +178 -178
  42. package/src/tests/image.test.ts +222 -222
  43. package/src/tests/index.test.ts +112 -112
  44. package/src/tests/subtitleConvert.test.ts +23 -23
  45. package/src/tests/video.test.ts +285 -216
  46. package/src/typings.d.ts +6 -6
  47. package/src/video.ts +550 -547
  48. package/src/waitForEventLoop.ts +2 -2
package/dist/index.js CHANGED
@@ -1,529 +1,532 @@
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, 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;
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)];
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, 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;
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)];
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, 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
- audioDuration = audioBuffer.length / sampleRate;
320
- framesCount = Math.trunc(audioDuration * FPS);
321
- outputResolution = config_1.getOutputResolution(config);
322
- maxProgressReported = -1;
323
- reportProgress = function (progress) {
324
- if (!onProgress) {
325
- return;
326
- }
327
- var normalized = +Math.min(100, Math.max(0, progress)).toFixed(2);
328
- if (normalized <= maxProgressReported) {
329
- return;
330
- }
331
- maxProgressReported = normalized;
332
- onProgress(normalized);
333
- };
334
- reportProgress(0);
335
- return [4 /*yield*/, preProcessAudio(audioBuffer, sampleRate, FPS, framesCount, function (progress) {
336
- reportProgress(progress);
337
- })];
338
- case 2:
339
- preprocessed = _d.sent();
340
- reportProgress(PRE_PROCESS_PROGRESS_SHARE);
341
- autoHighlight = config_1.getAudioAutoHighlight(config);
342
- spectrumsForRender = preprocessed.spectrums;
343
- beatIndicesForRender = preprocessed.beatFrameIndices;
344
- framesCountForRender = framesCount;
345
- if (!autoHighlight) return [3 /*break*/, 4];
346
- reportProgress(PRE_PROCESS_PROGRESS_SHARE + 1);
347
- return [4 /*yield*/, highlight_1.computeHighlightSlice(FPS, framesCount, preprocessed.spectrums, preprocessed.beatEvents, config_1.getAudioAutoHighlightCount(config))];
348
- case 3:
349
- highlightSlice = _d.sent();
350
- spectrumsForRender = highlightSlice.spectrums;
351
- beatIndicesForRender = highlightSlice.beatFrameIndices;
352
- framesCountForRender = highlightSlice.highlightFrames;
353
- reportProgress(PRE_PROCESS_PROGRESS_SHARE + POST_AUDIO_PROGRESS_SHARE);
354
- return [3 /*break*/, 5];
355
- case 4:
356
- reportProgress(PRE_PROCESS_PROGRESS_SHARE + POST_AUDIO_PROGRESS_SHARE);
357
- _d.label = 5;
358
- case 5:
359
- separateHighlightFiles = autoHighlight &&
360
- highlightSlice &&
361
- highlightSlice.runs.length > 1;
362
- passes = separateHighlightFiles && highlightSlice
363
- ? highlightSlice.runs.map(function (run, i) {
364
- var dir = path_1.default.dirname(outVideoPath);
365
- var ext = path_1.default.extname(outVideoPath);
366
- var base = path_1.default.basename(outVideoPath, ext);
367
- var numberedPath = path_1.default.resolve(dir, base + "-" + (i + 1) + ext);
368
- return {
369
- outPath: numberedPath,
370
- spectrums: run.spectrums,
371
- beatIndices: run.beatFrameIndices,
372
- frameCount: run.highlightFrames,
373
- audioSegment: run.audioSegment,
374
- };
375
- })
376
- : [
377
- {
378
- outPath: outVideoPath,
379
- spectrums: spectrumsForRender,
380
- beatIndices: beatIndicesForRender,
381
- frameCount: framesCountForRender,
382
- audioSegment: autoHighlight &&
383
- highlightSlice &&
384
- highlightSlice.audioSegments.length > 0
385
- ? highlightSlice.audioSegments[0]
386
- : undefined,
387
- },
388
- ];
389
- totalPassFrames = passes.reduce(function (sum, pass) { return sum + pass.frameCount; }, 0);
390
- totalRenderMilestones = totalPassFrames + (passes.length * 2);
391
- renderMilestonesCompleted = 0;
392
- reportRenderProgress = function (milestoneIncrement) {
393
- if (milestoneIncrement === void 0) { milestoneIncrement = 1; }
394
- renderMilestonesCompleted += milestoneIncrement;
395
- reportProgress(PRE_PROCESS_PROGRESS_SHARE
396
- + POST_AUDIO_PROGRESS_SHARE
397
- + (RENDER_PROGRESS_SHARE * renderMilestonesCompleted / Math.max(1, totalRenderMilestones)));
398
- };
399
- lastExitCode = 0;
400
- outputVideoFiles = [];
401
- _d.label = 6;
402
- case 6:
403
- _d.trys.push([6, , 21, 22]);
404
- _i = 0, passes_1 = passes;
405
- _d.label = 7;
406
- case 7:
407
- if (!(_i < passes_1.length)) return [3 /*break*/, 20];
408
- pass = passes_1[_i];
409
- return [4 /*yield*/, prepareBackgroundForRender({
410
- useVideoBackground: useVideoBackground,
411
- backgroundVideoPath: backgroundVideoPath,
412
- backgroundImagePath: backgroundImagePath,
413
- beatFrameIndices: pass.beatIndices,
414
- framesCount: pass.frameCount,
415
- outputResolution: outputResolution,
416
- fps: FPS,
417
- autoEditVideo: config_1.getAutoEditVideo(config),
418
- })];
419
- case 8:
420
- _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;
421
- reportRenderProgress();
422
- createVisualizerFrame = createVisualizerFrameGenerator(config, backgroundWidth, backgroundHeight, defaultColor, spectrumBusMargin);
423
- ffmpegVideoWriter = video_1.spawnFfmpegVideoWriter(__assign(__assign(__assign(__assign(__assign({ audioFilename: audioFilePath, videoFileName: pass.outPath }, (subtitleFilePath && {
424
- subtitleFilename: subtitleFilePath,
425
- subtitleAlignmentAss: subtitleAlignmentAss,
426
- })), { fps: FPS }), (pass.audioSegment && { audioSegment: pass.audioSegment })), (ffmpeg_cfr && { crf: ffmpeg_cfr })), (ffmpeg_preset && { preset: ffmpeg_preset })));
427
- exitPromise = video_1.waitForProcessExit(ffmpegVideoWriter);
428
- stoppedEarly = false;
429
- i = 0;
430
- _d.label = 9;
431
- case 9:
432
- if (!(i < pass.frameCount)) return [3 /*break*/, 17];
433
- spectrum = pass.spectrums[i];
434
- return [4 /*yield*/, resolveBackgroundFrameBuffer({
435
- frameIndex: i,
436
- useVideoBackground: useVideoBackground,
437
- videoFrameReader: videoFrameReader,
438
- videoFrameSize: videoFrameSize,
439
- encodeVideoFrame: encodeVideoFrame,
440
- staticBackgroundBuffer: staticBackgroundBuffer,
441
- })];
442
- case 10:
443
- _b = _d.sent(), backgroundImageBuffer = _b.frameBuffer, frameReadFailed = _b.frameReadFailed;
444
- commonVisualizerFrameProps = {
445
- backgroundImageBuffer: backgroundImageBuffer,
446
- spectrum: spectrum,
447
- };
448
- frameImage = createVisualizerFrame(commonVisualizerFrameProps);
449
- isFrameProcessed = ffmpegVideoWriter.stdin.write(frameImage.data);
450
- if (!!isFrameProcessed) return [3 /*break*/, 12];
451
- return [4 /*yield*/, video_1.waitDrain(ffmpegVideoWriter.stdin, ffmpegVideoWriter)];
452
- case 11:
453
- isDrained = _d.sent();
454
- if (!isDrained) {
455
- stoppedEarly = true;
456
- exitReason = 'ffmpeg stdin drain failed';
457
- return [3 /*break*/, 17];
458
- }
459
- _d.label = 12;
460
- case 12:
461
- if (shouldStop && shouldStop()) {
462
- stoppedEarly = true;
463
- exitReason = 'render stopped by shouldStop callback';
464
- return [3 /*break*/, 17];
465
- }
466
- if (!frame_processing_delay) return [3 /*break*/, 14];
467
- return [4 /*yield*/, sleep(frame_processing_delay)];
468
- case 13:
469
- _d.sent();
470
- _d.label = 14;
471
- case 14: return [4 /*yield*/, waitForEventLoop_1.waitForEventLoop()];
472
- case 15:
473
- _d.sent();
474
- reportRenderProgress();
475
- _d.label = 16;
476
- case 16:
477
- i++;
478
- return [3 /*break*/, 9];
479
- case 17:
480
- if (videoFrameReader) {
481
- videoFrameReader.kill();
482
- }
483
- if (concatFilePath) {
484
- video_1.cleanupConcatFile(concatFilePath);
485
- }
486
- ffmpegVideoWriter.stdin.end();
487
- return [4 /*yield*/, exitPromise];
488
- case 18:
489
- _c = _d.sent(), exitCode = _c.exitCode, ffmpegExitReason = _c.reason;
490
- lastExitCode = exitCode;
491
- if (!exitReason && ffmpegExitReason) {
492
- exitReason = ffmpegExitReason;
493
- }
494
- if (lastExitCode === 0 && !stoppedEarly) {
495
- reportRenderProgress();
496
- }
497
- if (lastExitCode === 0 && !stoppedEarly) {
498
- outputVideoFiles.push(pass.outPath);
499
- }
500
- if (stoppedEarly || lastExitCode !== 0) {
501
- if (exitReason && lastExitCode === 0) {
502
- lastExitCode = 1;
503
- }
504
- return [3 /*break*/, 20];
505
- }
506
- _d.label = 19;
507
- case 19:
508
- _i++;
509
- return [3 /*break*/, 7];
510
- case 20:
511
- if (lastExitCode === 0 && outputVideoFiles.length === passes.length) {
512
- reportProgress(100);
513
- }
514
- if (exitReason) {
515
- console.error(exitReason);
516
- }
517
- resolve({ exitCode: lastExitCode, reason: exitReason, outputVideoFiles: outputVideoFiles });
518
- return [3 /*break*/, 22];
519
- case 21:
520
- if (subtitleFilePath && subtitleFileIsTemporary) {
521
- video_1.cleanupTempFile(subtitleFilePath);
522
- }
523
- return [7 /*endfinally*/];
524
- case 22: return [2 /*return*/];
525
- }
526
- });
527
- }); });
528
- };
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
+ };
529
532
  //# sourceMappingURL=index.js.map