nodejs-audio-visualizer 4.5.0 → 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 -80
- 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 -77
- package/dist/config.js +369 -369
- 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 -79
- package/dist/index.js +531 -531
- 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 -426
- 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 -476
- package/src/dsp.ts +95 -95
- package/src/highlight.ts +284 -284
- package/src/image.ts +602 -602
- package/src/index.ts +713 -713
- package/src/subtitleConvert.ts +63 -63
- package/src/tests/audio.test.ts +96 -96
- package/src/tests/config.test.ts +496 -496
- 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 -542
- package/src/waitForEventLoop.ts +2 -2
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import { expect } from 'chai';
|
|
2
|
-
import { lrcToSrt, isLikelyLrc, normalizeInlineSubtitlesToSrt } from '../subtitleConvert';
|
|
3
|
-
|
|
4
|
-
describe('subtitleConvert', function () {
|
|
5
|
-
it('isLikelyLrc', function () {
|
|
6
|
-
expect(isLikelyLrc('[00:01.00]Hello')).equal(true);
|
|
7
|
-
expect(isLikelyLrc('1\n00:00:01,000 --> 00:00:02,000\nSRT')).equal(false);
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
it('lrcToSrt converts basic LRC to SRT', function () {
|
|
11
|
-
const lrc = '[00:01.00]Line one\n[00:03.50]Line two';
|
|
12
|
-
const srt = lrcToSrt(lrc);
|
|
13
|
-
expect(srt).to.include('00:00:01,000 --> 00:00:03,500');
|
|
14
|
-
expect(srt).to.include('Line one');
|
|
15
|
-
expect(srt).to.include('00:00:03,500 -->');
|
|
16
|
-
expect(srt).to.include('Line two');
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('normalizeInlineSubtitlesToSrt leaves SRT unchanged', function () {
|
|
20
|
-
const s = '1\n00:00:00,000 --> 00:00:01,000\nX';
|
|
21
|
-
expect(normalizeInlineSubtitlesToSrt(s)).equal(s);
|
|
22
|
-
});
|
|
23
|
-
});
|
|
1
|
+
import { expect } from 'chai';
|
|
2
|
+
import { lrcToSrt, isLikelyLrc, normalizeInlineSubtitlesToSrt } from '../subtitleConvert';
|
|
3
|
+
|
|
4
|
+
describe('subtitleConvert', function () {
|
|
5
|
+
it('isLikelyLrc', function () {
|
|
6
|
+
expect(isLikelyLrc('[00:01.00]Hello')).equal(true);
|
|
7
|
+
expect(isLikelyLrc('1\n00:00:01,000 --> 00:00:02,000\nSRT')).equal(false);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('lrcToSrt converts basic LRC to SRT', function () {
|
|
11
|
+
const lrc = '[00:01.00]Line one\n[00:03.50]Line two';
|
|
12
|
+
const srt = lrcToSrt(lrc);
|
|
13
|
+
expect(srt).to.include('00:00:01,000 --> 00:00:03,500');
|
|
14
|
+
expect(srt).to.include('Line one');
|
|
15
|
+
expect(srt).to.include('00:00:03,500 -->');
|
|
16
|
+
expect(srt).to.include('Line two');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('normalizeInlineSubtitlesToSrt leaves SRT unchanged', function () {
|
|
20
|
+
const s = '1\n00:00:00,000 --> 00:00:01,000\nX';
|
|
21
|
+
expect(normalizeInlineSubtitlesToSrt(s)).equal(s);
|
|
22
|
+
});
|
|
23
|
+
});
|
package/src/tests/video.test.ts
CHANGED
|
@@ -1,216 +1,285 @@
|
|
|
1
|
-
import { expect } from 'chai';
|
|
2
|
-
import { Writable, Readable, Pipe } from 'stream';
|
|
3
|
-
import { EventEmitter } from 'events';
|
|
4
|
-
import { spawnFfmpegVideoWriter, waitDrain, readVideoFrame, waitForProcessExit } from '../video';
|
|
5
|
-
import { createSandbox, SinonStub } from 'sinon';
|
|
6
|
-
import child_process, { ChildProcessWithoutNullStreams } from 'child_process';
|
|
7
|
-
|
|
8
|
-
let childProcessStream = {
|
|
9
|
-
stdin: new Writable(),
|
|
10
|
-
stderr: new Readable(),
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
const videoSandbox = createSandbox();
|
|
14
|
-
|
|
15
|
-
describe('video', function () {
|
|
16
|
-
|
|
17
|
-
this.beforeAll(function () {
|
|
18
|
-
videoSandbox.stub(child_process, 'spawn').returns(childProcessStream as ChildProcessWithoutNullStreams);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
this.afterAll(function () {
|
|
22
|
-
videoSandbox.restore();
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('spawnFfmpegVideoWriter returns truthy value', function () {
|
|
26
|
-
const childProcessReadableStream = new Readable();
|
|
27
|
-
childProcessReadableStream._read = () => { };
|
|
28
|
-
const childProcessWritableStream = new Writable();
|
|
29
|
-
(<Pipe>childProcessWritableStream.pipe) = () => childProcessWritableStream;
|
|
30
|
-
|
|
31
|
-
childProcessStream.stdin = childProcessWritableStream;
|
|
32
|
-
childProcessStream.stderr = childProcessReadableStream;
|
|
33
|
-
|
|
34
|
-
const result = spawnFfmpegVideoWriter({ audioFilename: 'test', videoFileName: 'test', fps: 11 });
|
|
35
|
-
expect(!!result).equal(true);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('spawnFfmpegVideoWriter uses -ss and -t for single audioSegment', function () {
|
|
39
|
-
const childProcessReadableStream = new Readable();
|
|
40
|
-
childProcessReadableStream._read = () => { };
|
|
41
|
-
const childProcessWritableStream = new Writable();
|
|
42
|
-
(<Pipe>childProcessWritableStream.pipe) = () => childProcessWritableStream;
|
|
43
|
-
|
|
44
|
-
childProcessStream.stdin = childProcessWritableStream;
|
|
45
|
-
childProcessStream.stderr = childProcessReadableStream;
|
|
46
|
-
|
|
47
|
-
let spawnArgs: string[] = [];
|
|
48
|
-
const spawnStub = child_process.spawn as SinonStub;
|
|
49
|
-
spawnStub.callsFake((_cmd: string, args: string[]) => {
|
|
50
|
-
spawnArgs = args;
|
|
51
|
-
return childProcessStream as ChildProcessWithoutNullStreams;
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
spawnFfmpegVideoWriter({
|
|
55
|
-
audioFilename: 'audio.mp3',
|
|
56
|
-
videoFileName: 'out.mp4',
|
|
57
|
-
fps: 25,
|
|
58
|
-
audioSegment: { seekSeconds: 12, durationSeconds: 15 },
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
spawnStub.resetBehavior();
|
|
62
|
-
spawnStub.returns(childProcessStream as ChildProcessWithoutNullStreams);
|
|
63
|
-
|
|
64
|
-
expect(spawnArgs).to.not.include('-filter_complex');
|
|
65
|
-
const ssIdx = spawnArgs.indexOf('-ss');
|
|
66
|
-
expect(ssIdx).greaterThan(-1);
|
|
67
|
-
expect(spawnArgs[ssIdx + 1]).to.equal('12');
|
|
68
|
-
const tIdx = spawnArgs.indexOf('-t');
|
|
69
|
-
expect(tIdx).greaterThan(-1);
|
|
70
|
-
expect(spawnArgs[tIdx + 1]).to.equal('15');
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('spawnFfmpegVideoWriter adds subtitles filter when subtitle file is provided', function () {
|
|
74
|
-
const childProcessReadableStream = new Readable();
|
|
75
|
-
childProcessReadableStream._read = () => { };
|
|
76
|
-
const childProcessWritableStream = new Writable();
|
|
77
|
-
(<Pipe>childProcessWritableStream.pipe) = () => childProcessWritableStream;
|
|
78
|
-
|
|
79
|
-
childProcessStream.stdin = childProcessWritableStream;
|
|
80
|
-
childProcessStream.stderr = childProcessReadableStream;
|
|
81
|
-
|
|
82
|
-
let spawnArgs: string[] = [];
|
|
83
|
-
const spawnStub = child_process.spawn as SinonStub;
|
|
84
|
-
spawnStub.callsFake((_cmd: string, args: string[]) => {
|
|
85
|
-
spawnArgs = args;
|
|
86
|
-
return childProcessStream as ChildProcessWithoutNullStreams;
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
spawnFfmpegVideoWriter({
|
|
90
|
-
audioFilename: 'audio.mp3',
|
|
91
|
-
subtitleFilename: '/tmp/subtitles.srt',
|
|
92
|
-
videoFileName: 'out.mp4',
|
|
93
|
-
fps: 25,
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
spawnStub.resetBehavior();
|
|
97
|
-
spawnStub.returns(childProcessStream as ChildProcessWithoutNullStreams);
|
|
98
|
-
|
|
99
|
-
const vfIdx = spawnArgs.indexOf('-vf');
|
|
100
|
-
expect(vfIdx).greaterThan(-1);
|
|
101
|
-
expect(spawnArgs[vfIdx + 1]).to.equal(
|
|
102
|
-
"subtitles='/tmp/subtitles.srt':force_style='Alignment=2'",
|
|
103
|
-
);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('spawnFfmpegVideoWriter passes subtitle Alignment from subtitleAlignmentAss', function () {
|
|
107
|
-
const childProcessReadableStream = new Readable();
|
|
108
|
-
childProcessReadableStream._read = () => { };
|
|
109
|
-
const childProcessWritableStream = new Writable();
|
|
110
|
-
(<Pipe>childProcessWritableStream.pipe) = () => childProcessWritableStream;
|
|
111
|
-
|
|
112
|
-
childProcessStream.stdin = childProcessWritableStream;
|
|
113
|
-
childProcessStream.stderr = childProcessReadableStream;
|
|
114
|
-
|
|
115
|
-
let spawnArgs: string[] = [];
|
|
116
|
-
const spawnStub = child_process.spawn as SinonStub;
|
|
117
|
-
spawnStub.callsFake((_cmd: string, args: string[]) => {
|
|
118
|
-
spawnArgs = args;
|
|
119
|
-
return childProcessStream as ChildProcessWithoutNullStreams;
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
spawnFfmpegVideoWriter({
|
|
123
|
-
audioFilename: 'audio.mp3',
|
|
124
|
-
subtitleFilename: '/tmp/subtitles.srt',
|
|
125
|
-
subtitleAlignmentAss: 8,
|
|
126
|
-
videoFileName: 'out.mp4',
|
|
127
|
-
fps: 25,
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
spawnStub.resetBehavior();
|
|
131
|
-
spawnStub.returns(childProcessStream as ChildProcessWithoutNullStreams);
|
|
132
|
-
|
|
133
|
-
const vfIdx = spawnArgs.indexOf('-vf');
|
|
134
|
-
expect(vfIdx).greaterThan(-1);
|
|
135
|
-
expect(spawnArgs[vfIdx + 1]).to.equal(
|
|
136
|
-
"subtitles='/tmp/subtitles.srt':force_style='Alignment=8'",
|
|
137
|
-
);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it('spawnFfmpegVideoWriter configures image pipe input before output file', function () {
|
|
141
|
-
const childProcessReadableStream = new Readable();
|
|
142
|
-
childProcessReadableStream._read = () => { };
|
|
143
|
-
const childProcessWritableStream = new Writable();
|
|
144
|
-
(<Pipe>childProcessWritableStream.pipe) = () => childProcessWritableStream;
|
|
145
|
-
|
|
146
|
-
childProcessStream.stdin = childProcessWritableStream;
|
|
147
|
-
childProcessStream.stderr = childProcessReadableStream;
|
|
148
|
-
|
|
149
|
-
let spawnArgs: string[] = [];
|
|
150
|
-
const spawnStub = child_process.spawn as SinonStub;
|
|
151
|
-
spawnStub.callsFake((_cmd: string, args: string[]) => {
|
|
152
|
-
spawnArgs = args;
|
|
153
|
-
return childProcessStream as ChildProcessWithoutNullStreams;
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
spawnFfmpegVideoWriter({
|
|
157
|
-
audioFilename: 'audio.mp3',
|
|
158
|
-
videoFileName: 'out.mp4',
|
|
159
|
-
fps: 23.976,
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
spawnStub.resetBehavior();
|
|
163
|
-
spawnStub.returns(childProcessStream as ChildProcessWithoutNullStreams);
|
|
164
|
-
|
|
165
|
-
const audioInputIndex = spawnArgs.indexOf('-i');
|
|
166
|
-
expect(audioInputIndex).greaterThan(-1);
|
|
167
|
-
expect(spawnArgs[audioInputIndex + 1]).to.equal('audio.mp3');
|
|
168
|
-
const imagePipeFlagIndex = spawnArgs.indexOf('image2pipe');
|
|
169
|
-
expect(imagePipeFlagIndex).greaterThan(-1);
|
|
170
|
-
expect(spawnArgs[imagePipeFlagIndex - 1]).to.equal('-f');
|
|
171
|
-
const inputFpsIndex = spawnArgs.indexOf('-framerate');
|
|
172
|
-
expect(inputFpsIndex).greaterThan(-1);
|
|
173
|
-
expect(spawnArgs[inputFpsIndex + 1]).to.equal('23.976');
|
|
174
|
-
const pipeInputIndex = spawnArgs.lastIndexOf('-i');
|
|
175
|
-
expect(spawnArgs[pipeInputIndex + 1]).to.equal('-');
|
|
176
|
-
expect(spawnArgs.includes('-shortest')).to.equal(true);
|
|
177
|
-
expect(spawnArgs[spawnArgs.length - 1]).to.equal('out.mp4');
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it('waitDrain resolves true on drain', async function () {
|
|
181
|
-
const writable = new Writable();
|
|
182
|
-
const waitPromise = waitDrain(writable, undefined, 50);
|
|
183
|
-
setTimeout(() => writable.emit('drain'), 5);
|
|
184
|
-
const isDrained = await waitPromise;
|
|
185
|
-
expect(isDrained).equal(true);
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
it('waitDrain resolves false on timeout', async function () {
|
|
189
|
-
const writable = new Writable();
|
|
190
|
-
const isDrained = await waitDrain(writable, undefined, 5);
|
|
191
|
-
expect(isDrained).equal(false);
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it('readVideoFrame resolves null on timeout', async function () {
|
|
195
|
-
const readable = new Readable();
|
|
196
|
-
readable._read = () => {};
|
|
197
|
-
const frame = await readVideoFrame(readable, 4, 5);
|
|
198
|
-
expect(frame).equal(null);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
it('
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
1
|
+
import { expect } from 'chai';
|
|
2
|
+
import { Writable, Readable, Pipe } from 'stream';
|
|
3
|
+
import { EventEmitter } from 'events';
|
|
4
|
+
import { spawnFfmpegVideoWriter, waitDrain, readVideoFrame, waitForProcessExit, spawnConcatVideoFrameReader } from '../video';
|
|
5
|
+
import { createSandbox, SinonStub } from 'sinon';
|
|
6
|
+
import child_process, { ChildProcessWithoutNullStreams } from 'child_process';
|
|
7
|
+
|
|
8
|
+
let childProcessStream = {
|
|
9
|
+
stdin: new Writable(),
|
|
10
|
+
stderr: new Readable(),
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const videoSandbox = createSandbox();
|
|
14
|
+
|
|
15
|
+
describe('video', function () {
|
|
16
|
+
|
|
17
|
+
this.beforeAll(function () {
|
|
18
|
+
videoSandbox.stub(child_process, 'spawn').returns(childProcessStream as ChildProcessWithoutNullStreams);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
this.afterAll(function () {
|
|
22
|
+
videoSandbox.restore();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('spawnFfmpegVideoWriter returns truthy value', function () {
|
|
26
|
+
const childProcessReadableStream = new Readable();
|
|
27
|
+
childProcessReadableStream._read = () => { };
|
|
28
|
+
const childProcessWritableStream = new Writable();
|
|
29
|
+
(<Pipe>childProcessWritableStream.pipe) = () => childProcessWritableStream;
|
|
30
|
+
|
|
31
|
+
childProcessStream.stdin = childProcessWritableStream;
|
|
32
|
+
childProcessStream.stderr = childProcessReadableStream;
|
|
33
|
+
|
|
34
|
+
const result = spawnFfmpegVideoWriter({ audioFilename: 'test', videoFileName: 'test', fps: 11 });
|
|
35
|
+
expect(!!result).equal(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('spawnFfmpegVideoWriter uses -ss and -t for single audioSegment', function () {
|
|
39
|
+
const childProcessReadableStream = new Readable();
|
|
40
|
+
childProcessReadableStream._read = () => { };
|
|
41
|
+
const childProcessWritableStream = new Writable();
|
|
42
|
+
(<Pipe>childProcessWritableStream.pipe) = () => childProcessWritableStream;
|
|
43
|
+
|
|
44
|
+
childProcessStream.stdin = childProcessWritableStream;
|
|
45
|
+
childProcessStream.stderr = childProcessReadableStream;
|
|
46
|
+
|
|
47
|
+
let spawnArgs: string[] = [];
|
|
48
|
+
const spawnStub = child_process.spawn as SinonStub;
|
|
49
|
+
spawnStub.callsFake((_cmd: string, args: string[]) => {
|
|
50
|
+
spawnArgs = args;
|
|
51
|
+
return childProcessStream as ChildProcessWithoutNullStreams;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
spawnFfmpegVideoWriter({
|
|
55
|
+
audioFilename: 'audio.mp3',
|
|
56
|
+
videoFileName: 'out.mp4',
|
|
57
|
+
fps: 25,
|
|
58
|
+
audioSegment: { seekSeconds: 12, durationSeconds: 15 },
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
spawnStub.resetBehavior();
|
|
62
|
+
spawnStub.returns(childProcessStream as ChildProcessWithoutNullStreams);
|
|
63
|
+
|
|
64
|
+
expect(spawnArgs).to.not.include('-filter_complex');
|
|
65
|
+
const ssIdx = spawnArgs.indexOf('-ss');
|
|
66
|
+
expect(ssIdx).greaterThan(-1);
|
|
67
|
+
expect(spawnArgs[ssIdx + 1]).to.equal('12');
|
|
68
|
+
const tIdx = spawnArgs.indexOf('-t');
|
|
69
|
+
expect(tIdx).greaterThan(-1);
|
|
70
|
+
expect(spawnArgs[tIdx + 1]).to.equal('15');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('spawnFfmpegVideoWriter adds subtitles filter when subtitle file is provided', function () {
|
|
74
|
+
const childProcessReadableStream = new Readable();
|
|
75
|
+
childProcessReadableStream._read = () => { };
|
|
76
|
+
const childProcessWritableStream = new Writable();
|
|
77
|
+
(<Pipe>childProcessWritableStream.pipe) = () => childProcessWritableStream;
|
|
78
|
+
|
|
79
|
+
childProcessStream.stdin = childProcessWritableStream;
|
|
80
|
+
childProcessStream.stderr = childProcessReadableStream;
|
|
81
|
+
|
|
82
|
+
let spawnArgs: string[] = [];
|
|
83
|
+
const spawnStub = child_process.spawn as SinonStub;
|
|
84
|
+
spawnStub.callsFake((_cmd: string, args: string[]) => {
|
|
85
|
+
spawnArgs = args;
|
|
86
|
+
return childProcessStream as ChildProcessWithoutNullStreams;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
spawnFfmpegVideoWriter({
|
|
90
|
+
audioFilename: 'audio.mp3',
|
|
91
|
+
subtitleFilename: '/tmp/subtitles.srt',
|
|
92
|
+
videoFileName: 'out.mp4',
|
|
93
|
+
fps: 25,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
spawnStub.resetBehavior();
|
|
97
|
+
spawnStub.returns(childProcessStream as ChildProcessWithoutNullStreams);
|
|
98
|
+
|
|
99
|
+
const vfIdx = spawnArgs.indexOf('-vf');
|
|
100
|
+
expect(vfIdx).greaterThan(-1);
|
|
101
|
+
expect(spawnArgs[vfIdx + 1]).to.equal(
|
|
102
|
+
"subtitles='/tmp/subtitles.srt':force_style='Alignment=2'",
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('spawnFfmpegVideoWriter passes subtitle Alignment from subtitleAlignmentAss', function () {
|
|
107
|
+
const childProcessReadableStream = new Readable();
|
|
108
|
+
childProcessReadableStream._read = () => { };
|
|
109
|
+
const childProcessWritableStream = new Writable();
|
|
110
|
+
(<Pipe>childProcessWritableStream.pipe) = () => childProcessWritableStream;
|
|
111
|
+
|
|
112
|
+
childProcessStream.stdin = childProcessWritableStream;
|
|
113
|
+
childProcessStream.stderr = childProcessReadableStream;
|
|
114
|
+
|
|
115
|
+
let spawnArgs: string[] = [];
|
|
116
|
+
const spawnStub = child_process.spawn as SinonStub;
|
|
117
|
+
spawnStub.callsFake((_cmd: string, args: string[]) => {
|
|
118
|
+
spawnArgs = args;
|
|
119
|
+
return childProcessStream as ChildProcessWithoutNullStreams;
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
spawnFfmpegVideoWriter({
|
|
123
|
+
audioFilename: 'audio.mp3',
|
|
124
|
+
subtitleFilename: '/tmp/subtitles.srt',
|
|
125
|
+
subtitleAlignmentAss: 8,
|
|
126
|
+
videoFileName: 'out.mp4',
|
|
127
|
+
fps: 25,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
spawnStub.resetBehavior();
|
|
131
|
+
spawnStub.returns(childProcessStream as ChildProcessWithoutNullStreams);
|
|
132
|
+
|
|
133
|
+
const vfIdx = spawnArgs.indexOf('-vf');
|
|
134
|
+
expect(vfIdx).greaterThan(-1);
|
|
135
|
+
expect(spawnArgs[vfIdx + 1]).to.equal(
|
|
136
|
+
"subtitles='/tmp/subtitles.srt':force_style='Alignment=8'",
|
|
137
|
+
);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('spawnFfmpegVideoWriter configures image pipe input before output file', function () {
|
|
141
|
+
const childProcessReadableStream = new Readable();
|
|
142
|
+
childProcessReadableStream._read = () => { };
|
|
143
|
+
const childProcessWritableStream = new Writable();
|
|
144
|
+
(<Pipe>childProcessWritableStream.pipe) = () => childProcessWritableStream;
|
|
145
|
+
|
|
146
|
+
childProcessStream.stdin = childProcessWritableStream;
|
|
147
|
+
childProcessStream.stderr = childProcessReadableStream;
|
|
148
|
+
|
|
149
|
+
let spawnArgs: string[] = [];
|
|
150
|
+
const spawnStub = child_process.spawn as SinonStub;
|
|
151
|
+
spawnStub.callsFake((_cmd: string, args: string[]) => {
|
|
152
|
+
spawnArgs = args;
|
|
153
|
+
return childProcessStream as ChildProcessWithoutNullStreams;
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
spawnFfmpegVideoWriter({
|
|
157
|
+
audioFilename: 'audio.mp3',
|
|
158
|
+
videoFileName: 'out.mp4',
|
|
159
|
+
fps: 23.976,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
spawnStub.resetBehavior();
|
|
163
|
+
spawnStub.returns(childProcessStream as ChildProcessWithoutNullStreams);
|
|
164
|
+
|
|
165
|
+
const audioInputIndex = spawnArgs.indexOf('-i');
|
|
166
|
+
expect(audioInputIndex).greaterThan(-1);
|
|
167
|
+
expect(spawnArgs[audioInputIndex + 1]).to.equal('audio.mp3');
|
|
168
|
+
const imagePipeFlagIndex = spawnArgs.indexOf('image2pipe');
|
|
169
|
+
expect(imagePipeFlagIndex).greaterThan(-1);
|
|
170
|
+
expect(spawnArgs[imagePipeFlagIndex - 1]).to.equal('-f');
|
|
171
|
+
const inputFpsIndex = spawnArgs.indexOf('-framerate');
|
|
172
|
+
expect(inputFpsIndex).greaterThan(-1);
|
|
173
|
+
expect(spawnArgs[inputFpsIndex + 1]).to.equal('23.976');
|
|
174
|
+
const pipeInputIndex = spawnArgs.lastIndexOf('-i');
|
|
175
|
+
expect(spawnArgs[pipeInputIndex + 1]).to.equal('-');
|
|
176
|
+
expect(spawnArgs.includes('-shortest')).to.equal(true);
|
|
177
|
+
expect(spawnArgs[spawnArgs.length - 1]).to.equal('out.mp4');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('waitDrain resolves true on drain', async function () {
|
|
181
|
+
const writable = new Writable();
|
|
182
|
+
const waitPromise = waitDrain(writable, undefined, 50);
|
|
183
|
+
setTimeout(() => writable.emit('drain'), 5);
|
|
184
|
+
const isDrained = await waitPromise;
|
|
185
|
+
expect(isDrained).equal(true);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('waitDrain resolves false on timeout', async function () {
|
|
189
|
+
const writable = new Writable();
|
|
190
|
+
const isDrained = await waitDrain(writable, undefined, 5);
|
|
191
|
+
expect(isDrained).equal(false);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('readVideoFrame resolves null on timeout', async function () {
|
|
195
|
+
const readable = new Readable();
|
|
196
|
+
readable._read = () => {};
|
|
197
|
+
const frame = await readVideoFrame(readable, 4, 5);
|
|
198
|
+
expect(frame).equal(null);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('readVideoFrame assembles a frame from multiple chunks', async function () {
|
|
202
|
+
const readable = new Readable();
|
|
203
|
+
readable._read = () => {};
|
|
204
|
+
const framePromise = readVideoFrame(readable, 6, 200);
|
|
205
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
206
|
+
readable.push(Buffer.from([1, 2]));
|
|
207
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
208
|
+
readable.push(Buffer.from([3, 4]));
|
|
209
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
210
|
+
readable.push(Buffer.from([5, 6]));
|
|
211
|
+
const frame = await framePromise;
|
|
212
|
+
expect(!!frame && frame.equals(Buffer.from([1, 2, 3, 4, 5, 6]))).equal(true);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('readVideoFrame does not timeout after a successful chunked read', async function () {
|
|
216
|
+
const errors: any[] = [];
|
|
217
|
+
const originalError = console.error;
|
|
218
|
+
console.error = (...args: any[]) => {
|
|
219
|
+
errors.push(args);
|
|
220
|
+
};
|
|
221
|
+
const readable = new Readable();
|
|
222
|
+
readable._read = () => {};
|
|
223
|
+
try {
|
|
224
|
+
const timeoutMs = 40;
|
|
225
|
+
const framePromise = readVideoFrame(readable, 4, timeoutMs);
|
|
226
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
227
|
+
readable.push(Buffer.from([1, 2]));
|
|
228
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
229
|
+
readable.push(Buffer.from([3, 4]));
|
|
230
|
+
const frame = await framePromise;
|
|
231
|
+
expect(!!frame && frame.equals(Buffer.from([1, 2, 3, 4]))).equal(true);
|
|
232
|
+
await new Promise(resolve => setTimeout(resolve, timeoutMs + 30));
|
|
233
|
+
expect(errors).deep.equal([]);
|
|
234
|
+
} finally {
|
|
235
|
+
console.error = originalError;
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('spawnConcatVideoFrameReader regenerates timestamps for auto-edit concat', function () {
|
|
240
|
+
const childProcessReadableStream = new Readable();
|
|
241
|
+
childProcessReadableStream._read = () => { };
|
|
242
|
+
const childProcessWritableStream = new Writable();
|
|
243
|
+
(<Pipe>childProcessWritableStream.pipe) = () => childProcessWritableStream;
|
|
244
|
+
|
|
245
|
+
childProcessStream.stdin = childProcessWritableStream;
|
|
246
|
+
childProcessStream.stderr = childProcessReadableStream;
|
|
247
|
+
|
|
248
|
+
let spawnArgs: string[] = [];
|
|
249
|
+
const spawnStub = child_process.spawn as SinonStub;
|
|
250
|
+
spawnStub.callsFake((_cmd: string, args: string[]) => {
|
|
251
|
+
spawnArgs = args;
|
|
252
|
+
return childProcessStream as ChildProcessWithoutNullStreams;
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
spawnConcatVideoFrameReader({
|
|
256
|
+
concatFilePath: '/tmp/av-concat.txt',
|
|
257
|
+
fps: 30,
|
|
258
|
+
totalFrames: 90,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
spawnStub.resetBehavior();
|
|
262
|
+
spawnStub.returns(childProcessStream as ChildProcessWithoutNullStreams);
|
|
263
|
+
|
|
264
|
+
expect(spawnArgs.indexOf('-fflags')).greaterThan(-1);
|
|
265
|
+
expect(spawnArgs[spawnArgs.indexOf('-fflags') + 1]).equal('+genpts');
|
|
266
|
+
expect(spawnArgs.indexOf('-an')).greaterThan(-1);
|
|
267
|
+
expect(spawnArgs[spawnArgs.indexOf('-avoid_negative_ts') + 1]).equal('make_zero');
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('waitForProcessExit resolves exit code', async function () {
|
|
271
|
+
const processEmitter = new EventEmitter();
|
|
272
|
+
const waitPromise = waitForProcessExit(processEmitter, 50);
|
|
273
|
+
setTimeout(() => processEmitter.emit('exit', 0), 5);
|
|
274
|
+
const { exitCode, reason } = await waitPromise;
|
|
275
|
+
expect(exitCode).equal(0);
|
|
276
|
+
expect(reason).equal(undefined);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('waitForProcessExit resolves non-zero on timeout', async function () {
|
|
280
|
+
const processEmitter = new EventEmitter();
|
|
281
|
+
const { exitCode, reason } = await waitForProcessExit(processEmitter, 5);
|
|
282
|
+
expect(exitCode).equal(1);
|
|
283
|
+
expect(reason).equal('waitForProcessExit timeout (5ms)');
|
|
284
|
+
});
|
|
285
|
+
});
|
package/src/typings.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
declare module 'fft-js' {
|
|
2
|
-
export function fft(signal: number[]): [number, number][];
|
|
3
|
-
export const util: {
|
|
4
|
-
fftFreq(phasors: [number, number][], sampleRate: number): number[];
|
|
5
|
-
fftMag(phasors: [number, number][]): number[];
|
|
6
|
-
};
|
|
1
|
+
declare module 'fft-js' {
|
|
2
|
+
export function fft(signal: number[]): [number, number][];
|
|
3
|
+
export const util: {
|
|
4
|
+
fftFreq(phasors: [number, number][], sampleRate: number): number[];
|
|
5
|
+
fftMag(phasors: [number, number][]): number[];
|
|
6
|
+
};
|
|
7
7
|
}
|