47 lines
1.3 KiB
JavaScript
47 lines
1.3 KiB
JavaScript
/**
|
|
* Aggressively compresses all photos in public/assets/photos/ to max 1280px
|
|
* and quality 70 using sharp. Target: all files under 200KB.
|
|
*/
|
|
import sharp from 'sharp';
|
|
import { readdir, stat } from 'fs/promises';
|
|
import { join } from 'path';
|
|
|
|
const PHOTOS_DIR = 'public/assets/photos';
|
|
const MAX_WIDTH = 1280;
|
|
const QUALITY = 70;
|
|
const MAX_BYTES = 200_000;
|
|
|
|
const files = await readdir(PHOTOS_DIR);
|
|
const imageFiles = files.filter(f => /\.(jpg|jpeg|JPG|JPEG)$/i.test(f));
|
|
|
|
let compressed = 0;
|
|
let skipped = 0;
|
|
|
|
for (const file of imageFiles) {
|
|
const fullPath = join(PHOTOS_DIR, file);
|
|
const { size } = await stat(fullPath);
|
|
|
|
if (size <= MAX_BYTES) {
|
|
skipped++;
|
|
continue;
|
|
}
|
|
|
|
console.log(`Compressing: ${file} (${Math.round(size / 1024)}KB)`);
|
|
try {
|
|
const buf = await sharp(fullPath)
|
|
.rotate() // auto-orient from EXIF
|
|
.resize({ width: MAX_WIDTH, withoutEnlargement: true })
|
|
.jpeg({ quality: QUALITY, progressive: true, mozjpeg: true })
|
|
.toBuffer();
|
|
|
|
await sharp(buf).toFile(fullPath);
|
|
const afterSize = (await stat(fullPath)).size;
|
|
console.log(` → ${Math.round(afterSize / 1024)}KB (saved ${Math.round((size - afterSize) / 1024)}KB)`);
|
|
compressed++;
|
|
} catch (err) {
|
|
console.error(` ✗ Failed: ${err.message}`);
|
|
}
|
|
}
|
|
|
|
console.log(`\nDone: ${compressed} compressed, ${skipped} already small.`);
|