Skip to content

Commit 9f90ee6

Browse files
committed
feat(video): process video and background image for the video
1 parent 782a338 commit 9f90ee6

File tree

1 file changed

+88
-0
lines changed

1 file changed

+88
-0
lines changed

src/video.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import ffmpeg from 'fluent-ffmpeg';
2+
import { Track } from './audio';
3+
import { Browser, launch } from 'puppeteer';
4+
import config from './config';
5+
import path, { resolve } from 'path';
6+
7+
let window: Browser;
8+
9+
export const launchPage = async () => (window = await launch());
10+
11+
export const closePage = async () => window && (await window.close());
12+
13+
export const prepareSvg = (
14+
bgUrl: string,
15+
songName: string,
16+
artistName: string
17+
) => {
18+
let textX = '10%';
19+
20+
if (songName.length > 20) {
21+
textX = '2%';
22+
}
23+
return `
24+
<style>html,body{margin: 0; padding: 0;}</style>
25+
<link href="https://fonts.googleapis.com/css?family=Poppins&display=swap&text=${songName}${artistName}" rel="stylesheet">
26+
<svg viewBox="0 0 1920 1080" lang="en-US" xmlns="http://www.w3.org/2000/svg">
27+
<defs>
28+
<linearGradient id="bottomGrad" x1="0%" y1="0%" x2="0%" y2="100%">
29+
<stop offset="0%" style="stop-color:rgb(255,255,255);stop-opacity:0" />
30+
<stop offset="100%" style="stop-color:rgb(0,0,0);stop-opacity:.8;" />
31+
</linearGradient>
32+
</defs>
33+
<image href="${bgUrl}" x="0" y="0" width="100%" height="100%" />
34+
<rect x="0" y="40%" width="100%" height="60%" fill="url(#bottomGrad)"/>
35+
<text x="${textX}" style="font-family: 'Poppins', arial; font-weight: bold; font-size: 5em;" y="90%" fill="white">${songName}</text>
36+
<text x="${textX}" style="font-family: 'Poppins', arial; font-size: 3em; font-weight: 300;" y="95%" fill="white">${artistName}</text>
37+
</svg>
38+
`;
39+
};
40+
41+
export const generateImage = async (content: string) => {
42+
console.log('prepping image');
43+
const page = await window.newPage();
44+
await page.setContent(content);
45+
await page.setViewport({ width: 1920, height: 1080, isLandscape: true });
46+
const imageBuffer = await page.screenshot({
47+
omitBackground: true,
48+
fullPage: true,
49+
path: resolve(__dirname, '../assets/out.png'),
50+
});
51+
console.log('image prepared');
52+
await window.close();
53+
54+
return imageBuffer;
55+
};
56+
57+
export const processVideo = (
58+
song: Pick<Track, 'duration' | 'download_url'>,
59+
image: string
60+
): Promise<void> => {
61+
//@ts-ignore
62+
const processChain = ffmpeg(image)
63+
.inputFPS(30)
64+
.loop()
65+
.withSize('1920x1080')
66+
.input(song.download_url + `?client_id=${config.SOUNDCLOUD_CLIENT_ID}`)
67+
.outputOption('-shortest')
68+
.videoCodec('libx264')
69+
.videoBitrate(10000, true)
70+
.audioCodec('aac')
71+
.audioBitrate(384)
72+
.outputOption('-pix_fmt yuv420p')
73+
.outputFPS(30)
74+
.videoFilters(
75+
`fade=in:st=0:d=3,fade=out:st=${song.duration / 1000 - 2}:d=1`
76+
);
77+
78+
return new Promise((resolve, reject) => {
79+
processChain
80+
.on('start', (cmd: string) => console.log(cmd))
81+
.on('progress', function(progress: { percent: number }) {
82+
console.log(progress);
83+
})
84+
.on('end', resolve)
85+
.on('error', reject)
86+
.save(path.resolve(__dirname, '../assets/out.mp4'));
87+
});
88+
};

0 commit comments

Comments
 (0)