Procedurally generates a radio weather report
1import { Stitcher } from '../src/stitcher.js';
2import { describe, expect, it, vi, beforeEach } from 'vitest';
3import { EventEmitter } from 'events';
4import { spawn } from 'child_process';
5
6const mockChildProcess = new (class MockChildProcess
7 extends EventEmitter {
8 kill = vi.fn(() => {
9 return true;
10 });
11})();
12
13vi.mock('child_process', () => {
14 return {
15 spawn: vi.fn(() => mockChildProcess)
16 }
17});
18
19describe('stitcher', () => {
20
21 beforeEach(() => {
22 vi.clearAllMocks();
23 });
24
25 it('passes the correct arguments to ffmpeg', async () => {
26 const p = Stitcher(['1.flac', 'dir/2.flac']);
27 mockChildProcess.emit('exit', 0, null);
28 await p;
29 expect(spawn).toBeCalledWith('ffmpeg', [
30 "-i",
31 '1.flac',
32 '-i',
33 'dir/2.flac',
34 '-filter_complex',
35 '[0:a][1:a][2:a]concat=n=2:v=0:a=1[out]',
36 '-map',
37 '[out]',
38 '-ar',
39 '44100',
40 '-ac',
41 '2',
42 '-c:a',
43 'pcm_s16le',
44 'output.wav',
45 '-y'
46 ]);
47 });
48
49 it('throws an error when ffmpeg fails', async () => {
50 const p = Stitcher(['sound.mp3']);
51 mockChildProcess.emit('exit', 1, null);
52 await expect(p).rejects.toThrow('exited with 1');
53 expect(spawn).toBeCalledWith('ffmpeg', [
54 "-i",
55 'sound.mp3',
56 '-filter_complex',
57 '[0:a][1:a][2:a]concat=n=1:v=0:a=1[out]',
58 '-map',
59 '[out]',
60 '-ar',
61 '44100',
62 '-ac',
63 '2',
64 '-c:a',
65 'pcm_s16le',
66 'output.wav',
67 '-y'
68 ]);
69 });
70
71 it('throws an error when ffmpeg takes longer than expected', { timeout: 6000 }, async () => {
72 await expect(Stitcher(['in.wav'])).rejects.toThrow('timed out');
73 expect(spawn).toBeCalledWith('ffmpeg', [
74 "-i",
75 'in.wav',
76 '-filter_complex',
77 '[0:a][1:a][2:a]concat=n=1:v=0:a=1[out]',
78 '-map',
79 '[out]',
80 '-ar',
81 '44100',
82 '-ac',
83 '2',
84 '-c:a',
85 'pcm_s16le',
86 'output.wav',
87 '-y'
88 ]);
89 });
90});