import sharp from 'sharp'; import { readdirSync } from 'node:fs'; import { join } from 'node:path'; const [,, dir, out, colsArg = '4', wArg = '360'] = process.argv; const cols = Number(colsArg), w = Number(wArg), h = Math.round(w * 0.66); const files = readdirSync(dir).filter(f => /\.(jpe?g|png|webp)$/i.test(f)).sort(); const tiles = []; for (const [i, f] of files.entries()) { try { const buf = await sharp(join(dir, f)).resize(w, h, { fit: 'cover' }).toBuffer(); const label = Buffer.from(`${i}: ${f.slice(0, 40)}`); const tile = await sharp(buf).composite([{ input: label, top: h - 24, left: 0 }]).toBuffer(); tiles.push({ input: tile, left: (i % cols) * w, top: Math.floor(i / cols) * h }); } catch (e) { console.error('skip', f, e.message); } } const rows = Math.ceil(tiles.length / cols); await sharp({ create: { width: cols * w, height: rows * h, channels: 3, background: '#222' } }).composite(tiles).jpeg({ quality: 80 }).toFile(out); console.log('wrote', out, files.length, 'images');