/ packages / tools / src / generateThumbnails.ts
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  }