/** * Find paintings whose thumbnail aspect ratio differs from the full image * (likely wrong thumbnail from an external search result). */ const fs = require('fs'); const path = require('path'); const sharp = require('sharp'); const pool = require('../server/db'); const IMAGE_DIR = path.resolve(process.env.IMAGE_DIR || './data/images'); function localPath(rel) { return path.join(IMAGE_DIR, rel); } async function aspectRatio(filePath) { const meta = await sharp(filePath).metadata(); if (!meta.width || !meta.height) return null; return meta.width / meta.height; } (async () => { const rows = await pool.query( `SELECT p.id, p.title, p.image_path, p.thumbnail_path, a.name AS artist_name FROM paintings p JOIN artists a ON a.id = p.artist_id WHERE p.image_path IS NOT NULL AND p.thumbnail_path IS NOT NULL ORDER BY a.name, p.title` ); const mismatches = []; for (const row of rows.rows) { const fullPath = localPath(row.image_path); const thumbPath = localPath(row.thumbnail_path); if (!fs.existsSync(fullPath) || !fs.existsSync(thumbPath)) continue; try { const [fullAr, thumbAr] = await Promise.all([ aspectRatio(fullPath), aspectRatio(thumbPath), ]); if (!fullAr || !thumbAr) continue; const delta = Math.abs(fullAr - thumbAr) / fullAr; if (delta > 0.15) { mismatches.push({ id: row.id, artist: row.artist_name, title: row.title, fullAr: fullAr.toFixed(3), thumbAr: thumbAr.toFixed(3), delta: (delta * 100).toFixed(1) + '%', }); } } catch { /* ignore unreadable files */ } } console.log('Checked:', rows.rows.length); console.log('Aspect-ratio mismatches:', mismatches.length); mismatches.forEach((m) => console.log(`#${m.id} ${m.artist} — ${m.title} (full ${m.fullAr}, thumb ${m.thumbAr}, Δ ${m.delta})`) ); await pool.end(); })().catch((err) => { console.error(err); process.exit(1); });