nodejs-audio-visualizer 4.4.12 → 4.5.1
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/LICENSE +21 -21
- package/README.md +80 -75
- package/dist/audio.d.ts +12 -12
- package/dist/audio.js +120 -120
- package/dist/beats.d.ts +19 -19
- package/dist/beats.js +43 -43
- package/dist/bpmEncoder.d.ts +14 -14
- package/dist/bpmEncoder.js +97 -97
- package/dist/config.d.ts +77 -67
- package/dist/config.js +369 -356
- package/dist/config.js.map +1 -1
- package/dist/dsp.d.ts +16 -16
- package/dist/dsp.js +76 -76
- package/dist/highlight.d.ts +32 -32
- package/dist/highlight.js +325 -325
- package/dist/image.d.ts +79 -79
- package/dist/image.js +458 -458
- package/dist/index.d.ts +79 -74
- package/dist/index.js +531 -528
- package/dist/index.js.map +1 -1
- package/dist/subtitleConvert.d.ts +3 -3
- package/dist/subtitleConvert.js +61 -61
- package/dist/video.d.ts +80 -80
- package/dist/video.js +434 -430
- package/dist/video.js.map +1 -1
- package/dist/waitForEventLoop.d.ts +1 -1
- package/dist/waitForEventLoop.js +5 -5
- package/package.json +61 -61
- package/src/audio.ts +120 -120
- package/src/beats.ts +68 -68
- package/src/bpmEncoder.ts +97 -97
- package/src/config.ts +476 -462
- package/src/dsp.ts +95 -95
- package/src/highlight.ts +284 -284
- package/src/image.ts +602 -602
- package/src/index.ts +713 -685
- package/src/subtitleConvert.ts +63 -63
- package/src/tests/audio.test.ts +96 -96
- package/src/tests/config.test.ts +496 -475
- package/src/tests/dsp.test.ts +24 -24
- package/src/tests/highlight.test.ts +178 -178
- package/src/tests/image.test.ts +222 -222
- package/src/tests/index.test.ts +112 -112
- package/src/tests/subtitleConvert.test.ts +23 -23
- package/src/tests/video.test.ts +285 -216
- package/src/typings.d.ts +6 -6
- package/src/video.ts +550 -547
- package/src/waitForEventLoop.ts +2 -2
package/src/config.ts
CHANGED
|
@@ -1,462 +1,476 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import { Config, SpectrumSizeValue, PositionAliasName, RotationAliasName } from './index';
|
|
3
|
-
import { FREQUENCY_BANDS } from './dsp';
|
|
4
|
-
|
|
5
|
-
export const defaults = {
|
|
6
|
-
fps: 30,
|
|
7
|
-
spectrumWidth: '33%',
|
|
8
|
-
spectrumHeight: 160,
|
|
9
|
-
spectrumX: 'center',
|
|
10
|
-
spectrumY: 'middle',
|
|
11
|
-
spectrumRotation: 'mirror',
|
|
12
|
-
spectrumBusMargin: 12,
|
|
13
|
-
spectrumOpacity: '80%',
|
|
14
|
-
polarX: 'center',
|
|
15
|
-
polarY: 'middle',
|
|
16
|
-
polarInnerRadius: 100,
|
|
17
|
-
polarMaxBarLength: 160,
|
|
18
|
-
polarBarWidth: 15,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
{ name: '
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
export const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
export
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
export
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if (pathRaw.length
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
) =>
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
)
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
);
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
);
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
(
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
return
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
)
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
return
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
export const
|
|
445
|
-
(config.outVideo.polar && config.outVideo.polar.
|
|
446
|
-
defaults.
|
|
447
|
-
|
|
448
|
-
export const
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { Config, SpectrumSizeValue, PositionAliasName, RotationAliasName } from './index';
|
|
3
|
+
import { FREQUENCY_BANDS } from './dsp';
|
|
4
|
+
|
|
5
|
+
export const defaults = {
|
|
6
|
+
fps: 30,
|
|
7
|
+
spectrumWidth: '33%',
|
|
8
|
+
spectrumHeight: 160,
|
|
9
|
+
spectrumX: 'center',
|
|
10
|
+
spectrumY: 'middle',
|
|
11
|
+
spectrumRotation: 'mirror',
|
|
12
|
+
spectrumBusMargin: 12,
|
|
13
|
+
spectrumOpacity: '80%',
|
|
14
|
+
polarX: 'center',
|
|
15
|
+
polarY: 'middle',
|
|
16
|
+
polarInnerRadius: 100,
|
|
17
|
+
polarMaxBarLength: 160,
|
|
18
|
+
polarBarWidth: 15,
|
|
19
|
+
timeouts: {
|
|
20
|
+
readVideoFrame: 30 * 1000,
|
|
21
|
+
waitDrain: 120 * 1000,
|
|
22
|
+
waitForProcessExit: 480 * 1000,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type RelativePositionValue = 0 | 0.5 | 1;
|
|
27
|
+
|
|
28
|
+
export type SpectrumEffect = 'volume' | 'smooth';
|
|
29
|
+
|
|
30
|
+
interface PositionAlias {
|
|
31
|
+
name: PositionAliasName;
|
|
32
|
+
value: RelativePositionValue;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const postitionAliases: PositionAlias[] = [
|
|
36
|
+
{ name: 'left', value: 0 },
|
|
37
|
+
{ name: 'center', value: 0.5 },
|
|
38
|
+
{ name: 'right', value: 1 },
|
|
39
|
+
{ name: 'top', value: 0 },
|
|
40
|
+
{ name: 'middle', value: 0.5 },
|
|
41
|
+
{ name: 'bottom', value: 1 },
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
export const rotationAliasValues = ['up', 'down', 'mirror'] as const;
|
|
45
|
+
|
|
46
|
+
export const checkIsPercentValue = (value: string) =>
|
|
47
|
+
RegExp(/\d+%/).test(value)
|
|
48
|
+
|
|
49
|
+
export const getAudioFilePath = (config: Config) =>
|
|
50
|
+
path.resolve(config.audio.path);
|
|
51
|
+
|
|
52
|
+
export const getAudioAutoHighlight = (config: Config) =>
|
|
53
|
+
config.audio.autoHighlight === true;
|
|
54
|
+
|
|
55
|
+
export const getAudioAutoHighlightCount = (config: Config): number => {
|
|
56
|
+
const raw = config.audio.autoHighlightCount;
|
|
57
|
+
if (raw === undefined) {
|
|
58
|
+
return 1;
|
|
59
|
+
}
|
|
60
|
+
if (
|
|
61
|
+
typeof raw !== 'number' ||
|
|
62
|
+
!Number.isFinite(raw) ||
|
|
63
|
+
!Number.isInteger(raw) ||
|
|
64
|
+
raw < 1
|
|
65
|
+
) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`Invalid audio.autoHighlightCount: expected a finite integer >= 1, got '${String(raw)}'.`,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
return raw;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const getBackgroundImagePath = (config: Config) =>
|
|
74
|
+
config.image ? path.resolve(config.image.path) : undefined;
|
|
75
|
+
|
|
76
|
+
export const getBackgroundVideoPath = (config: Config) =>
|
|
77
|
+
config.video ? path.resolve(config.video.path) : undefined;
|
|
78
|
+
|
|
79
|
+
export const getAutoEditVideo = (config: Config) =>
|
|
80
|
+
config.video?.autoEdit === true;
|
|
81
|
+
|
|
82
|
+
export const getOutVideoPath = (config: Config) =>
|
|
83
|
+
path.resolve(config.outVideo.path);
|
|
84
|
+
|
|
85
|
+
export type SubtitleAlignment = 'top' | 'middle' | 'bottom';
|
|
86
|
+
|
|
87
|
+
export type SubtitleRenderSpec = {
|
|
88
|
+
source: { kind: 'file'; path: string } | { kind: 'inline'; text: string };
|
|
89
|
+
alignment: SubtitleAlignment;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export const subtitleAlignmentToAss = (alignment: SubtitleAlignment): number => {
|
|
93
|
+
switch (alignment) {
|
|
94
|
+
case 'top':
|
|
95
|
+
return 6;
|
|
96
|
+
case 'middle':
|
|
97
|
+
return 10;
|
|
98
|
+
case 'bottom':
|
|
99
|
+
return 2;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const parseSubtitleAlignment = (raw: unknown): SubtitleAlignment => {
|
|
104
|
+
if (raw === undefined) {
|
|
105
|
+
return 'bottom';
|
|
106
|
+
}
|
|
107
|
+
if (raw === 'top' || raw === 'middle' || raw === 'bottom') {
|
|
108
|
+
return raw;
|
|
109
|
+
}
|
|
110
|
+
throw new Error(
|
|
111
|
+
`Invalid outVideo.subtitles.alignment: expected 'top', 'middle', or 'bottom', got '${String(raw)}'.`,
|
|
112
|
+
);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export const getSubtitleRenderSpec = (config: Config): SubtitleRenderSpec | undefined => {
|
|
116
|
+
const subtitles = config.outVideo.subtitles;
|
|
117
|
+
if (subtitles === undefined || subtitles === null) {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
if (typeof subtitles === 'string') {
|
|
121
|
+
if (subtitles.trim().length === 0) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`Invalid outVideo.subtitles: expected non-empty string content, got '${String(subtitles)}'.`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
source: { kind: 'inline', text: subtitles },
|
|
128
|
+
alignment: 'bottom',
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
if (typeof subtitles !== 'object' || Array.isArray(subtitles)) {
|
|
132
|
+
throw new Error(
|
|
133
|
+
`Invalid outVideo.subtitles: expected string or object, got '${String(subtitles)}'.`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
const obj = subtitles as {
|
|
137
|
+
path?: string;
|
|
138
|
+
rawContent?: string;
|
|
139
|
+
alignment?: unknown;
|
|
140
|
+
};
|
|
141
|
+
const alignment = parseSubtitleAlignment(obj.alignment);
|
|
142
|
+
const pathRaw = obj.path !== undefined ? String(obj.path).trim() : '';
|
|
143
|
+
const rawContent =
|
|
144
|
+
obj.rawContent !== undefined ? String(obj.rawContent).trim() : '';
|
|
145
|
+
if (pathRaw.length > 0 && rawContent.length > 0) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
'Invalid outVideo.subtitles: specify only one of "path" or "rawContent", not both.',
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
if (pathRaw.length === 0 && rawContent.length === 0) {
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
if (pathRaw.length > 0) {
|
|
154
|
+
return {
|
|
155
|
+
source: { kind: 'file', path: path.resolve(pathRaw) },
|
|
156
|
+
alignment,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
source: { kind: 'inline', text: rawContent },
|
|
161
|
+
alignment,
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
export const getFPS = (config: Config) =>
|
|
166
|
+
config.outVideo.fps || defaults.fps;
|
|
167
|
+
|
|
168
|
+
export const getOutputResolution = (config: Config) =>
|
|
169
|
+
config.outVideo.resolution;
|
|
170
|
+
|
|
171
|
+
export const getSpectrumBusMargin = () => defaults.spectrumBusMargin;
|
|
172
|
+
|
|
173
|
+
const getSpectrumWidth = (config: Config) =>
|
|
174
|
+
(config.outVideo.spectrum && config.outVideo.spectrum.width) ||
|
|
175
|
+
defaults.spectrumWidth;
|
|
176
|
+
|
|
177
|
+
export const getSpectrumEffect = (config: Config) =>
|
|
178
|
+
config.outVideo.spectrum?.effect;
|
|
179
|
+
|
|
180
|
+
const getSpectrumSizeAbsolute = (
|
|
181
|
+
spectrumSize: SpectrumSizeValue,
|
|
182
|
+
backgroundImageSize: number,
|
|
183
|
+
fieldName: string
|
|
184
|
+
) => {
|
|
185
|
+
if (typeof spectrumSize === 'number') {
|
|
186
|
+
return spectrumSize;
|
|
187
|
+
}
|
|
188
|
+
const isPercentValue = checkIsPercentValue(spectrumSize);
|
|
189
|
+
if (!isPercentValue) {
|
|
190
|
+
throw new Error(getSpectrumSizeValueErrorMessage(fieldName, spectrumSize));
|
|
191
|
+
}
|
|
192
|
+
const percentValue = parseInt(spectrumSize) / 100;
|
|
193
|
+
return backgroundImageSize * percentValue;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const getSpectrumSizeValueErrorMessage = (
|
|
197
|
+
fieldName: string, value: SpectrumSizeValue
|
|
198
|
+
) =>
|
|
199
|
+
`Invalid spectrum ${fieldName} value: '${value}'. Use number value or percent value in string, for example: '30%'.`
|
|
200
|
+
|
|
201
|
+
export const getSpectrumWidthAbsolute = (
|
|
202
|
+
config: Config, backgroundImageWidth: number
|
|
203
|
+
) => {
|
|
204
|
+
const spectrumWidth = getSpectrumWidth(config);
|
|
205
|
+
const spectrumWidthAbsolute = getSpectrumSizeAbsolute(spectrumWidth, backgroundImageWidth, 'width');
|
|
206
|
+
const spectrumWidthWithoutMargin =
|
|
207
|
+
Math.trunc(spectrumWidthAbsolute / FREQUENCY_BANDS.length) - defaults.spectrumBusMargin / 2;
|
|
208
|
+
if (spectrumWidthWithoutMargin <= 0) {
|
|
209
|
+
throw new Error(`Spectrum width '${spectrumWidth}' is too small.`);
|
|
210
|
+
}
|
|
211
|
+
return spectrumWidthAbsolute;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const getSpectrumHeight = (config: Config) =>
|
|
215
|
+
(config.outVideo.spectrum && config.outVideo.spectrum.height) ||
|
|
216
|
+
defaults.spectrumHeight;
|
|
217
|
+
|
|
218
|
+
export const getSpectrumHeightAbsolute = (
|
|
219
|
+
config: Config, backgroundImageHeight: number
|
|
220
|
+
) => {
|
|
221
|
+
const spectrumHeight = getSpectrumHeight(config);
|
|
222
|
+
return getSpectrumSizeAbsolute(spectrumHeight, backgroundImageHeight, 'height');
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const getSpectrumX = (config: Config) =>
|
|
226
|
+
parseNumberIfPossible(
|
|
227
|
+
(config.outVideo.spectrum && config.outVideo.spectrum.x) ||
|
|
228
|
+
defaults.spectrumX
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
const getSpectrumY = (config: Config) =>
|
|
232
|
+
parseNumberIfPossible(
|
|
233
|
+
(config.outVideo.spectrum && config.outVideo.spectrum.y) ||
|
|
234
|
+
defaults.spectrumY
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
const parseNumberIfPossible = (value: string | number) => {
|
|
238
|
+
const parsed = Number(value);
|
|
239
|
+
if (isNaN(parsed)) {
|
|
240
|
+
return value;
|
|
241
|
+
}
|
|
242
|
+
return parsed;
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const parsePositionAlias = (aliasName: string): PositionAlias => {
|
|
246
|
+
const nameNormalized = aliasName.trim().toLowerCase();
|
|
247
|
+
const alias = postitionAliases.find(
|
|
248
|
+
als => als.name === nameNormalized
|
|
249
|
+
);
|
|
250
|
+
if (!alias) {
|
|
251
|
+
throw new Error();
|
|
252
|
+
}
|
|
253
|
+
return alias;
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const getValidPositionAliasValues = () =>
|
|
257
|
+
postitionAliases.map(aliasEl => aliasEl.name).join(', ');
|
|
258
|
+
|
|
259
|
+
const getSpectrumAbsolutePosition = (
|
|
260
|
+
relativePosition: RelativePositionValue,
|
|
261
|
+
spectrumSize: number,
|
|
262
|
+
backgroundImageSize: number
|
|
263
|
+
) => {
|
|
264
|
+
switch (relativePosition) {
|
|
265
|
+
case 0:
|
|
266
|
+
return 0;
|
|
267
|
+
case 0.5:
|
|
268
|
+
return relativePosition * backgroundImageSize - spectrumSize / 2;
|
|
269
|
+
case 1:
|
|
270
|
+
return relativePosition * backgroundImageSize - spectrumSize;
|
|
271
|
+
default:
|
|
272
|
+
throw new Error();
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
export const getSpectrumXAbsolute = (
|
|
277
|
+
config: Config,
|
|
278
|
+
spectrumWidth: number,
|
|
279
|
+
backgroundImageWidth: number
|
|
280
|
+
) => {
|
|
281
|
+
const spectrumX = getSpectrumX(config);
|
|
282
|
+
if (typeof spectrumX !== 'string') {
|
|
283
|
+
return spectrumX;
|
|
284
|
+
}
|
|
285
|
+
try {
|
|
286
|
+
const spectrumXParsed = parsePositionAlias(spectrumX);
|
|
287
|
+
return getSpectrumAbsolutePosition(
|
|
288
|
+
spectrumXParsed.value,
|
|
289
|
+
spectrumWidth,
|
|
290
|
+
backgroundImageWidth
|
|
291
|
+
);
|
|
292
|
+
} catch {
|
|
293
|
+
throw new Error(`Invalid spectrum x value: ${spectrumX}. Valid values: ${getValidPositionAliasValues()}.`);
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
export const getSpectrumYAbsolute = (
|
|
298
|
+
config: Config,
|
|
299
|
+
spectrumHeight: number,
|
|
300
|
+
backgroundImageHeight: number
|
|
301
|
+
) => {
|
|
302
|
+
const spectrumY = getSpectrumY(config);
|
|
303
|
+
if (typeof spectrumY !== 'string') {
|
|
304
|
+
return spectrumY;
|
|
305
|
+
}
|
|
306
|
+
try {
|
|
307
|
+
const spectrumXParsed = parsePositionAlias(spectrumY);
|
|
308
|
+
return getSpectrumAbsolutePosition(
|
|
309
|
+
spectrumXParsed.value,
|
|
310
|
+
spectrumHeight,
|
|
311
|
+
backgroundImageHeight
|
|
312
|
+
);
|
|
313
|
+
} catch {
|
|
314
|
+
throw new Error(`Invalid spectrum y value: ${spectrumY}. Valid values: ${getValidPositionAliasValues()}.`);
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
const getValidRotationAliasValues = () =>
|
|
319
|
+
rotationAliasValues.join(', ');
|
|
320
|
+
|
|
321
|
+
export const checkIsValidRotationAlias = (aliasName: string) => {
|
|
322
|
+
return !!rotationAliasValues.find(al => al === aliasName);
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
export const getSpectrumRotation = (config: Config): RotationAliasName => {
|
|
326
|
+
const rotationValue =
|
|
327
|
+
(config.outVideo.spectrum && config.outVideo.spectrum.rotation) ||
|
|
328
|
+
defaults.spectrumRotation;
|
|
329
|
+
if (!checkIsValidRotationAlias(rotationValue)) {
|
|
330
|
+
throw new Error(`Invalid spectrum rotation value: ${rotationValue}. Valid values: ${getValidRotationAliasValues}.`);
|
|
331
|
+
}
|
|
332
|
+
return rotationValue as RotationAliasName;
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
export const getSpectrumColor = (config: Config) =>
|
|
336
|
+
config.outVideo.spectrum && config.outVideo.spectrum.color;
|
|
337
|
+
|
|
338
|
+
export const getSpectrumOpacity = (config: Config) =>
|
|
339
|
+
(config.outVideo.spectrum && config.outVideo.spectrum.opacity) ||
|
|
340
|
+
defaults.spectrumOpacity;
|
|
341
|
+
|
|
342
|
+
export const getSpectrumOpacityParsed = (config: Config) => {
|
|
343
|
+
const spectrumOpacity = getSpectrumOpacity(config);
|
|
344
|
+
if (!checkIsPercentValue(spectrumOpacity)) {
|
|
345
|
+
throw new Error(`Invalid spectrum opacity value: '${spectrumOpacity}'. Use string percent value, for example '80%'.`);
|
|
346
|
+
}
|
|
347
|
+
const percentValue = parseInt(spectrumOpacity);
|
|
348
|
+
if (
|
|
349
|
+
(percentValue < 0) ||
|
|
350
|
+
(percentValue > 100)
|
|
351
|
+
) {
|
|
352
|
+
throw new Error(`Invalid spectrum opacity value: '${spectrumOpacity}'. Percent values must be in range from '0%' to '100%'.`);
|
|
353
|
+
}
|
|
354
|
+
return percentValue / 100;
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
export const getFfmpeg_cfr = (config: Config) =>
|
|
358
|
+
config.tweaks && config.tweaks.ffmpeg_cfr;
|
|
359
|
+
|
|
360
|
+
export const getFfmpeg_preset = (config: Config) =>
|
|
361
|
+
config.tweaks && config.tweaks.ffmpeg_preset;
|
|
362
|
+
|
|
363
|
+
export const getFrame_processing_delay = (config: Config) =>
|
|
364
|
+
config.tweaks && config.tweaks.frame_processing_delay;
|
|
365
|
+
|
|
366
|
+
export const getVideoTimeouts = (config: Config) => ({
|
|
367
|
+
readVideoFrame:
|
|
368
|
+
config.tweaks?.timeouts?.readVideoFrame ?? defaults.timeouts.readVideoFrame,
|
|
369
|
+
waitDrain:
|
|
370
|
+
config.tweaks?.timeouts?.waitDrain ?? defaults.timeouts.waitDrain,
|
|
371
|
+
waitForProcessExit:
|
|
372
|
+
config.tweaks?.timeouts?.waitForProcessExit ?? defaults.timeouts.waitForProcessExit,
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
// Polar visualizer config getters
|
|
376
|
+
const getPolarX = (config: Config) =>
|
|
377
|
+
parseNumberIfPossible(
|
|
378
|
+
(config.outVideo.polar && config.outVideo.polar.x) ||
|
|
379
|
+
defaults.polarX
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
const getPolarY = (config: Config) =>
|
|
383
|
+
parseNumberIfPossible(
|
|
384
|
+
(config.outVideo.polar && config.outVideo.polar.y) ||
|
|
385
|
+
defaults.polarY
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
export const getPolarXAbsolute = (
|
|
389
|
+
config: Config,
|
|
390
|
+
backgroundImageWidth: number
|
|
391
|
+
) => {
|
|
392
|
+
const polarX = getPolarX(config);
|
|
393
|
+
if (typeof polarX !== 'string') {
|
|
394
|
+
return polarX;
|
|
395
|
+
}
|
|
396
|
+
try {
|
|
397
|
+
const polarXParsed = parsePositionAlias(polarX);
|
|
398
|
+
if (polarXParsed.value === 0) {
|
|
399
|
+
const polarLength = getPolarMaxBarLength(config);
|
|
400
|
+
const polarRadius = getPolarInnerRadius(config);
|
|
401
|
+
return polarRadius + polarLength;
|
|
402
|
+
}
|
|
403
|
+
if (polarXParsed.value === 1) {
|
|
404
|
+
const polarLength = getPolarMaxBarLength(config);
|
|
405
|
+
const polarRadius = getPolarInnerRadius(config);
|
|
406
|
+
return backgroundImageWidth - polarRadius - polarLength;
|
|
407
|
+
}
|
|
408
|
+
return polarXParsed.value * backgroundImageWidth;
|
|
409
|
+
} catch {
|
|
410
|
+
throw new Error(`Invalid polar x value: ${polarX}. Valid values: ${getValidPositionAliasValues()}.`);
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
export const getPolarYAbsolute = (
|
|
415
|
+
config: Config,
|
|
416
|
+
backgroundImageHeight: number
|
|
417
|
+
) => {
|
|
418
|
+
const polarY = getPolarY(config);
|
|
419
|
+
if (typeof polarY !== 'string') {
|
|
420
|
+
return polarY;
|
|
421
|
+
}
|
|
422
|
+
try {
|
|
423
|
+
const polarYParsed = parsePositionAlias(polarY);
|
|
424
|
+
if (polarYParsed.value === 0) {
|
|
425
|
+
const polarLength = getPolarMaxBarLength(config);
|
|
426
|
+
const polarRadius = getPolarInnerRadius(config);
|
|
427
|
+
return polarRadius + polarLength;
|
|
428
|
+
}
|
|
429
|
+
if (polarYParsed.value === 1) {
|
|
430
|
+
const polarLength = getPolarMaxBarLength(config);
|
|
431
|
+
const polarRadius = getPolarInnerRadius(config);
|
|
432
|
+
return backgroundImageHeight - polarRadius - polarLength;
|
|
433
|
+
}
|
|
434
|
+
return polarYParsed.value * backgroundImageHeight;
|
|
435
|
+
} catch {
|
|
436
|
+
throw new Error(`Invalid polar y value: ${polarY}. Valid values: ${getValidPositionAliasValues()}.`);
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
export const getPolarInnerRadius = (config: Config) =>
|
|
441
|
+
(config.outVideo.polar && config.outVideo.polar.innerRadius) ||
|
|
442
|
+
defaults.polarInnerRadius;
|
|
443
|
+
|
|
444
|
+
export const getPolarMaxBarLength = (config: Config) =>
|
|
445
|
+
(config.outVideo.polar && config.outVideo.polar.maxBarLength) ||
|
|
446
|
+
defaults.polarMaxBarLength;
|
|
447
|
+
|
|
448
|
+
export const getPolarBarWidth = (config: Config) =>
|
|
449
|
+
(config.outVideo.polar && config.outVideo.polar.barWidth) ||
|
|
450
|
+
defaults.polarBarWidth;
|
|
451
|
+
|
|
452
|
+
export const getPolarEffect = (config: Config) =>
|
|
453
|
+
config.outVideo.polar?.effect;
|
|
454
|
+
|
|
455
|
+
export const getPolarColor = (config: Config) =>
|
|
456
|
+
config.outVideo.polar && config.outVideo.polar.color;
|
|
457
|
+
|
|
458
|
+
export const getPolarOpacity = (config: Config) =>
|
|
459
|
+
(config.outVideo.polar && config.outVideo.polar.opacity) ||
|
|
460
|
+
defaults.spectrumOpacity;
|
|
461
|
+
|
|
462
|
+
export const getPolarOpacityParsed = (config: Config) => {
|
|
463
|
+
const polarOpacity = getPolarOpacity(config);
|
|
464
|
+
if (!checkIsPercentValue(polarOpacity)) {
|
|
465
|
+
throw new Error(`Invalid polar opacity value: '${polarOpacity}'. Use string percent value, for example '80%'.`);
|
|
466
|
+
}
|
|
467
|
+
const percentValue = parseInt(polarOpacity);
|
|
468
|
+
if (
|
|
469
|
+
(percentValue < 0) ||
|
|
470
|
+
(percentValue > 100)
|
|
471
|
+
) {
|
|
472
|
+
throw new Error(`Invalid polar opacity value: '${polarOpacity}'. Percent values must be in range from '0%' to '100%'.`);
|
|
473
|
+
}
|
|
474
|
+
return percentValue / 100;
|
|
475
|
+
};
|
|
476
|
+
|