nodejs-audio-visualizer 2.0.0 → 2.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/src/image.ts CHANGED
@@ -18,31 +18,94 @@ interface Size {
18
18
  height: number;
19
19
  }
20
20
 
21
- export const drawRect = (imageDstBuffer: BmpDecoder, position: Position, size: Size, color: Color) => {
21
+ interface MixColorProps {
22
+ color: Color;
23
+ colorOpacity: number;
24
+ backgroundColor: Color;
25
+ }
26
+
27
+ export const mixValues =(value1: number, value1Opacity: number, value2: number) =>
28
+ Math.round(value2 * (1 - value1Opacity) + value1 * value1Opacity);
29
+
30
+ export const mixColors = ({
31
+ color,
32
+ colorOpacity,
33
+ backgroundColor,
34
+ }: MixColorProps): Color => {
35
+ return {
36
+ red: mixValues(color.red, colorOpacity, backgroundColor.red),
37
+ green: mixValues(color.green, colorOpacity, backgroundColor.green),
38
+ blue: mixValues(color.blue, colorOpacity, backgroundColor.blue),
39
+ };
40
+ };
41
+
42
+ interface DrawRectPorps {
43
+ imageDstBuffer: BmpDecoder;
44
+ position: Position;
45
+ size: Size;
46
+ color: Color;
47
+ opacity: number;
48
+ }
49
+
50
+ export const drawRect = ({
51
+ imageDstBuffer,
52
+ position,
53
+ size,
54
+ color,
55
+ opacity,
56
+ }: DrawRectPorps) => {
22
57
  for (var currY = position.y; currY < position.y + size.height; currY++) {
23
58
  for (var currX = position.x; currX < position.x + size.width; currX++) {
24
59
  var idx = (imageDstBuffer.width * currY + currX) << 2;
25
-
26
- imageDstBuffer.data[idx + 1] = color.blue;
27
- imageDstBuffer.data[idx + 2] = color.green;
28
- imageDstBuffer.data[idx + 3] = color.red;
60
+ if (opacity === 1) {
61
+ imageDstBuffer.data[idx + 1] = color.blue;
62
+ imageDstBuffer.data[idx + 2] = color.green;
63
+ imageDstBuffer.data[idx + 3] = color.red;
64
+ continue;
65
+ }
66
+
67
+ const backgroundPixelColor: Color = {
68
+ red: imageDstBuffer.data[idx + 3],
69
+ green: imageDstBuffer.data[idx + 2],
70
+ blue: imageDstBuffer.data[idx + 1],
71
+ };
72
+ const resultColor = mixColors({
73
+ color,
74
+ colorOpacity: opacity,
75
+ backgroundColor: backgroundPixelColor,
76
+ });
77
+ imageDstBuffer.data[idx + 1] = resultColor.blue;
78
+ imageDstBuffer.data[idx + 2] = resultColor.green;
79
+ imageDstBuffer.data[idx + 3] = resultColor.red;
29
80
  }
30
81
  }
31
82
  };
32
83
 
33
- const drawSpectrum = (
34
- imageDstBuffer: BmpDecoder,
35
- spectrum: number[],
36
- size: Size,
37
- position: Position,
38
- rotation: RotationAliasName,
39
- color: Color
40
- ) => {
84
+ interface DrawSpectrumProps {
85
+ imageDstBuffer: BmpDecoder;
86
+ spectrum: number[];
87
+ size: Size;
88
+ position: Position;
89
+ rotation: RotationAliasName;
90
+ margin: number;
91
+ color: Color;
92
+ opacity: number;
93
+ }
94
+
95
+ const drawSpectrum = ({
96
+ imageDstBuffer,
97
+ spectrum,
98
+ size,
99
+ position,
100
+ rotation,
101
+ margin,
102
+ color,
103
+ opacity,
104
+ }: DrawSpectrumProps) => {
41
105
  const busWidth = Math.trunc(size.width / spectrum.length);
42
106
  const left = Math.trunc(position.x);
43
107
  const top = Math.trunc(position.y);
44
108
  const height = Math.trunc(size.height);
45
- const margin = 4;
46
109
 
47
110
  for (let spectrumX = 0; spectrumX < spectrum.length; spectrumX++) {
48
111
  const spectrumValue = spectrum[spectrumX];
@@ -53,24 +116,66 @@ const drawSpectrum = (
53
116
  const rectX = left + busWidth * spectrumX;
54
117
  const rectHeight = Math.trunc(height * spectrumValue);
55
118
  const rectY = (rotation === 'up') ? top + height - rectHeight : top;
56
- drawRect(imageDstBuffer, { x: rectX + margin, y: rectY }, { width: busWidth - margin / 2, height: rectHeight }, color);
119
+ drawRect({
120
+ imageDstBuffer,
121
+ position: { x: rectX + margin, y: rectY },
122
+ size: { width: busWidth - margin / 2, height: rectHeight },
123
+ color,
124
+ opacity,
125
+ });
57
126
  }
58
127
  };
59
128
 
60
- export const createVisualizerFrame = (backgroundImageBuffer: BmpDecoder, spectrum: number[], size: Size, position: Position, rotation: RotationAliasName, color: Color | string) => {
61
- const image = Object.assign({}, backgroundImageBuffer);
62
- image.data = Buffer.from(image.data);
129
+ interface CreateVisualizerFrameProps {
130
+ backgroundImageBuffer: BmpDecoder;
131
+ spectrum: number[];
132
+ size: Size;
133
+ position: Position;
134
+ rotation: RotationAliasName;
135
+ margin: number;
136
+ color: Color | string;
137
+ opacity: number;
138
+ }
139
+
140
+ export const createVisualizerFrame = ({
141
+ backgroundImageBuffer,
142
+ spectrum,
143
+ size,
144
+ position,
145
+ rotation,
146
+ margin,
147
+ color,
148
+ opacity,
149
+ }: CreateVisualizerFrameProps) => {
150
+ const imageDstBuffer = Object.assign({}, backgroundImageBuffer);
151
+ imageDstBuffer.data = Buffer.from(imageDstBuffer.data);
63
152
  const rgbSpectrumColor = (typeof color === 'string') ? hexToRgb(color) : color;
64
- drawSpectrum(image, spectrum, size, position, rotation, rgbSpectrumColor);
65
- return image;
153
+ drawSpectrum({
154
+ imageDstBuffer,
155
+ spectrum,
156
+ size,
157
+ position,
158
+ rotation,
159
+ margin,
160
+ color: rgbSpectrumColor,
161
+ opacity,
162
+ });
163
+ return imageDstBuffer;
66
164
  };
67
165
 
68
166
  export const convertToBmp = async (filePath: string) =>
69
- new Promise<Buffer>(async resolve => {
70
- const image = await Jimp.read(filePath);
71
- image.getBuffer("image/bmp", (err, value) => {
72
- resolve(value);
73
- });
167
+ new Promise<Buffer>(async (resolve, reject) => {
168
+ try {
169
+ const image = await Jimp.read(filePath);
170
+ image.getBuffer("image/bmp", (err, value) => {
171
+ if (err) {
172
+ reject(err);
173
+ }
174
+ resolve(value);
175
+ });
176
+ } catch {
177
+ throw new Error(`Cannot procces image file from path: ${filePath}`);
178
+ }
74
179
  });
75
180
 
76
181
  export const parseImage = (buffer: Buffer) => decode(buffer);
package/src/index.ts CHANGED
@@ -3,11 +3,14 @@ import {
3
3
  getBackgroundImagePath,
4
4
  getOutVideoPath,
5
5
  getFPS,
6
+ getSpectrumBusesCount,
7
+ getSpectrumBusMargin,
6
8
  getSpectrumWidthAbsolute,
7
9
  getSpectrumHeightAbsolute,
8
10
  getSpectrumXAbsolute,
9
11
  getSpectrumYAbsolute,
10
12
  getSpectrumColor,
13
+ getSpectrumOpacityParsed,
11
14
  getFfmpeg_cfr,
12
15
  getFfmpeg_preset,
13
16
  getFrame_processing_delay,
@@ -42,6 +45,7 @@ export interface Config {
42
45
  y?: number | PositionAliasName;
43
46
  rotation?: RotationAliasName;
44
47
  color?: Color | string;
48
+ opacity?: string;
45
49
  }
46
50
  };
47
51
  tweaks?: {
@@ -81,6 +85,8 @@ export const renderAudioVisualizer = (config: Config, onProgress?: (progress: nu
81
85
  throw new Error('ffmpeg didn\'t show audio sample rate');
82
86
  }
83
87
 
88
+ const spectrumBusesCount = getSpectrumBusesCount();
89
+ const spectrumBusMargin = getSpectrumBusMargin();
84
90
  const FPS = getFPS(config);
85
91
  const spectrumWidth =
86
92
  getSpectrumWidthAbsolute(config, backgroundImage.width);
@@ -95,10 +101,10 @@ export const renderAudioVisualizer = (config: Config, onProgress?: (progress: nu
95
101
  const spectrumColor =
96
102
  getSpectrumColor(config) ||
97
103
  invertColor(getImageColor(backgroundImage));
104
+ const spectrumOpacity = getSpectrumOpacityParsed(config);
98
105
  const ffmpeg_cfr = getFfmpeg_cfr(config);
99
106
  const ffmpeg_preset = getFfmpeg_preset(config);
100
107
  const frame_processing_delay = getFrame_processing_delay(config);
101
- const spectrumBusesCount = 64;
102
108
 
103
109
  const audioDuration = audioBuffer.length / sampleRate;
104
110
  const framesCount = Math.trunc(audioDuration * FPS);
@@ -119,14 +125,16 @@ export const renderAudioVisualizer = (config: Config, onProgress?: (progress: nu
119
125
  const audioDataParser = () =>
120
126
  PCM_FORMAT.parseFunction(audioBuffer, i * audioDataStep, i * audioDataStep + audioDataStep);
121
127
  const spectrum = processSpectrum(i, audioDataParser);
122
- const frameImage = createVisualizerFrame(
123
- backgroundImage,
128
+ const frameImage = createVisualizerFrame({
129
+ backgroundImageBuffer: backgroundImage,
124
130
  spectrum,
125
- { width: spectrumWidth, height: spectrumHeight },
126
- { x: spectrumX, y: spectrumY },
127
- spectrumRotation,
128
- spectrumColor
129
- );
131
+ size: { width: spectrumWidth, height: spectrumHeight },
132
+ position: { x: spectrumX, y: spectrumY },
133
+ rotation: spectrumRotation,
134
+ margin: spectrumBusMargin,
135
+ color: spectrumColor,
136
+ opacity: spectrumOpacity,
137
+ });
130
138
  const frameImageBuffer = createImageBuffer(frameImage);
131
139
  const isFrameProcessed = ffmpegVideoWriter.stdin.write(frameImageBuffer);
132
140
  if (!isFrameProcessed) {
@@ -12,6 +12,8 @@ import {
12
12
  getSpectrumXAbsolute,
13
13
  getSpectrumYAbsolute,
14
14
  getSpectrumColor,
15
+ getSpectrumOpacity,
16
+ getSpectrumOpacityParsed,
15
17
  getFfmpeg_cfr,
16
18
  getFfmpeg_preset,
17
19
  getFrame_processing_delay,
@@ -94,6 +96,24 @@ describe('config', function() {
94
96
  backgroundImageWidth
95
97
  );
96
98
  expect(result4).to.throw('Invalid spectrum width value: \'69\'. Use number value or percent value in string, for example: \'30%\'.');
99
+
100
+ const result5 = getSpectrumWidthAbsolute.bind(
101
+ undefined,
102
+ {
103
+ outVideo: { spectrum: { width: 1 }}
104
+ } as Config,
105
+ backgroundImageWidth
106
+ );
107
+ expect(result5).to.throw('Spectrum width \'1\' is too small.');
108
+
109
+ const result6 = getSpectrumWidthAbsolute.bind(
110
+ undefined,
111
+ {
112
+ outVideo: { spectrum: { width: '1%' }}
113
+ } as Config,
114
+ backgroundImageWidth
115
+ );
116
+ expect(result6).to.throw('Spectrum width \'1%\' is too small.');
97
117
  });
98
118
 
99
119
  it('getSpectrumHeightAbsolute', function() {
@@ -267,6 +287,46 @@ describe('config', function() {
267
287
  expect(result2).equal('#cccc99');
268
288
  });
269
289
 
290
+ it('getSpectrumOpacity', function() {
291
+ const result1 = getSpectrumOpacity({
292
+ outVideo: {}
293
+ } as Config);
294
+ expect(result1).equal(defaults.spectrumOpacity);
295
+
296
+ const result2 = getSpectrumOpacity({
297
+ outVideo: { spectrum: { opacity: '69%' } }
298
+ } as Config);
299
+ expect(result2).equal('69%');
300
+ });
301
+
302
+ it('getSpectrumOpacityParsed', function() {
303
+ const result1 = getSpectrumOpacityParsed({
304
+ outVideo: {}
305
+ } as Config);
306
+ expect(result1).equal(parseInt(defaults.spectrumOpacity) / 100);
307
+
308
+ const result2 = getSpectrumOpacityParsed({
309
+ outVideo: { spectrum: { opacity: '69%' } }
310
+ } as Config);
311
+ expect(result2).equal(0.69);
312
+
313
+ const result3 = getSpectrumOpacityParsed.bind(
314
+ undefined,
315
+ {
316
+ outVideo: { spectrum: { opacity: 'fake value' } }
317
+ } as Config
318
+ );
319
+ expect(result3).to.throw('Invalid spectrum opacity value: \'fake value\'. Use string percent value, for example \'80%\'.');
320
+
321
+ const result4 = getSpectrumOpacityParsed.bind(
322
+ undefined,
323
+ {
324
+ outVideo: { spectrum: { opacity: '103%' } }
325
+ } as Config
326
+ );
327
+ expect(result4).to.throw('Invalid spectrum opacity value: \'103%\'. Percent values must be in range from \'0%\' to \'100%\'.');
328
+ });
329
+
270
330
  it('getFfmpeg_cfr', function() {
271
331
  const result1 = getFfmpeg_cfr({
272
332
  tweaks: {}
@@ -1,11 +1,50 @@
1
1
  import { expect } from 'chai';
2
2
  import path from 'path';
3
- import { drawRect, Color, createVisualizerFrame, parseImage, convertToBmp } from '../image';
3
+ import fs from 'fs';
4
+ import {
5
+ drawRect,
6
+ Color,
7
+ createVisualizerFrame,
8
+ parseImage,
9
+ convertToBmp,
10
+ mixValues,
11
+ mixColors,
12
+ } from '../image';
4
13
 
5
14
  import frameSnapshotSpectrumDown from './snapshots/frameSnapshotSpectrumDown.json';
6
15
  import frameSnapshotSpectrumUp from './snapshots/frameSnapshotSpectrumUp.json';
16
+ import frameSnapshotSpectrumUpOpacity50 from './snapshots/frameSnapshotSpectrumUpOpacity50.json';
7
17
 
8
18
  describe('image', function () {
19
+ it('mixValues', function () {
20
+ expect(mixValues(420, 0.69, 96)).equal(320);
21
+ expect(mixValues(420, 1, 96)).equal(420);
22
+ expect(mixValues(420, 0, 96)).equal(96);
23
+ });
24
+
25
+ it('mixColors', function () {
26
+ const result1 = mixColors({
27
+ color: { red: 123, green: 69, blue: 96 },
28
+ colorOpacity: 0.24,
29
+ backgroundColor: { red: 69, green: 123, blue: 42 },
30
+ });
31
+ expect(result1).deep.equal({ red: 82, green: 110, blue: 55 });
32
+
33
+ const result2 = mixColors({
34
+ color: { red: 123, green: 69, blue: 96 },
35
+ colorOpacity: 0,
36
+ backgroundColor: { red: 69, green: 123, blue: 42 },
37
+ });
38
+ expect(result2).deep.equal({ red: 69, green: 123, blue: 42 });
39
+
40
+ const result3 = mixColors({
41
+ color: { red: 123, green: 69, blue: 96 },
42
+ colorOpacity: 1,
43
+ backgroundColor: { red: 69, green: 123, blue: 42 },
44
+ });
45
+ expect(result3).deep.equal({ red: 123, green: 69, blue: 96 });
46
+ });
47
+
9
48
  it('drawRect', function () {
10
49
  const imageWidth = 10;
11
50
  const imageHeight = 10;
@@ -27,7 +66,13 @@ describe('image', function () {
27
66
  expectedImageData[pixelIndex + 3] = rectColor.red;
28
67
  });
29
68
 
30
- drawRect(imageDstBuffer as any, { x: rectX, y: rectY }, { width: rectWidth, height: rectHeight }, rectColor);
69
+ drawRect({
70
+ imageDstBuffer: imageDstBuffer as any,
71
+ position: { x: rectX, y: rectY },
72
+ size: { width: rectWidth, height: rectHeight },
73
+ color: rectColor,
74
+ opacity: 1
75
+ });
31
76
  expect(imageDstBuffer.data).deep.equal(expectedImageData);
32
77
  });
33
78
 
@@ -35,26 +80,43 @@ describe('image', function () {
35
80
  const backgroundImagePath = path.resolve('example/media/background.png');
36
81
  const backgroundImageBmpBuffer = await convertToBmp(backgroundImagePath);
37
82
  const backgroundImage = parseImage(backgroundImageBmpBuffer);
38
- const frameSpectrumDown = createVisualizerFrame(
39
- backgroundImage,
40
- [0.5, 0, 1],
41
- { width: 15, height: 20 },
42
- { x: 15, y: 0 },
43
- 'down',
44
- { red: 1, green: 1, blue: 1 }
45
- );
83
+ const frameSpectrumDown = createVisualizerFrame({
84
+ backgroundImageBuffer: backgroundImage,
85
+ spectrum: [0.5, 0, 1],
86
+ size: { width: 15, height: 20 },
87
+ position: { x: 15, y: 0 },
88
+ rotation: 'down',
89
+ margin: 4,
90
+ color: { red: 1, green: 1, blue: 1 },
91
+ opacity: 1,
92
+ });
46
93
  const resultSpectrumDown = frameSpectrumDown.data.toJSON().data;
47
94
  expect(resultSpectrumDown).deep.equal(frameSnapshotSpectrumDown);
48
95
 
49
- const frameSpectrumUp = createVisualizerFrame(
50
- backgroundImage,
51
- [0.1, 1, 0],
52
- { width: 25, height: 23 },
53
- { x: 10, y: 5 },
54
- 'up',
55
- { red: 0, green: 123, blue: 69 }
56
- );
96
+ const frameSpectrumUp = createVisualizerFrame({
97
+ backgroundImageBuffer: backgroundImage,
98
+ spectrum: [0.1, 1, 0],
99
+ size: { width: 25, height: 23 },
100
+ position: { x: 10, y: 5 },
101
+ rotation: 'up',
102
+ margin: 4,
103
+ color: { red: 0, green: 123, blue: 69 },
104
+ opacity: 1,
105
+ });
57
106
  const resultSpectrumUp = frameSpectrumUp.data.toJSON().data;
58
107
  expect(resultSpectrumUp).deep.equal(frameSnapshotSpectrumUp);
108
+
109
+ const frameSpectrumUpOpacity50 = createVisualizerFrame({
110
+ backgroundImageBuffer: backgroundImage,
111
+ spectrum: [0.1, 1, 0],
112
+ size: { width: 25, height: 23 },
113
+ position: { x: 10, y: 5 },
114
+ rotation: 'up',
115
+ margin: 4,
116
+ color: { red: 0, green: 123, blue: 69 },
117
+ opacity: 0.5,
118
+ });
119
+ const resultSpectrumUpOpacity50 = frameSpectrumUpOpacity50.data.toJSON().data;
120
+ expect(resultSpectrumUpOpacity50).deep.equal(frameSnapshotSpectrumUpOpacity50);
59
121
  });
60
122
  });