|
| 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