#!/usr/bin/env node /** * Script to preprocess image directories and generate thumbnail data URIs * 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"); // Configuration const MAX_THUMBNAIL_SIZE = 32; /** * Create a thumbnail data URI for an image * @param {string} imagePath - Path to the image * @returns {Promise} The data URI */ async function createThumbnailDataURI(imagePath) { try { const image = await loadImage(imagePath); // Create a small canvas for thumbnail const aspectRatio = image.width / image.height; let thumbnailWidth = MAX_THUMBNAIL_SIZE; let thumbnailHeight = MAX_THUMBNAIL_SIZE; if (aspectRatio > 1) { thumbnailHeight = Math.round(thumbnailWidth / aspectRatio); } else { thumbnailWidth = Math.round(thumbnailHeight * aspectRatio); } const canvas = createCanvas(thumbnailWidth, thumbnailHeight); const ctx = canvas.getContext("2d"); // Draw image to thumbnail size ctx.drawImage(image, 0, 0, thumbnailWidth, thumbnailHeight); // Convert to data URI const dataURI = canvas.toDataURL("image/png"); return dataURI; } catch (err) { console.error(`Error processing ${imagePath}:`, err); return null; } } /** * Process a directory and update its files.lst with thumbnail data URIs * @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 dataURI = await createThumbnailDataURI(filePath); if (dataURI) { fileData.push({ file, dataURI }); } else { // Include file without data URI if creation fails fileData.push({ file, dataURI: null }); } } console.log("\nWriting files.lst..."); // Write to files.lst const outputPath = path.join(dirPath, "files.lst"); const fileLines = fileData.map((entry) => { if (entry.dataURI) { return `${entry.file}\t${entry.dataURI}`; } return entry.file; }); fs.writeFileSync(outputPath, fileLines.join("\n")); console.log( `Created files.lst with ${fileData.length} entries (${ fileData.filter((e) => e.dataURI).length } with thumbnail data URIs)` ); } // 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); });