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/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.0.0",
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