103 lines
3.5 KiB
JavaScript
103 lines
3.5 KiB
JavaScript
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...'); |