import ffmpeg from 'fluent-ffmpeg'; import sharp from 'sharp'; import chalk from 'chalk'; import { exec } from 'child_process'; import fs from 'fs'; const args = process.argv.slice(2); const videoPathIndex = args.indexOf('--path'); if (videoPathIndex === -1 || !args[videoPathIndex + 1]) { console.error('Please provide the video path using --path'); process.exit(1); } const fpsIndex = args.indexOf('--fps'); const resolutionIndex = args.indexOf('--resolution'); const videoPath = args[videoPathIndex + 1]; const frameRate = fpsIndex !== -1 ? parseInt(args[fpsIndex + 1]) || 10 : 10; const resolutionSetting = resolutionIndex !== -1 ? args[resolutionIndex + 1] : 'current'; console.log(`Made with <3 by Lisa Honkay (lisahonkay@gmail.com)`); console.log(`Processing video: ${videoPath}`); console.log(`Using FPS: ${frameRate}`); console.log(`Resolution: ${resolutionSetting}`); const resolutionFilter = resolutionSetting === 'current' ? 'scale=iw:ih' : `scale=${resolutionSetting}`; const framePathPattern = 'frames_%04d.png'; ffmpeg(videoPath) .outputOptions('-vf', `fps=${frameRate},${resolutionFilter}`) .outputOptions('-q:v', '2') .saveToFile(framePathPattern) .on('end', () => { console.log('All frames processed.'); processFrames(); }); let frameQueue = []; function processFrames() { exec(`ls ${framePathPattern.replace('%04d', '*')} | sort`, (error, stdout) => { if (error) { console.error('Error listing frames:', error); return; } frameQueue = stdout.split('\n').filter(file => file); console.log(`Loaded ${frameQueue.length} frames.`); playFrames(); }); } function playFrames() { let index = 0; function renderFrame() { if (index >= frameQueue.length) { console.log('Playback finished.'); return; } console.time('FrameRender'); sharp(frameQueue[index]) .toBuffer() .then(imageBuffer => sharp(imageBuffer).raw().toBuffer({ resolveWithObject: true })) .then(({ data, info }) => { console.timeEnd('FrameRender'); const terminalWidth = process.stdout.columns || 80; const terminalHeight = process.stdout.rows || 24; const aspectRatio = info.width / info.height; const targetWidth = Math.max(20, terminalWidth - 2); const targetHeight = Math.min(Math.floor(targetWidth / aspectRatio), terminalHeight - 2); let output = ''; for (let y = 0; y < targetHeight; y++) { for (let x = 0; x < targetWidth; x++) { const idx = (Math.floor(y * info.height / targetHeight) * info.width + Math.floor(x * info.width / targetWidth)) * 3; const r = data[idx]; const g = data[idx + 1]; const b = data[idx + 2]; output += chalk.rgb(r, g, b)('█'); } output += '\n'; } process.stdout.cursorTo(0, 0); process.stdout.write(output); fs.unlink(frameQueue[index], err => { if (err) console.error('Error deleting frame:', err); }); index++; process.nextTick(renderFrame); }) .catch(error => console.error('Error processing frame:', error)); } setTimeout(renderFrame, 4000); } console.log('Extracting frames, this may take a moment...');