#!/usr/bin/env node /** * Script to preprocess image directories and generate blurhashes * to be stored in files.lst files. * * Usage: * node generate-blurhashes.js * * Example: * node generate-blurhashes.js ../content/motion_source/monstera1 */ const fs = require("fs"); const path = require("path"); const { createCanvas, loadImage } = require("canvas"); const { encode } = require("blurhash"); // Configuration const COMPONENT_X = 4; // Number of X components in blurhash const COMPONENT_Y = 3; // Number of Y components in blurhash /** * Calculate blurhash for an image * @param {string} imagePath - Path to the image * @returns {Promise} The blurhash */ async function calculateBlurhash(imagePath) { try { const image = await loadImage(imagePath); const canvas = createCanvas(image.width, image.height); const ctx = canvas.getContext("2d"); // Draw image to canvas ctx.drawImage(image, 0, 0); // Get image data const imageData = ctx.getImageData(0, 0, image.width, image.height); const pixels = imageData.data; // Encode blurhash const hash = encode( pixels, image.width, image.height, COMPONENT_X, COMPONENT_Y ); return hash; } catch (err) { console.error(`Error processing ${imagePath}:`, err); return null; } } /** * Process a directory and update its files.lst with blurhashes * @param {string} dirPath - Path to the directory containing images */ async function processDirectory(dirPath) { console.log(`Processing directory: ${dirPath}`); // Read directory for image files const files = fs .readdirSync(dirPath) .filter((file) => [".jpg", ".jpeg", ".png", ".webp", ".gif"].includes( path.extname(file).toLowerCase() ) ) .sort((a, b) => { // Natural sort for numbered files (e.g., img001.jpg, img002.jpg) const numA = parseInt(a.match(/\d+/) || "0"); const numB = parseInt(b.match(/\d+/) || "0"); return numA - numB; }); if (files.length === 0) { console.warn(`No image files found in ${dirPath}`); return; } // Process each file and collect blurhashes const fileData = []; let count = 0; const total = files.length; for (const file of files) { count++; const filePath = path.join(dirPath, file); process.stdout.write(`\rProcessing image ${count}/${total}: ${file}`); const hash = await calculateBlurhash(filePath); if (hash) { fileData.push({ file, hash }); } else { // Include file without hash if hash calculation fails fileData.push({ file, hash: null }); } } console.log("\nWriting files.lst..."); // Write to files.lst const outputPath = path.join(dirPath, "files.lst"); const fileLines = fileData.map((entry) => { if (entry.hash) { return `${entry.file}\t${entry.hash}`; } return entry.file; }); fs.writeFileSync(outputPath, fileLines.join("\n")); console.log( `Created files.lst with ${fileData.length} entries (${ fileData.filter((e) => e.hash).length } with blurhashes)` ); } // Main execution async function main() { const args = process.argv.slice(2); if (args.length === 0) { console.error("Please provide a directory path"); process.exit(1); } const dirPath = args[0]; if (!fs.existsSync(dirPath)) { console.error(`Directory does not exist: ${dirPath}`); process.exit(1); } if (!fs.statSync(dirPath).isDirectory()) { console.error(`Not a directory: ${dirPath}`); process.exit(1); } await processDirectory(dirPath); } main().catch((err) => { console.error("Error:", err); process.exit(1); });