line-and-surface/scripts/generate-blurhashes.js
2025-07-28 17:36:16 +02:00

147 lines
3.6 KiB
JavaScript

#!/usr/bin/env node
/**
* Script to preprocess image directories and generate blurhashes
* to be stored in files.lst files.
*
* Usage:
* node generate-blurhashes.js <directory>
*
* 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<string>} 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);
});