yslootahrobotics/scripts/aggressive-compress.mjs
Najjar\NajjarV02 5a40f6b733
Some checks are pending
CI/CD / test-and-build (push) Waiting to run
CI/CD / deploy (push) Blocked by required conditions
perf: aggressive webp re-compression + max-width 1920 resize
- recompress every webp at q=70-82 depending on size band
- resize anything wider than 1920px to 1920 max
- convert remaining png/jpg holdouts (mine, thermal, dex3-1 etc) to webp
- restore apple-touch-icon.png (iOS requires png)
- 86 files recompressed, saved 4.36 MB
- public/ images: 11.7 MB -> 6.69 MB (-43%)
- total trajectory: 23.3 MB -> 6.69 MB (-71%)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 17:31:56 +04:00

86 lines
2.7 KiB
JavaScript

import { promises as fs } from 'node:fs';
import path from 'node:path';
import sharp from 'sharp';
const ROOT = path.resolve('public');
const SKIP = new Set(['favicon.ico']);
const MAX_W = 1920; // cap any image wider than this
const exts = new Set(['.jpg', '.jpeg', '.png', '.gif', '.webp']);
let convertedCount = 0;
let savedBytes = 0;
let skippedCount = 0;
async function walk(dir) {
const entries = await fs.readdir(dir, { withFileTypes: true });
for (const e of entries) {
const p = path.join(dir, e.name);
if (e.isDirectory()) await walk(p);
else if (exts.has(path.extname(p).toLowerCase())) await compress(p);
}
}
function qualityFor(bytes) {
if (bytes > 700 * 1024) return 70;
if (bytes > 300 * 1024) return 75;
if (bytes > 120 * 1024) return 78;
return 82;
}
async function compress(input) {
const base = path.basename(input);
if (SKIP.has(base)) return;
const ext = path.extname(input).toLowerCase();
const isGif = ext === '.gif';
const relRoot = path.relative(process.cwd(), input).split(path.sep).join('/');
try {
const stIn = await fs.stat(input);
if (stIn.size < 8 * 1024) {
// tiny: leave
return;
}
const target = input.slice(0, -ext.length) + '.webp';
// Read into memory first, then close handle
const srcBuf = await fs.readFile(input);
const meta = await sharp(srcBuf, { animated: isGif }).metadata();
let pipeline = sharp(srcBuf, { animated: isGif });
if (meta.width && meta.width > MAX_W) {
pipeline = pipeline.resize({ width: MAX_W, withoutEnlargement: true });
}
const q = qualityFor(stIn.size);
pipeline = pipeline.webp({
quality: q,
alphaQuality: 88,
effort: 6,
smartSubsample: true,
...(isGif ? { loop: 0 } : {}),
});
const buf = await pipeline.toBuffer();
if (buf.length >= stIn.size && ext === '.webp') {
skippedCount++;
return;
}
// write via temp file then rename for atomicity
const tmp = target + '.tmp';
await fs.writeFile(tmp, buf);
if (target !== input) {
try { await fs.unlink(input); } catch {}
}
await fs.rename(tmp, target);
convertedCount++;
savedBytes += stIn.size - buf.length;
const inKB = (stIn.size / 1024).toFixed(0);
const outKB = (buf.length / 1024).toFixed(0);
console.log(`OK q=${q} ${relRoot} ${inKB}KB -> ${outKB}KB${meta.width && meta.width > MAX_W ? ' (resized)' : ''}`);
} catch (err) {
console.log(`FAIL ${relRoot}: ${err.message}`);
}
}
await walk(ROOT);
const savedMB = (savedBytes / 1024 / 1024).toFixed(2);
console.log('\n---');
console.log(`compressed: ${convertedCount} skipped: ${skippedCount}`);
console.log(`saved: ${savedMB} MB`);