nodejs-audio-visualizer 1.2.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/README.md +7 -3
- package/dist/config.d.ts +18 -2
- package/dist/config.js +135 -13
- package/dist/config.js.map +1 -1
- package/dist/image.d.ts +27 -2
- package/dist/image.js +78 -22
- package/dist/image.js.map +1 -1
- package/dist/index.d.ts +10 -2
- package/dist/index.js +17 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/config.ts +190 -14
- package/src/image.ts +137 -21
- package/src/index.ts +44 -9
- package/src/tests/config.test.ts +239 -5
- package/src/tests/image.test.ts +88 -9
- package/src/tests/snapshots/frameSnapshotSpectrumDown.json +1 -0
- package/src/tests/snapshots/frameSnapshotSpectrumUp.json +1 -0
- package/src/tests/snapshots/frameSnapshotSpectrumUpOpacity50.json +1 -0
package/src/config.ts
CHANGED
|
@@ -1,14 +1,38 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
-
import { Config } from './index';
|
|
2
|
+
import { Config, SpectrumSizeValue, PositionAliasName, RotationAliasName } from './index';
|
|
3
3
|
|
|
4
4
|
export const defaults = {
|
|
5
5
|
fps: 60,
|
|
6
|
-
spectrumWidth:
|
|
7
|
-
spectrumHeight:
|
|
6
|
+
spectrumWidth: '40%',
|
|
7
|
+
spectrumHeight: '10%',
|
|
8
|
+
spectrumX: 'center',
|
|
9
|
+
spectrumY: 'top',
|
|
10
|
+
spectrumRotation: 'down',
|
|
11
|
+
spectrumBusesCount: 64,
|
|
12
|
+
spectrumBusMargin: 4,
|
|
13
|
+
spectrumOpacity: '100%',
|
|
8
14
|
};
|
|
9
15
|
|
|
10
|
-
|
|
11
|
-
|
|
16
|
+
type RelativePositionValue = 0 | 0.5 | 1;
|
|
17
|
+
|
|
18
|
+
interface PositionAlias {
|
|
19
|
+
name: PositionAliasName;
|
|
20
|
+
value: RelativePositionValue;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const postitionAliases: PositionAlias[] = [
|
|
24
|
+
{ name: 'left', value: 0 },
|
|
25
|
+
{ name: 'center', value: 0.5 },
|
|
26
|
+
{ name: 'right', value: 1 },
|
|
27
|
+
{ name: 'top', value: 0 },
|
|
28
|
+
{ name: 'middle', value: 0.5 },
|
|
29
|
+
{ name: 'bottom', value: 1 },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
export const rotationAliasValues = ['up', 'down'] as const;
|
|
33
|
+
|
|
34
|
+
export const checkIsPercentValue = (value: string) =>
|
|
35
|
+
RegExp(/\d+%/).test(value)
|
|
12
36
|
|
|
13
37
|
export const getAudioFilePath = (config: Config) =>
|
|
14
38
|
path.resolve(config.audio.path);
|
|
@@ -22,19 +46,46 @@ export const getOutVideoPath = (config: Config) =>
|
|
|
22
46
|
export const getFPS = (config: Config) =>
|
|
23
47
|
config.outVideo.fps || defaults.fps;
|
|
24
48
|
|
|
49
|
+
export const getSpectrumBusesCount = () => defaults.spectrumBusesCount;
|
|
50
|
+
|
|
51
|
+
export const getSpectrumBusMargin = () => defaults.spectrumBusMargin;
|
|
52
|
+
|
|
25
53
|
const getSpectrumWidth = (config: Config) =>
|
|
26
54
|
(config.outVideo.spectrum && config.outVideo.spectrum.width) ||
|
|
27
55
|
defaults.spectrumWidth;
|
|
28
56
|
|
|
57
|
+
const getSpectrumSizeAbsolute = (
|
|
58
|
+
spectrumSize: SpectrumSizeValue,
|
|
59
|
+
backgroundImageSize: number,
|
|
60
|
+
fieldName: string
|
|
61
|
+
) => {
|
|
62
|
+
if (typeof spectrumSize === 'number') {
|
|
63
|
+
return spectrumSize;
|
|
64
|
+
}
|
|
65
|
+
const isPercentValue = checkIsPercentValue(spectrumSize);
|
|
66
|
+
if (!isPercentValue) {
|
|
67
|
+
throw new Error(getSpectrumSizeValueErrorMessage(fieldName, spectrumSize));
|
|
68
|
+
}
|
|
69
|
+
const percentValue = parseInt(spectrumSize) / 100;
|
|
70
|
+
return backgroundImageSize * percentValue;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const getSpectrumSizeValueErrorMessage = (
|
|
74
|
+
fieldName: string, value: SpectrumSizeValue
|
|
75
|
+
) =>
|
|
76
|
+
`Invalid spectrum ${fieldName} value: '${value}'. Use number value or percent value in string, for example: '30%'.`
|
|
77
|
+
|
|
29
78
|
export const getSpectrumWidthAbsolute = (
|
|
30
79
|
config: Config, backgroundImageWidth: number
|
|
31
80
|
) => {
|
|
32
81
|
const spectrumWidth = getSpectrumWidth(config);
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
82
|
+
const spectrumWidthAbsolute = getSpectrumSizeAbsolute(spectrumWidth, backgroundImageWidth, 'width');
|
|
83
|
+
const spectrumWidthWithoutMargin =
|
|
84
|
+
Math.trunc(spectrumWidthAbsolute / defaults.spectrumBusesCount) - defaults.spectrumBusMargin / 2;
|
|
85
|
+
if (spectrumWidthWithoutMargin <= 0) {
|
|
86
|
+
throw new Error(`Spectrum width '${spectrumWidth}' is too small.`);
|
|
36
87
|
}
|
|
37
|
-
return
|
|
88
|
+
return spectrumWidthAbsolute;
|
|
38
89
|
};
|
|
39
90
|
|
|
40
91
|
const getSpectrumHeight = (config: Config) =>
|
|
@@ -45,15 +96,140 @@ export const getSpectrumHeightAbsolute = (
|
|
|
45
96
|
config: Config, backgroundImageHeight: number
|
|
46
97
|
) => {
|
|
47
98
|
const spectrumHeight = getSpectrumHeight(config);
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
99
|
+
return getSpectrumSizeAbsolute(spectrumHeight, backgroundImageHeight, 'height');
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const getSpectrumX = (config: Config) =>
|
|
103
|
+
parseNumberIfPossible(
|
|
104
|
+
(config.outVideo.spectrum && config.outVideo.spectrum.x) ||
|
|
105
|
+
defaults.spectrumX
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const getSpectrumY = (config: Config) =>
|
|
109
|
+
parseNumberIfPossible(
|
|
110
|
+
(config.outVideo.spectrum && config.outVideo.spectrum.y) ||
|
|
111
|
+
defaults.spectrumY
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const parseNumberIfPossible = (value: string | number) => {
|
|
115
|
+
const parsed = Number(value);
|
|
116
|
+
if (isNaN(parsed)) {
|
|
117
|
+
return value;
|
|
51
118
|
}
|
|
52
|
-
return
|
|
119
|
+
return parsed;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const parsePositionAlias = (aliasName: string): PositionAlias => {
|
|
123
|
+
const nameNormalized = aliasName.trim().toLowerCase();
|
|
124
|
+
const alias = postitionAliases.find(
|
|
125
|
+
als => als.name === nameNormalized
|
|
126
|
+
);
|
|
127
|
+
if (!alias) {
|
|
128
|
+
throw new Error();
|
|
129
|
+
}
|
|
130
|
+
return alias;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const getValidPositionAliasValues = () =>
|
|
134
|
+
postitionAliases.map(aliasEl => aliasEl.name).join(', ');
|
|
135
|
+
|
|
136
|
+
const getSpectrumAbsolutePosition = (
|
|
137
|
+
relativePosition: RelativePositionValue,
|
|
138
|
+
spectrumSize: number,
|
|
139
|
+
backgroundImageSize: number
|
|
140
|
+
) => {
|
|
141
|
+
switch (relativePosition) {
|
|
142
|
+
case 0:
|
|
143
|
+
return 0;
|
|
144
|
+
case 0.5:
|
|
145
|
+
return relativePosition * backgroundImageSize - spectrumSize / 2;
|
|
146
|
+
case 1:
|
|
147
|
+
return relativePosition * backgroundImageSize - spectrumSize;
|
|
148
|
+
default:
|
|
149
|
+
throw new Error();
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export const getSpectrumXAbsolute = (
|
|
154
|
+
config: Config,
|
|
155
|
+
spectrumWidth: number,
|
|
156
|
+
backgroundImageWidth: number
|
|
157
|
+
) => {
|
|
158
|
+
const spectrumX = getSpectrumX(config);
|
|
159
|
+
if (typeof spectrumX !== 'string') {
|
|
160
|
+
return spectrumX;
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
const spectrumXParsed = parsePositionAlias(spectrumX);
|
|
164
|
+
return getSpectrumAbsolutePosition(
|
|
165
|
+
spectrumXParsed.value,
|
|
166
|
+
spectrumWidth,
|
|
167
|
+
backgroundImageWidth
|
|
168
|
+
);
|
|
169
|
+
} catch {
|
|
170
|
+
throw new Error(`Invalid spectrum x value: ${spectrumX}. Valid values: ${getValidPositionAliasValues()}.`);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export const getSpectrumYAbsolute = (
|
|
175
|
+
config: Config,
|
|
176
|
+
spectrumHeight: number,
|
|
177
|
+
backgroundImageHeight: number
|
|
178
|
+
) => {
|
|
179
|
+
const spectrumY = getSpectrumY(config);
|
|
180
|
+
if (typeof spectrumY !== 'string') {
|
|
181
|
+
return spectrumY;
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
const spectrumXParsed = parsePositionAlias(spectrumY);
|
|
185
|
+
return getSpectrumAbsolutePosition(
|
|
186
|
+
spectrumXParsed.value,
|
|
187
|
+
spectrumHeight,
|
|
188
|
+
backgroundImageHeight
|
|
189
|
+
);
|
|
190
|
+
} catch {
|
|
191
|
+
throw new Error(`Invalid spectrum y value: ${spectrumY}. Valid values: ${getValidPositionAliasValues()}.`);
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const getValidRotationAliasValues = () =>
|
|
196
|
+
rotationAliasValues.join(', ');
|
|
197
|
+
|
|
198
|
+
export const checkIsValidRotationAlias = (aliasName: string) => {
|
|
199
|
+
return !!rotationAliasValues.find(al => al === aliasName);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
export const getSpectrumRotation = (config: Config): RotationAliasName => {
|
|
203
|
+
const rotationValue =
|
|
204
|
+
(config.outVideo.spectrum && config.outVideo.spectrum.rotation) ||
|
|
205
|
+
defaults.spectrumRotation;
|
|
206
|
+
if (!checkIsValidRotationAlias(rotationValue)) {
|
|
207
|
+
throw new Error(`Invalid spectrum rotation value: ${rotationValue}. Valid values: ${getValidRotationAliasValues}.`);
|
|
208
|
+
}
|
|
209
|
+
return rotationValue as RotationAliasName;
|
|
53
210
|
};
|
|
54
211
|
|
|
55
212
|
export const getSpectrumColor = (config: Config) =>
|
|
56
|
-
|
|
213
|
+
config.outVideo.spectrum && config.outVideo.spectrum.color;
|
|
214
|
+
|
|
215
|
+
export const getSpectrumOpacity = (config: Config) =>
|
|
216
|
+
(config.outVideo.spectrum && config.outVideo.spectrum.opacity) ||
|
|
217
|
+
defaults.spectrumOpacity;
|
|
218
|
+
|
|
219
|
+
export const getSpectrumOpacityParsed = (config: Config) => {
|
|
220
|
+
const spectrumOpacity = getSpectrumOpacity(config);
|
|
221
|
+
if (!checkIsPercentValue(spectrumOpacity)) {
|
|
222
|
+
throw new Error(`Invalid spectrum opacity value: '${spectrumOpacity}'. Use string percent value, for example '80%'.`);
|
|
223
|
+
}
|
|
224
|
+
const percentValue = parseInt(spectrumOpacity);
|
|
225
|
+
if (
|
|
226
|
+
(percentValue < 0) ||
|
|
227
|
+
(percentValue > 100)
|
|
228
|
+
) {
|
|
229
|
+
throw new Error(`Invalid spectrum opacity value: '${spectrumOpacity}'. Percent values must be in range from '0%' to '100%'.`);
|
|
230
|
+
}
|
|
231
|
+
return percentValue / 100;
|
|
232
|
+
};
|
|
57
233
|
|
|
58
234
|
export const getFfmpeg_cfr = (config: Config) =>
|
|
59
235
|
config.tweaks && config.tweaks.ffmpeg_cfr;
|
package/src/image.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RotationAliasName } from './index';
|
|
1
2
|
import { decode, encode, BmpDecoder } from 'bmp-js';
|
|
2
3
|
import Jimp from 'jimp';
|
|
3
4
|
|
|
@@ -17,22 +18,94 @@ interface Size {
|
|
|
17
18
|
height: number;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
|
|
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) => {
|
|
21
57
|
for (var currY = position.y; currY < position.y + size.height; currY++) {
|
|
22
58
|
for (var currX = position.x; currX < position.x + size.width; currX++) {
|
|
23
59
|
var idx = (imageDstBuffer.width * currY + currX) << 2;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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;
|
|
28
80
|
}
|
|
29
81
|
}
|
|
30
82
|
};
|
|
31
83
|
|
|
32
|
-
|
|
33
|
-
|
|
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) => {
|
|
34
105
|
const busWidth = Math.trunc(size.width / spectrum.length);
|
|
35
|
-
const
|
|
106
|
+
const left = Math.trunc(position.x);
|
|
107
|
+
const top = Math.trunc(position.y);
|
|
108
|
+
const height = Math.trunc(size.height);
|
|
36
109
|
|
|
37
110
|
for (let spectrumX = 0; spectrumX < spectrum.length; spectrumX++) {
|
|
38
111
|
const spectrumValue = spectrum[spectrumX];
|
|
@@ -40,26 +113,69 @@ const drawSpectrum = (imageDstBuffer: BmpDecoder, spectrum: number[], size: Size
|
|
|
40
113
|
throw new Error('Spectrum values must be in range from 0 to 1');
|
|
41
114
|
}
|
|
42
115
|
|
|
43
|
-
const rectX =
|
|
44
|
-
const rectHeight =
|
|
45
|
-
|
|
116
|
+
const rectX = left + busWidth * spectrumX;
|
|
117
|
+
const rectHeight = Math.trunc(height * spectrumValue);
|
|
118
|
+
const rectY = (rotation === 'up') ? top + height - rectHeight : top;
|
|
119
|
+
drawRect({
|
|
120
|
+
imageDstBuffer,
|
|
121
|
+
position: { x: rectX + margin, y: rectY },
|
|
122
|
+
size: { width: busWidth - margin / 2, height: rectHeight },
|
|
123
|
+
color,
|
|
124
|
+
opacity,
|
|
125
|
+
});
|
|
46
126
|
}
|
|
47
127
|
};
|
|
48
128
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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);
|
|
52
152
|
const rgbSpectrumColor = (typeof color === 'string') ? hexToRgb(color) : color;
|
|
53
|
-
drawSpectrum(
|
|
54
|
-
|
|
153
|
+
drawSpectrum({
|
|
154
|
+
imageDstBuffer,
|
|
155
|
+
spectrum,
|
|
156
|
+
size,
|
|
157
|
+
position,
|
|
158
|
+
rotation,
|
|
159
|
+
margin,
|
|
160
|
+
color: rgbSpectrumColor,
|
|
161
|
+
opacity,
|
|
162
|
+
});
|
|
163
|
+
return imageDstBuffer;
|
|
55
164
|
};
|
|
56
165
|
|
|
57
166
|
export const convertToBmp = async (filePath: string) =>
|
|
58
|
-
new Promise<Buffer>(async resolve => {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
+
}
|
|
63
179
|
});
|
|
64
180
|
|
|
65
181
|
export const parseImage = (buffer: Buffer) => decode(buffer);
|
package/src/index.ts
CHANGED
|
@@ -3,12 +3,19 @@ import {
|
|
|
3
3
|
getBackgroundImagePath,
|
|
4
4
|
getOutVideoPath,
|
|
5
5
|
getFPS,
|
|
6
|
+
getSpectrumBusesCount,
|
|
7
|
+
getSpectrumBusMargin,
|
|
6
8
|
getSpectrumWidthAbsolute,
|
|
7
9
|
getSpectrumHeightAbsolute,
|
|
10
|
+
getSpectrumXAbsolute,
|
|
11
|
+
getSpectrumYAbsolute,
|
|
8
12
|
getSpectrumColor,
|
|
13
|
+
getSpectrumOpacityParsed,
|
|
9
14
|
getFfmpeg_cfr,
|
|
10
15
|
getFfmpeg_preset,
|
|
11
|
-
getFrame_processing_delay
|
|
16
|
+
getFrame_processing_delay,
|
|
17
|
+
rotationAliasValues,
|
|
18
|
+
getSpectrumRotation
|
|
12
19
|
} from './config';
|
|
13
20
|
import { createAudioBuffer, bufferToUInt8, createSpectrumsProcessor } from './audio';
|
|
14
21
|
import { parseImage, createVisualizerFrame, createImageBuffer, getImageColor, invertColor, Color, convertToBmp } from './image';
|
|
@@ -32,9 +39,13 @@ export interface Config {
|
|
|
32
39
|
path: string;
|
|
33
40
|
fps?: number;
|
|
34
41
|
spectrum?: {
|
|
35
|
-
width?:
|
|
36
|
-
height?:
|
|
42
|
+
width?: SpectrumSizeValue;
|
|
43
|
+
height?: SpectrumSizeValue;
|
|
44
|
+
x?: number | PositionAliasName;
|
|
45
|
+
y?: number | PositionAliasName;
|
|
46
|
+
rotation?: RotationAliasName;
|
|
37
47
|
color?: Color | string;
|
|
48
|
+
opacity?: string;
|
|
38
49
|
}
|
|
39
50
|
};
|
|
40
51
|
tweaks?: {
|
|
@@ -44,6 +55,18 @@ export interface Config {
|
|
|
44
55
|
};
|
|
45
56
|
}
|
|
46
57
|
|
|
58
|
+
export type SpectrumSizeValue = number | string;
|
|
59
|
+
|
|
60
|
+
export type PositionAliasName =
|
|
61
|
+
'left' |
|
|
62
|
+
'center' |
|
|
63
|
+
'right' |
|
|
64
|
+
'top' |
|
|
65
|
+
'middle' |
|
|
66
|
+
'bottom';
|
|
67
|
+
|
|
68
|
+
export type RotationAliasName = typeof rotationAliasValues[number];
|
|
69
|
+
|
|
47
70
|
const sleep = (timeout: number) =>
|
|
48
71
|
new Promise(resolve => setTimeout(resolve, timeout));
|
|
49
72
|
|
|
@@ -62,18 +85,26 @@ export const renderAudioVisualizer = (config: Config, onProgress?: (progress: nu
|
|
|
62
85
|
throw new Error('ffmpeg didn\'t show audio sample rate');
|
|
63
86
|
}
|
|
64
87
|
|
|
88
|
+
const spectrumBusesCount = getSpectrumBusesCount();
|
|
89
|
+
const spectrumBusMargin = getSpectrumBusMargin();
|
|
65
90
|
const FPS = getFPS(config);
|
|
66
91
|
const spectrumWidth =
|
|
67
92
|
getSpectrumWidthAbsolute(config, backgroundImage.width);
|
|
68
93
|
const spectrumHeight =
|
|
69
94
|
getSpectrumHeightAbsolute(config, backgroundImage.height);
|
|
95
|
+
const spectrumX =
|
|
96
|
+
getSpectrumXAbsolute(config, spectrumWidth, backgroundImage.width);
|
|
97
|
+
const spectrumY =
|
|
98
|
+
getSpectrumYAbsolute(config, spectrumHeight, backgroundImage.height);
|
|
99
|
+
const spectrumRotation =
|
|
100
|
+
getSpectrumRotation(config);
|
|
70
101
|
const spectrumColor =
|
|
71
102
|
getSpectrumColor(config) ||
|
|
72
103
|
invertColor(getImageColor(backgroundImage));
|
|
104
|
+
const spectrumOpacity = getSpectrumOpacityParsed(config);
|
|
73
105
|
const ffmpeg_cfr = getFfmpeg_cfr(config);
|
|
74
106
|
const ffmpeg_preset = getFfmpeg_preset(config);
|
|
75
107
|
const frame_processing_delay = getFrame_processing_delay(config);
|
|
76
|
-
const spectrumBusesCount = 64;
|
|
77
108
|
|
|
78
109
|
const audioDuration = audioBuffer.length / sampleRate;
|
|
79
110
|
const framesCount = Math.trunc(audioDuration * FPS);
|
|
@@ -94,12 +125,16 @@ export const renderAudioVisualizer = (config: Config, onProgress?: (progress: nu
|
|
|
94
125
|
const audioDataParser = () =>
|
|
95
126
|
PCM_FORMAT.parseFunction(audioBuffer, i * audioDataStep, i * audioDataStep + audioDataStep);
|
|
96
127
|
const spectrum = processSpectrum(i, audioDataParser);
|
|
97
|
-
const frameImage = createVisualizerFrame(
|
|
98
|
-
backgroundImage,
|
|
128
|
+
const frameImage = createVisualizerFrame({
|
|
129
|
+
backgroundImageBuffer: backgroundImage,
|
|
99
130
|
spectrum,
|
|
100
|
-
{ width: spectrumWidth, height: spectrumHeight },
|
|
101
|
-
|
|
102
|
-
|
|
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
|
+
});
|
|
103
138
|
const frameImageBuffer = createImageBuffer(frameImage);
|
|
104
139
|
const isFrameProcessed = ffmpegVideoWriter.stdin.write(frameImageBuffer);
|
|
105
140
|
if (!isFrameProcessed) {
|