nodejs-audio-visualizer 3.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": "3.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
@@ -11,6 +11,11 @@ export const defaults = {
11
11
  spectrumRotation: 'mirror',
12
12
  spectrumBusMargin: 12,
13
13
  spectrumOpacity: '80%',
14
+ polarX: 'center',
15
+ polarY: 'middle',
16
+ polarInnerRadius: 100,
17
+ polarMaxBarLength: 160,
18
+ polarBarWidth: 15,
14
19
  };
15
20
 
16
21
  type RelativePositionValue = 0 | 0.5 | 1;
@@ -40,7 +45,13 @@ export const getAudioFilePath = (config: Config) =>
40
45
  path.resolve(config.audio.path);
41
46
 
42
47
  export const getBackgroundImagePath = (config: Config) =>
43
- 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;
44
55
 
45
56
  export const getOutVideoPath = (config: Config) =>
46
57
  path.resolve(config.outVideo.path);
@@ -48,6 +59,9 @@ export const getOutVideoPath = (config: Config) =>
48
59
  export const getFPS = (config: Config) =>
49
60
  config.outVideo.fps || defaults.fps;
50
61
 
62
+ export const getOutputResolution = (config: Config) =>
63
+ config.outVideo.resolution;
64
+
51
65
  export const getSpectrumBusMargin = () => defaults.spectrumBusMargin;
52
66
 
53
67
  const getSpectrumWidth = (config: Config) =>
@@ -243,3 +257,105 @@ export const getFfmpeg_preset = (config: Config) =>
243
257
  export const getFrame_processing_delay = (config: Config) =>
244
258
  config.tweaks && config.tweaks.frame_processing_delay;
245
259
 
260
+ // Polar visualizer config getters
261
+ const getPolarX = (config: Config) =>
262
+ parseNumberIfPossible(
263
+ (config.outVideo.polar && config.outVideo.polar.x) ||
264
+ defaults.polarX
265
+ );
266
+
267
+ const getPolarY = (config: Config) =>
268
+ parseNumberIfPossible(
269
+ (config.outVideo.polar && config.outVideo.polar.y) ||
270
+ defaults.polarY
271
+ );
272
+
273
+ export const getPolarXAbsolute = (
274
+ config: Config,
275
+ backgroundImageWidth: number
276
+ ) => {
277
+ const polarX = getPolarX(config);
278
+ if (typeof polarX !== 'string') {
279
+ return polarX;
280
+ }
281
+ try {
282
+ const polarXParsed = parsePositionAlias(polarX);
283
+ if (polarXParsed.value === 0) {
284
+ const polarLength = getPolarMaxBarLength(config);
285
+ const polarRadius = getPolarInnerRadius(config);
286
+ return polarRadius + polarLength;
287
+ }
288
+ if (polarXParsed.value === 1) {
289
+ const polarLength = getPolarMaxBarLength(config);
290
+ const polarRadius = getPolarInnerRadius(config);
291
+ return backgroundImageWidth - polarRadius - polarLength;
292
+ }
293
+ return polarXParsed.value * backgroundImageWidth;
294
+ } catch {
295
+ throw new Error(`Invalid polar x value: ${polarX}. Valid values: ${getValidPositionAliasValues()}.`);
296
+ }
297
+ };
298
+
299
+ export const getPolarYAbsolute = (
300
+ config: Config,
301
+ backgroundImageHeight: number
302
+ ) => {
303
+ const polarY = getPolarY(config);
304
+ if (typeof polarY !== 'string') {
305
+ return polarY;
306
+ }
307
+ try {
308
+ const polarYParsed = parsePositionAlias(polarY);
309
+ if (polarYParsed.value === 0) {
310
+ const polarLength = getPolarMaxBarLength(config);
311
+ const polarRadius = getPolarInnerRadius(config);
312
+ return polarRadius + polarLength;
313
+ }
314
+ if (polarYParsed.value === 1) {
315
+ const polarLength = getPolarMaxBarLength(config);
316
+ const polarRadius = getPolarInnerRadius(config);
317
+ return backgroundImageHeight - polarRadius - polarLength;
318
+ }
319
+ return polarYParsed.value * backgroundImageHeight;
320
+ } catch {
321
+ throw new Error(`Invalid polar y value: ${polarY}. Valid values: ${getValidPositionAliasValues()}.`);
322
+ }
323
+ };
324
+
325
+ export const getPolarInnerRadius = (config: Config) =>
326
+ (config.outVideo.polar && config.outVideo.polar.innerRadius) ||
327
+ defaults.polarInnerRadius;
328
+
329
+ export const getPolarMaxBarLength = (config: Config) =>
330
+ (config.outVideo.polar && config.outVideo.polar.maxBarLength) ||
331
+ defaults.polarMaxBarLength;
332
+
333
+ export const getPolarBarWidth = (config: Config) =>
334
+ (config.outVideo.polar && config.outVideo.polar.barWidth) ||
335
+ defaults.polarBarWidth;
336
+
337
+ export const getPolarEffect = (config: Config) =>
338
+ config.outVideo.polar?.effect;
339
+
340
+ export const getPolarColor = (config: Config) =>
341
+ config.outVideo.polar && config.outVideo.polar.color;
342
+
343
+ export const getPolarOpacity = (config: Config) =>
344
+ (config.outVideo.polar && config.outVideo.polar.opacity) ||
345
+ defaults.spectrumOpacity;
346
+
347
+ export const getPolarOpacityParsed = (config: Config) => {
348
+ const polarOpacity = getPolarOpacity(config);
349
+ if (!checkIsPercentValue(polarOpacity)) {
350
+ throw new Error(`Invalid polar opacity value: '${polarOpacity}'. Use string percent value, for example '80%'.`);
351
+ }
352
+ const percentValue = parseInt(polarOpacity);
353
+ if (
354
+ (percentValue < 0) ||
355
+ (percentValue > 100)
356
+ ) {
357
+ throw new Error(`Invalid polar opacity value: '${polarOpacity}'. Percent values must be in range from '0%' to '100%'.`);
358
+ }
359
+ return percentValue / 100;
360
+ };
361
+