commit 3d41bc9345ce83fb5202c47f4abb2d16c421ebcd Author: Lisa Date: Fri Apr 11 20:51:13 2025 +0200 Add video.mjs diff --git a/video.mjs b/video.mjs new file mode 100644 index 0000000..29ae753 --- /dev/null +++ b/video.mjs @@ -0,0 +1,103 @@ +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...'); \ No newline at end of file