generateThumbnails.ts
1 import { IPFSEntry, IPFSHTTPClient } from "kubo-rpc-client/types"; 2 import { Regexes } from 'ipmc-core'; 3 import ffmpeg from 'fluent-ffmpeg'; 4 import fs from 'fs'; 5 import cliProgress from 'cli-progress'; 6 import { createMultiProgress, createProgressFormat } from "./utils/Progress"; 7 8 export async function generateThumbnails(ipfs: IPFSHTTPClient, path: string, parentProgress?: cliProgress.MultiBar) { 9 const progress = parentProgress ?? createMultiProgress(); 10 if (!parentProgress) { 11 progress.log(`Fetching files from <${path}>\n`); 12 } 13 14 const files: any[] = []; 15 for await (const file of ipfs.files.ls(path)) { 16 files.push(file); 17 } 18 19 const checking = progress.create(files.length, 0, { file: '?' }, { 20 format: createProgressFormat({ extra: '{file}' }) 21 }); 22 for (const file of files) { 23 checking.update({ file: file.name }); 24 25 try { 26 await generateForFile(path, file, progress, ipfs); 27 } catch (ex) { 28 progress.log(ex.message + '\n'); 29 } 30 31 checking.increment(); 32 } 33 } 34 35 async function generateForFile(path: string, file: IPFSEntry, progress: cliProgress.MultiBar, ipfs: IPFSHTTPClient) { 36 const components: IPFSEntry[] = []; 37 38 for await (const comp of ipfs.ls(file.cid)) { 39 components.push(comp); 40 } 41 42 if (!components.some(c => Regexes.Thumbnail.exec(c.name))) { 43 const videoFile = components.find(c => Regexes.VideoFile.exec(c.name)); 44 if (!videoFile) { 45 throw new Error(`No Video file found for <${file.name}> ${file.cid.toString()}`); 46 } 47 const fileProgress = progress.create(4, 0, { step: 'Preparing...' }, { 48 format: `| ${file.name} |{bar}| {percentage}% || {value}/{total} || {step}`, 49 }); 50 const updater = setInterval(() => { 51 fileProgress.updateETA(); 52 }, 1000); 53 cleanTempDir(); 54 55 // Write video to disk 56 fileProgress.update(1, { step: 'Writing video to disk...' }); 57 const stream = fs.createWriteStream('./tmp/video.mp4'); 58 for await (const chunk of ipfs.cat(videoFile.cid)) { 59 stream.write(chunk); 60 } 61 stream.close(); 62 63 // Generate thumbnails 64 fileProgress.update(2, { step: 'Generating thumbnails...' }); 65 await new Promise(resolve => ffmpeg('./tmp/video.mp4', { 66 priority: 0, 67 }) 68 .inputOptions('-threads 1') 69 .takeScreenshots({ 70 count: 10, 71 fastSeek: false, 72 size: '640x360', 73 filename: 'thumb%i.png', 74 }, './tmp') 75 .once('end', resolve)); 76 fileProgress.update(3); 77 78 // Remove video 79 fs.rmSync('./tmp/video.mp4'); 80 81 // Add thumbnails 82 const files = fs.readdirSync('./tmp'); 83 for (const localFile of files) { 84 const { cid } = await ipfs.add(fs.createReadStream(`./tmp/${localFile}`), { 85 pin: false, 86 }); 87 ipfs.files.cp(cid, `${path}/${file.name}/${localFile}`) 88 fs.rmSync(`./tmp/${localFile}`); 89 } 90 fileProgress.update(4); 91 92 clearInterval(updater); 93 } 94 } 95 96 function cleanTempDir() { 97 if (fs.existsSync('./tmp')) { 98 const stat = fs.statSync('./tmp'); 99 if (stat.isFile()) { 100 fs.rmSync('./tmp'); 101 fs.mkdirSync('./tmp'); 102 } else { 103 const files = fs.readdirSync('./tmp'); 104 files.forEach(f => fs.rmSync(`./tmp/${f}`)) 105 } 106 } else { 107 fs.mkdirSync('./tmp'); 108 } 109 }