nodejs-audio-visualizer 4.0.0 → 4.1.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.
- package/README.md +8 -2
- package/dist/beats.d.ts +19 -0
- package/dist/beats.js +44 -0
- package/dist/beats.js.map +1 -0
- package/dist/bpmEncoder.d.ts +4 -0
- package/dist/bpmEncoder.js +65 -43
- package/dist/bpmEncoder.js.map +1 -1
- package/dist/config.d.ts +7 -1
- package/dist/config.js +8 -1
- package/dist/config.js.map +1 -1
- package/dist/image.d.ts +2 -1
- package/dist/image.js +42 -5
- package/dist/image.js.map +1 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +191 -46
- package/dist/index.js.map +1 -1
- package/dist/video.d.ts +41 -1
- package/dist/video.js +244 -0
- package/dist/video.js.map +1 -1
- package/package.json +2 -2
- package/src/beats.ts +68 -0
- package/src/bpmEncoder.ts +57 -31
- package/src/config.ts +10 -1
- package/src/image.ts +46 -4
- package/src/index.ts +253 -34
- package/src/video.ts +302 -1
package/dist/video.js
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __spreadArrays = (this && this.__spreadArrays) || function () {
|
|
3
|
+
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
|
|
4
|
+
for (var r = Array(s), k = 0, i = 0; i < il; i++)
|
|
5
|
+
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
|
|
6
|
+
r[k] = a[j];
|
|
7
|
+
return r;
|
|
8
|
+
};
|
|
2
9
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
10
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
11
|
};
|
|
5
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
13
|
var child_process_1 = require("child_process");
|
|
14
|
+
var fs_1 = require("fs");
|
|
15
|
+
var path_1 = require("path");
|
|
16
|
+
var os_1 = require("os");
|
|
7
17
|
var ffmpeg_static_1 = __importDefault(require("ffmpeg-static"));
|
|
8
18
|
exports.spawnFfmpegVideoWriter = function (config) {
|
|
9
19
|
if (!ffmpeg_static_1.default) {
|
|
@@ -27,6 +37,139 @@ exports.spawnFfmpegVideoWriter = function (config) {
|
|
|
27
37
|
}
|
|
28
38
|
return ffmpeg;
|
|
29
39
|
};
|
|
40
|
+
exports.detectSceneChanges = function (videoPath, threshold) {
|
|
41
|
+
if (threshold === void 0) { threshold = 0.4; }
|
|
42
|
+
return new Promise(function (resolve, reject) {
|
|
43
|
+
if (!ffmpeg_static_1.default) {
|
|
44
|
+
reject(new Error('ffmpeg path not found'));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
var ffmpeg = child_process_1.spawn(ffmpeg_static_1.default, [
|
|
48
|
+
'-i', videoPath,
|
|
49
|
+
'-filter:v', "select='gt(scene," + threshold + ")',showinfo",
|
|
50
|
+
'-f', 'null',
|
|
51
|
+
'-'
|
|
52
|
+
]);
|
|
53
|
+
var stderr = '';
|
|
54
|
+
ffmpeg.stderr.on('data', function (data) { stderr += data.toString(); });
|
|
55
|
+
ffmpeg.on('close', function () {
|
|
56
|
+
var sceneChanges = [];
|
|
57
|
+
for (var _i = 0, _a = stderr.split('\n'); _i < _a.length; _i++) {
|
|
58
|
+
var line = _a[_i];
|
|
59
|
+
if (!line.includes('Parsed_showinfo'))
|
|
60
|
+
continue;
|
|
61
|
+
var nMatch = line.match(/n:\s*(\d+)/);
|
|
62
|
+
var ptsMatch = line.match(/pts:\s*(\d+)/);
|
|
63
|
+
var ptsTimeMatch = line.match(/pts_time:\s*([\d.]+)/);
|
|
64
|
+
if (nMatch && ptsTimeMatch) {
|
|
65
|
+
sceneChanges.push({
|
|
66
|
+
frameNumber: parseInt(nMatch[1]),
|
|
67
|
+
pts: ptsMatch ? parseInt(ptsMatch[1]) : 0,
|
|
68
|
+
ptsTime: parseFloat(ptsTimeMatch[1]),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
resolve(sceneChanges);
|
|
73
|
+
});
|
|
74
|
+
ffmpeg.on('error', reject);
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
exports.getVideoInfo = function (videoPath) {
|
|
78
|
+
return new Promise(function (resolvePromise, reject) {
|
|
79
|
+
if (!ffmpeg_static_1.default) {
|
|
80
|
+
reject(new Error('ffmpeg path not found'));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
var ffmpeg = child_process_1.spawn(ffmpeg_static_1.default, ['-i', videoPath, '-hide_banner']);
|
|
84
|
+
var stderr = '';
|
|
85
|
+
ffmpeg.stderr.on('data', function (data) { stderr += data.toString(); });
|
|
86
|
+
ffmpeg.on('close', function () {
|
|
87
|
+
var dimMatch = stderr.match(/Stream.*Video:.*?(\d{2,5})x(\d{2,5})/);
|
|
88
|
+
if (!dimMatch) {
|
|
89
|
+
reject(new Error("Could not determine video dimensions for: " + videoPath));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
var durMatch = stderr.match(/Duration:\s*(\d+):(\d+):([\d.]+)/);
|
|
93
|
+
var duration = durMatch
|
|
94
|
+
? parseInt(durMatch[1]) * 3600 + parseInt(durMatch[2]) * 60 + parseFloat(durMatch[3])
|
|
95
|
+
: 0;
|
|
96
|
+
resolvePromise({
|
|
97
|
+
width: parseInt(dimMatch[1]),
|
|
98
|
+
height: parseInt(dimMatch[2]),
|
|
99
|
+
duration: duration,
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
var isOrientationMismatch = function (sw, sh, tw, th) {
|
|
105
|
+
return (sw > sh && tw < th) || (sw < sh && tw > th);
|
|
106
|
+
};
|
|
107
|
+
exports.buildVideoFilter = function (sourceWidth, sourceHeight, targetWidth, targetHeight) {
|
|
108
|
+
if (!isOrientationMismatch(sourceWidth, sourceHeight, targetWidth, targetHeight)) {
|
|
109
|
+
return "scale=" + targetWidth + ":" + targetHeight;
|
|
110
|
+
}
|
|
111
|
+
var targetAspect = targetWidth / targetHeight;
|
|
112
|
+
var makeEven = function (n) { return Math.floor(n / 2) * 2; };
|
|
113
|
+
var cropW, cropH, cropX, cropY;
|
|
114
|
+
if (sourceWidth / sourceHeight > targetAspect) {
|
|
115
|
+
cropW = makeEven(Math.round(sourceHeight * targetAspect));
|
|
116
|
+
cropH = makeEven(sourceHeight);
|
|
117
|
+
cropX = Math.floor((sourceWidth - cropW) / 2);
|
|
118
|
+
cropY = Math.floor((sourceHeight - cropH) / 2);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
cropW = makeEven(sourceWidth);
|
|
122
|
+
cropH = makeEven(Math.round(sourceWidth / targetAspect));
|
|
123
|
+
cropX = Math.floor((sourceWidth - cropW) / 2);
|
|
124
|
+
cropY = Math.floor((sourceHeight - cropH) / 2);
|
|
125
|
+
}
|
|
126
|
+
return "crop=" + cropW + ":" + cropH + ":" + cropX + ":" + cropY + ",scale=" + targetWidth + ":" + targetHeight;
|
|
127
|
+
};
|
|
128
|
+
exports.spawnVideoFrameReader = function (config) {
|
|
129
|
+
if (!ffmpeg_static_1.default) {
|
|
130
|
+
throw new Error('ffmpeg path not found');
|
|
131
|
+
}
|
|
132
|
+
var args = [
|
|
133
|
+
'-stream_loop', '-1',
|
|
134
|
+
'-i', config.videoPath,
|
|
135
|
+
'-r', "" + config.fps,
|
|
136
|
+
'-frames:v', "" + config.totalFrames,
|
|
137
|
+
];
|
|
138
|
+
if (config.width && config.height) {
|
|
139
|
+
var filter = config.sourceWidth && config.sourceHeight
|
|
140
|
+
? exports.buildVideoFilter(config.sourceWidth, config.sourceHeight, config.width, config.height)
|
|
141
|
+
: "scale=" + config.width + ":" + config.height;
|
|
142
|
+
args.push('-vf', filter);
|
|
143
|
+
}
|
|
144
|
+
args.push('-f', 'rawvideo', '-pix_fmt', 'bgr24', '-v', 'quiet', 'pipe:1');
|
|
145
|
+
return child_process_1.spawn(ffmpeg_static_1.default, args);
|
|
146
|
+
};
|
|
147
|
+
exports.readVideoFrame = function (stream, frameSize) {
|
|
148
|
+
return new Promise(function (resolve) {
|
|
149
|
+
var tryRead = function () {
|
|
150
|
+
var data = stream.read(frameSize);
|
|
151
|
+
if (data !== null) {
|
|
152
|
+
resolve(data.length === frameSize ? data : null);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
var onReadable = function () {
|
|
156
|
+
cleanup();
|
|
157
|
+
tryRead();
|
|
158
|
+
};
|
|
159
|
+
var onEnd = function () {
|
|
160
|
+
cleanup();
|
|
161
|
+
resolve(null);
|
|
162
|
+
};
|
|
163
|
+
var cleanup = function () {
|
|
164
|
+
stream.removeListener('readable', onReadable);
|
|
165
|
+
stream.removeListener('end', onEnd);
|
|
166
|
+
};
|
|
167
|
+
stream.once('readable', onReadable);
|
|
168
|
+
stream.once('end', onEnd);
|
|
169
|
+
};
|
|
170
|
+
tryRead();
|
|
171
|
+
});
|
|
172
|
+
};
|
|
30
173
|
exports.getProgress = function (onProgress) {
|
|
31
174
|
return function (stderrOutput) {
|
|
32
175
|
var matchResult = stderrOutput.toString().match(/frame=[ ]+(\d+)/);
|
|
@@ -48,4 +191,105 @@ exports.calculateProgress = function (framesCount, callback) {
|
|
|
48
191
|
exports.waitDrain = function (stream) {
|
|
49
192
|
return new Promise(function (resolve) { return stream.once('drain', resolve); });
|
|
50
193
|
};
|
|
194
|
+
var mergeSmallScenes = function (sceneChanges, videoDuration, minDuration) {
|
|
195
|
+
if (minDuration === void 0) { minDuration = 2; }
|
|
196
|
+
if (sceneChanges.length === 0)
|
|
197
|
+
return [];
|
|
198
|
+
var merged = [sceneChanges[0]];
|
|
199
|
+
for (var i = 1; i < sceneChanges.length; i++) {
|
|
200
|
+
var lastKept = merged[merged.length - 1];
|
|
201
|
+
var gap = sceneChanges[i].ptsTime - lastKept.ptsTime;
|
|
202
|
+
if (gap >= minDuration) {
|
|
203
|
+
merged.push(sceneChanges[i]);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
var lastScene = merged[merged.length - 1];
|
|
207
|
+
if (videoDuration - lastScene.ptsTime < minDuration && merged.length > 1) {
|
|
208
|
+
merged.pop();
|
|
209
|
+
}
|
|
210
|
+
return merged;
|
|
211
|
+
};
|
|
212
|
+
exports.buildBeatSyncedSegments = function (beatFrameIndices, totalFrames, sceneChanges, videoDuration) {
|
|
213
|
+
if (sceneChanges.length === 0) {
|
|
214
|
+
return [
|
|
215
|
+
{
|
|
216
|
+
outputStartFrame: 0,
|
|
217
|
+
videoSeekSeconds: 0,
|
|
218
|
+
frameCount: totalFrames,
|
|
219
|
+
},
|
|
220
|
+
];
|
|
221
|
+
}
|
|
222
|
+
var consolidated = mergeSmallScenes(sceneChanges, videoDuration);
|
|
223
|
+
var seekPositions;
|
|
224
|
+
if (consolidated.length >= 2) {
|
|
225
|
+
seekPositions = consolidated.map(function (sc) { return sc.ptsTime; });
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
var count_1 = Math.max(10, Math.ceil(videoDuration / 3));
|
|
229
|
+
seekPositions = Array.from({ length: count_1 }, function (_, i) { return (i * videoDuration) / count_1; });
|
|
230
|
+
}
|
|
231
|
+
var boundaries = __spreadArrays([0], beatFrameIndices);
|
|
232
|
+
if (boundaries[boundaries.length - 1] !== totalFrames) {
|
|
233
|
+
boundaries.push(totalFrames);
|
|
234
|
+
}
|
|
235
|
+
var unique = Array.from(new Set(boundaries)).sort(function (a, b) { return a - b; });
|
|
236
|
+
var segments = [];
|
|
237
|
+
for (var i = 0; i < unique.length - 1; i++) {
|
|
238
|
+
segments.push({
|
|
239
|
+
outputStartFrame: unique[i],
|
|
240
|
+
videoSeekSeconds: seekPositions[i % seekPositions.length],
|
|
241
|
+
frameCount: unique[i + 1] - unique[i],
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
return segments;
|
|
245
|
+
};
|
|
246
|
+
exports.writeConcatFile = function (segments, videoPath, videoDuration, fps) {
|
|
247
|
+
var absVideoPath = path_1.resolve(videoPath).replace(/\\/g, '/');
|
|
248
|
+
var escaped = absVideoPath.replace(/'/g, "'\\''");
|
|
249
|
+
var content = '';
|
|
250
|
+
for (var _i = 0, segments_1 = segments; _i < segments_1.length; _i++) {
|
|
251
|
+
var seg = segments_1[_i];
|
|
252
|
+
var segDuration = seg.frameCount / fps;
|
|
253
|
+
var remaining = segDuration;
|
|
254
|
+
var pos = seg.videoSeekSeconds % videoDuration;
|
|
255
|
+
while (remaining > 0.001) {
|
|
256
|
+
var available = videoDuration - pos;
|
|
257
|
+
var take = Math.min(remaining, available);
|
|
258
|
+
content += "file '" + escaped + "'\n";
|
|
259
|
+
content += "inpoint " + pos.toFixed(6) + "\n";
|
|
260
|
+
content += "outpoint " + (pos + take).toFixed(6) + "\n\n";
|
|
261
|
+
remaining -= take;
|
|
262
|
+
pos = 0;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
var concatPath = path_1.join(os_1.tmpdir(), "av-concat-" + Date.now() + ".txt");
|
|
266
|
+
fs_1.writeFileSync(concatPath, content, 'utf-8');
|
|
267
|
+
return concatPath;
|
|
268
|
+
};
|
|
269
|
+
exports.spawnConcatVideoFrameReader = function (config) {
|
|
270
|
+
if (!ffmpeg_static_1.default) {
|
|
271
|
+
throw new Error('ffmpeg path not found');
|
|
272
|
+
}
|
|
273
|
+
var args = [
|
|
274
|
+
'-f', 'concat',
|
|
275
|
+
'-safe', '0',
|
|
276
|
+
'-i', config.concatFilePath,
|
|
277
|
+
'-r', "" + config.fps,
|
|
278
|
+
'-frames:v', "" + config.totalFrames,
|
|
279
|
+
];
|
|
280
|
+
if (config.width && config.height) {
|
|
281
|
+
var filter = config.sourceWidth && config.sourceHeight
|
|
282
|
+
? exports.buildVideoFilter(config.sourceWidth, config.sourceHeight, config.width, config.height)
|
|
283
|
+
: "scale=" + config.width + ":" + config.height;
|
|
284
|
+
args.push('-vf', filter);
|
|
285
|
+
}
|
|
286
|
+
args.push('-f', 'rawvideo', '-pix_fmt', 'bgr24', '-v', 'quiet', 'pipe:1');
|
|
287
|
+
return child_process_1.spawn(ffmpeg_static_1.default, args);
|
|
288
|
+
};
|
|
289
|
+
exports.cleanupConcatFile = function (filePath) {
|
|
290
|
+
try {
|
|
291
|
+
fs_1.unlinkSync(filePath);
|
|
292
|
+
}
|
|
293
|
+
catch (_a) { }
|
|
294
|
+
};
|
|
51
295
|
//# sourceMappingURL=video.js.map
|
package/dist/video.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"video.js","sourceRoot":"","sources":["../src/video.ts"],"names":[],"mappings":";;;;;AACA,+CAAsC;AACtC,gEAAuC;AAW1B,QAAA,sBAAsB,GAAG,UAAC,MAA+B;IACpE,IAAI,CAAC,uBAAU,EAAE;QACf,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;KAC1C;IACD,IAAM,GAAG,GAAG,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC;IAC/B,IAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,QAAQ,CAAC;IACzC,IAAM,IAAI,GAAG;QACX,IAAI;QACJ,IAAI,EAAE,MAAM,CAAC,aAAa;QAC1B,MAAM,EAAE,GAAG;QACX,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,SAAS;QACtD,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,KAAG,MAAM,CAAC,GAAK,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,aAAa;QACxG,IAAI,EAAE,KAAG,MAAM,CAAC,GAAK;QACrB,IAAI,EAAE,GAAG;KACV,CAAC;IACF,IAAM,MAAM,GAAG,qBAAK,CAAC,uBAAU,EAAE,IAAI,CAAC,CAAC;IACvC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAClC,IAAI,MAAM,CAAC,QAAQ,EAAE;QACnB,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;KAC3C;IACD,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAEW,QAAA,WAAW,GAAG,UAAC,UAAyC;IACnE,OAAA,UAAC,YAAoB;QACnB,IAAM,WAAW,GAAG,YAAY,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;QACrE,IAAI,CAAC,WAAW,EAAE;YAChB,OAAO;SACR;QACD,IAAM,YAAY,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;QACrC,IAAI,KAAK,CAAC,YAAY,CAAC,EAAE;YACvB,OAAO;SACR;QACD,UAAU,CAAC,YAAY,CAAC,CAAC;IAC3B,CAAC;AAVD,CAUC,CAAC;AAES,QAAA,iBAAiB,GAAG,UAAC,WAAmB,EAAE,QAAmC;IACxF,OAAA,UAAC,YAAoB;QACnB,OAAA,QAAQ,CACN,CAAC,CAAC,YAAY,GAAG,WAAW,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAC/C;IAFD,CAEC;AAHH,CAGG,CAAC;AAEO,QAAA,SAAS,GAAG,UAAC,MAAgB;IACxC,OAAA,IAAI,OAAO,CAAO,UAAA,OAAO,IAAI,OAAA,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,EAA7B,CAA6B,CAAC;AAA3D,CAA2D,CAAC"}
|
|
1
|
+
{"version":3,"file":"video.js","sourceRoot":"","sources":["../src/video.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodejs-audio-visualizer",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"repository": {
|
|
22
22
|
"type": "git",
|
|
23
|
-
"url": "git+https://github.com/VladislavPetyukevich/audio-visualizer"
|
|
23
|
+
"url": "git+https://github.com/VladislavPetyukevich/audio-visualizer.git"
|
|
24
24
|
},
|
|
25
25
|
"homepage": "https://github.com/VladislavPetyukevich/audio-visualizer#readme",
|
|
26
26
|
"keywords": [
|
package/src/beats.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export interface BeatInfo {
|
|
2
|
+
isBeat: boolean;
|
|
3
|
+
intensity: number;
|
|
4
|
+
energy: number;
|
|
5
|
+
threshold: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface BeatDetectorOptions {
|
|
9
|
+
/** Number of past frames to average for adaptive threshold (~1.5s by default) */
|
|
10
|
+
historySize?: number;
|
|
11
|
+
/** Energy must exceed average * this multiplier to trigger a beat */
|
|
12
|
+
thresholdMultiplier?: number;
|
|
13
|
+
/** How fast the beat intensity fades between beats (0-1, lower = faster fade) */
|
|
14
|
+
decayRate?: number;
|
|
15
|
+
/** Minimum frames between consecutive beats (~150ms by default) */
|
|
16
|
+
cooldownFrames?: number;
|
|
17
|
+
/** How many low-frequency spectrum bands contribute to energy (sub-bass + bass) */
|
|
18
|
+
bassEndIndex?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const createBeatDetector = (fps: number, options?: BeatDetectorOptions) => {
|
|
22
|
+
const historySize = options?.historySize ?? Math.round(fps * 1.5);
|
|
23
|
+
const thresholdMultiplier = options?.thresholdMultiplier ?? 1.4;
|
|
24
|
+
const decayRate = options?.decayRate ?? 0.85;
|
|
25
|
+
const cooldownFrames = options?.cooldownFrames ?? Math.round(fps * 0.15);
|
|
26
|
+
const bassEndIndex = options?.bassEndIndex ?? 8;
|
|
27
|
+
|
|
28
|
+
const energyHistory: number[] = [];
|
|
29
|
+
let framesSinceLastBeat = cooldownFrames;
|
|
30
|
+
let decayingIntensity = 0;
|
|
31
|
+
|
|
32
|
+
return (spectrum: number[]): BeatInfo => {
|
|
33
|
+
const bassSpectrum = spectrum.slice(0, Math.min(bassEndIndex, spectrum.length));
|
|
34
|
+
const energy = bassSpectrum.reduce((sum, val) => sum + val * val, 0) / bassSpectrum.length;
|
|
35
|
+
|
|
36
|
+
energyHistory.push(energy);
|
|
37
|
+
if (energyHistory.length > historySize) {
|
|
38
|
+
energyHistory.shift();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const avgEnergy = energyHistory.reduce((sum, e) => sum + e, 0) / energyHistory.length;
|
|
42
|
+
const variance = energyHistory.reduce((sum, e) => sum + (e - avgEnergy) ** 2, 0) / energyHistory.length;
|
|
43
|
+
const stdDev = Math.sqrt(variance);
|
|
44
|
+
|
|
45
|
+
const threshold = avgEnergy + stdDev * thresholdMultiplier;
|
|
46
|
+
|
|
47
|
+
framesSinceLastBeat++;
|
|
48
|
+
|
|
49
|
+
const hasEnoughHistory = energyHistory.length >= Math.round(historySize / 3);
|
|
50
|
+
const isAboveThreshold = energy > threshold && hasEnoughHistory;
|
|
51
|
+
const isCooldownOver = framesSinceLastBeat >= cooldownFrames;
|
|
52
|
+
const isBeat = isAboveThreshold && isCooldownOver;
|
|
53
|
+
|
|
54
|
+
if (isBeat) {
|
|
55
|
+
framesSinceLastBeat = 0;
|
|
56
|
+
decayingIntensity = Math.min(1, (energy - threshold) / (stdDev + 1e-6));
|
|
57
|
+
} else {
|
|
58
|
+
decayingIntensity *= decayRate;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
isBeat,
|
|
63
|
+
intensity: decayingIntensity,
|
|
64
|
+
energy,
|
|
65
|
+
threshold,
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
};
|
package/src/bpmEncoder.ts
CHANGED
|
@@ -4,46 +4,42 @@ export interface EncodedBmp {
|
|
|
4
4
|
data: Buffer;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
const writeBmpHeader = (buffer: Buffer, width: number, height: number, rgbSize: number) => {
|
|
8
|
+
const headerInfoSize = 40;
|
|
9
|
+
const offset = 54;
|
|
10
|
+
const fileSize = rgbSize + offset;
|
|
11
|
+
|
|
12
|
+
let pos = 0;
|
|
13
|
+
buffer.write('BM', pos, 2); pos += 2;
|
|
14
|
+
buffer.writeUInt32LE(fileSize, pos); pos += 4;
|
|
15
|
+
buffer.writeUInt32LE(0, pos); pos += 4;
|
|
16
|
+
buffer.writeUInt32LE(offset, pos); pos += 4;
|
|
17
|
+
|
|
18
|
+
buffer.writeUInt32LE(headerInfoSize, pos); pos += 4;
|
|
19
|
+
buffer.writeUInt32LE(width, pos); pos += 4;
|
|
20
|
+
buffer.writeInt32LE(-height, pos); pos += 4;
|
|
21
|
+
buffer.writeUInt16LE(1, pos); pos += 2;
|
|
22
|
+
buffer.writeUInt16LE(24, pos); pos += 2;
|
|
23
|
+
buffer.writeUInt32LE(0, pos); pos += 4;
|
|
24
|
+
buffer.writeUInt32LE(rgbSize, pos); pos += 4;
|
|
25
|
+
buffer.writeUInt32LE(0, pos); pos += 4;
|
|
26
|
+
buffer.writeUInt32LE(0, pos); pos += 4;
|
|
27
|
+
buffer.writeUInt32LE(0, pos); pos += 4;
|
|
28
|
+
buffer.writeUInt32LE(0, pos); pos += 4;
|
|
29
|
+
|
|
30
|
+
return offset;
|
|
31
|
+
};
|
|
32
|
+
|
|
7
33
|
export const createBpmEncoder = (imgData: { width: number; height: number; }) => {
|
|
8
34
|
const width = imgData.width;
|
|
9
35
|
const height = imgData.height;
|
|
10
36
|
const extraBytes = width % 4;
|
|
11
37
|
const rgbSize = height * (3 * width + extraBytes);
|
|
12
|
-
const headerInfoSize = 40;
|
|
13
|
-
|
|
14
|
-
/******************header***********************/
|
|
15
|
-
const flag = 'BM';
|
|
16
|
-
const reserved = 0;
|
|
17
38
|
const offset = 54;
|
|
18
|
-
const fileSize = rgbSize + offset;
|
|
19
|
-
const planes = 1;
|
|
20
|
-
const bitPP = 24;
|
|
21
|
-
const compress = 0;
|
|
22
|
-
const hr = 0;
|
|
23
|
-
const vr = 0;
|
|
24
|
-
const colors = 0;
|
|
25
|
-
const importantColors = 0;
|
|
26
39
|
|
|
27
40
|
return (buffer: Buffer): EncodedBmp => {
|
|
28
41
|
const tempBuffer = Buffer.alloc(offset + rgbSize);
|
|
29
|
-
|
|
30
|
-
let pos = 0;
|
|
31
|
-
tempBuffer.write(flag, pos, 2); pos += 2;
|
|
32
|
-
tempBuffer.writeUInt32LE(fileSize, pos); pos += 4;
|
|
33
|
-
tempBuffer.writeUInt32LE(reserved, pos); pos += 4;
|
|
34
|
-
tempBuffer.writeUInt32LE(offset, pos); pos += 4;
|
|
35
|
-
|
|
36
|
-
tempBuffer.writeUInt32LE(headerInfoSize, pos); pos += 4;
|
|
37
|
-
tempBuffer.writeUInt32LE(width, pos); pos += 4;
|
|
38
|
-
tempBuffer.writeInt32LE(-height, pos); pos += 4;
|
|
39
|
-
tempBuffer.writeUInt16LE(planes, pos); pos += 2;
|
|
40
|
-
tempBuffer.writeUInt16LE(bitPP, pos); pos += 2;
|
|
41
|
-
tempBuffer.writeUInt32LE(compress, pos); pos += 4;
|
|
42
|
-
tempBuffer.writeUInt32LE(rgbSize, pos); pos += 4;
|
|
43
|
-
tempBuffer.writeUInt32LE(hr, pos); pos += 4;
|
|
44
|
-
tempBuffer.writeUInt32LE(vr, pos); pos += 4;
|
|
45
|
-
tempBuffer.writeUInt32LE(colors, pos); pos += 4;
|
|
46
|
-
tempBuffer.writeUInt32LE(importantColors, pos); pos += 4;
|
|
42
|
+
const pos = writeBmpHeader(tempBuffer, width, height, rgbSize);
|
|
47
43
|
|
|
48
44
|
let i = 0;
|
|
49
45
|
const rowBytes = 3 * width + extraBytes;
|
|
@@ -69,3 +65,33 @@ export const createBpmEncoder = (imgData: { width: number; height: number; }) =>
|
|
|
69
65
|
};
|
|
70
66
|
};
|
|
71
67
|
};
|
|
68
|
+
|
|
69
|
+
export const createBgrFrameEncoder = (imgData: { width: number; height: number }) => {
|
|
70
|
+
const width = imgData.width;
|
|
71
|
+
const height = imgData.height;
|
|
72
|
+
const extraBytes = width % 4;
|
|
73
|
+
const rowBytes = 3 * width + extraBytes;
|
|
74
|
+
const rgbSize = height * rowBytes;
|
|
75
|
+
const offset = 54;
|
|
76
|
+
|
|
77
|
+
const tempBuffer = Buffer.alloc(offset + rgbSize);
|
|
78
|
+
const pos = writeBmpHeader(tempBuffer, width, height, rgbSize);
|
|
79
|
+
|
|
80
|
+
return (bgrBuffer: Buffer): EncodedBmp => {
|
|
81
|
+
if (extraBytes === 0) {
|
|
82
|
+
bgrBuffer.copy(tempBuffer, pos, 0, width * height * 3);
|
|
83
|
+
} else {
|
|
84
|
+
for (let y = 0; y < height; y++) {
|
|
85
|
+
const srcOffset = y * width * 3;
|
|
86
|
+
const dstOffset = pos + y * rowBytes;
|
|
87
|
+
bgrBuffer.copy(tempBuffer, dstOffset, srcOffset, srcOffset + width * 3);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
shiftPos: pos,
|
|
93
|
+
rowBytes,
|
|
94
|
+
data: tempBuffer
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
};
|
package/src/config.ts
CHANGED
|
@@ -45,7 +45,13 @@ export const getAudioFilePath = (config: Config) =>
|
|
|
45
45
|
path.resolve(config.audio.path);
|
|
46
46
|
|
|
47
47
|
export const getBackgroundImagePath = (config: Config) =>
|
|
48
|
-
path.resolve(config.image.path);
|
|
48
|
+
config.image ? path.resolve(config.image.path) : undefined;
|
|
49
|
+
|
|
50
|
+
export const getBackgroundVideoPath = (config: Config) =>
|
|
51
|
+
config.video ? path.resolve(config.video.path) : undefined;
|
|
52
|
+
|
|
53
|
+
export const getAutoEditVideo = (config: Config) =>
|
|
54
|
+
config.video?.autoEdit === true;
|
|
49
55
|
|
|
50
56
|
export const getOutVideoPath = (config: Config) =>
|
|
51
57
|
path.resolve(config.outVideo.path);
|
|
@@ -53,6 +59,9 @@ export const getOutVideoPath = (config: Config) =>
|
|
|
53
59
|
export const getFPS = (config: Config) =>
|
|
54
60
|
config.outVideo.fps || defaults.fps;
|
|
55
61
|
|
|
62
|
+
export const getOutputResolution = (config: Config) =>
|
|
63
|
+
config.outVideo.resolution;
|
|
64
|
+
|
|
56
65
|
export const getSpectrumBusMargin = () => defaults.spectrumBusMargin;
|
|
57
66
|
|
|
58
67
|
const getSpectrumWidth = (config: Config) =>
|
package/src/image.ts
CHANGED
|
@@ -459,7 +459,6 @@ export const createPolarVisualizerFrameGenerator = () => {
|
|
|
459
459
|
|
|
460
460
|
const rgbSpectrumColor = (typeof color === 'string') ? hexToRgb(color) : color;
|
|
461
461
|
|
|
462
|
-
// Draw shadow/volume effect
|
|
463
462
|
if (spectrumEffect === 'volume') {
|
|
464
463
|
drawPolarSpectrum({
|
|
465
464
|
imageDstBuffer,
|
|
@@ -474,7 +473,6 @@ export const createPolarVisualizerFrameGenerator = () => {
|
|
|
474
473
|
});
|
|
475
474
|
}
|
|
476
475
|
|
|
477
|
-
// Draw smooth/trail effect with previous frame
|
|
478
476
|
if (spectrumEffect === 'smooth' && prevSpectrum) {
|
|
479
477
|
drawPolarSpectrum({
|
|
480
478
|
imageDstBuffer,
|
|
@@ -489,7 +487,6 @@ export const createPolarVisualizerFrameGenerator = () => {
|
|
|
489
487
|
});
|
|
490
488
|
}
|
|
491
489
|
|
|
492
|
-
// Draw main spectrum
|
|
493
490
|
drawPolarSpectrum({
|
|
494
491
|
imageDstBuffer,
|
|
495
492
|
spectrum,
|
|
@@ -508,10 +505,36 @@ export const createPolarVisualizerFrameGenerator = () => {
|
|
|
508
505
|
};
|
|
509
506
|
};
|
|
510
507
|
|
|
511
|
-
export const convertToBmp = async (filePath: string) =>
|
|
508
|
+
export const convertToBmp = async (filePath: string, targetWidth?: number, targetHeight?: number) =>
|
|
512
509
|
new Promise<Buffer>(async (resolve, reject) => {
|
|
513
510
|
try {
|
|
514
511
|
const image = await Jimp.read(filePath);
|
|
512
|
+
if (targetWidth && targetHeight) {
|
|
513
|
+
const sw = image.getWidth();
|
|
514
|
+
const sh = image.getHeight();
|
|
515
|
+
const isOrientationMismatch =
|
|
516
|
+
(sw > sh && targetWidth < targetHeight) ||
|
|
517
|
+
(sw < sh && targetWidth > targetHeight);
|
|
518
|
+
|
|
519
|
+
if (isOrientationMismatch) {
|
|
520
|
+
const targetAspect = targetWidth / targetHeight;
|
|
521
|
+
let cropW: number, cropH: number, cropX: number, cropY: number;
|
|
522
|
+
if (sw / sh > targetAspect) {
|
|
523
|
+
cropW = Math.round(sh * targetAspect);
|
|
524
|
+
cropH = sh;
|
|
525
|
+
cropX = Math.round((sw - cropW) / 2);
|
|
526
|
+
cropY = 0;
|
|
527
|
+
} else {
|
|
528
|
+
cropW = sw;
|
|
529
|
+
cropH = Math.round(sw / targetAspect);
|
|
530
|
+
cropX = 0;
|
|
531
|
+
cropY = Math.round((sh - cropH) / 2);
|
|
532
|
+
}
|
|
533
|
+
image.crop(cropX, cropY, cropW, cropH);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
image.resize(targetWidth, targetHeight);
|
|
537
|
+
}
|
|
515
538
|
image.getBuffer("image/bmp", (err, value) => {
|
|
516
539
|
if (err) {
|
|
517
540
|
reject(err);
|
|
@@ -548,6 +571,25 @@ export const getImageColor = (image: BmpDecoder): Color => {
|
|
|
548
571
|
};
|
|
549
572
|
};
|
|
550
573
|
|
|
574
|
+
export const getVideoFrameColor = (bgrBuffer: Buffer, width: number, height: number): Color => {
|
|
575
|
+
let blueSum = 0;
|
|
576
|
+
let greenSum = 0;
|
|
577
|
+
let redSum = 0;
|
|
578
|
+
const pixelsCount = width * height;
|
|
579
|
+
|
|
580
|
+
for (let i = 0; i < bgrBuffer.length; i += 3) {
|
|
581
|
+
blueSum += bgrBuffer[i];
|
|
582
|
+
greenSum += bgrBuffer[i + 1];
|
|
583
|
+
redSum += bgrBuffer[i + 2];
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
return {
|
|
587
|
+
red: ~~(redSum / pixelsCount),
|
|
588
|
+
green: ~~(greenSum / pixelsCount),
|
|
589
|
+
blue: ~~(blueSum / pixelsCount),
|
|
590
|
+
};
|
|
591
|
+
};
|
|
592
|
+
|
|
551
593
|
export const invertColor = (color: Color) =>
|
|
552
594
|
({ red: 255 - color.red, green: 255 - color.green, blue: 255 - color.blue });
|
|
553
595
|
|