Files
Art-gallery/scripts/regenerate-portrait-thumbs.js
T
Danila KhodjaefandCursor 21e3e41e48 Add one-command dev-to-prod release with clear SUCCESS/FAILED banners.
Introduce devtoprod:release orchestrator, config file, CLI result footers on deploy scripts, auto-thumb regeneration on curator fixes, and updated deploy documentation.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-08 14:54:56 +03:00

103 lines
3.1 KiB
JavaScript

/**
* Generate timeline portrait thumbnails from existing full portraits.
* Writes portraits/thumbs/{Artist}_thumb.jpg and updates portrait_thumb_path.
*/
require('dotenv').config();
const fs = require('fs');
const path = require('path');
const pool = require('../server/db');
const { generateThumbnailFromFull } = require('./image-fetcher');
const { printCliResult } = require('./lib/cli-result');
const IMAGE_DIR = path.resolve(process.env.IMAGE_DIR || './data/images');
const PORTRAIT_THUMB_WIDTH = 256;
function safeArtistPortraitBase(artistName) {
return artistName.replace(/[^a-zA-Z0-9_-]/g, '_');
}
function localPath(rel) {
return path.join(IMAGE_DIR, rel);
}
async function generatePortraitThumb(row) {
if (!row.portrait_path) return { skipped: true, reason: 'no portrait' };
const fullPath = localPath(row.portrait_path);
if (!fs.existsSync(fullPath)) return { skipped: true, reason: 'missing file' };
const safeBase = safeArtistPortraitBase(row.name);
const thumbsDir = path.join(IMAGE_DIR, 'portraits', 'thumbs');
if (!fs.existsSync(thumbsDir)) fs.mkdirSync(thumbsDir, { recursive: true });
const thumbDest = path.join(thumbsDir, `${safeBase}_thumb.jpg`);
await generateThumbnailFromFull(fullPath, thumbDest, PORTRAIT_THUMB_WIDTH);
const portraitThumbPath = path.join('portraits', 'thumbs', `${safeBase}_thumb.jpg`).replace(/\\/g, '/');
await pool.query(`UPDATE artists SET portrait_thumb_path = $1 WHERE id = $2`, [
portraitThumbPath,
row.id,
]);
return { skipped: false, portraitThumbPath };
}
(async () => {
const { rows } = await pool.query(
`SELECT id, name, portrait_path, portrait_thumb_path FROM artists
WHERE portrait_path IS NOT NULL AND portrait_path <> ''
ORDER BY id`
);
let generated = 0;
let skipped = 0;
const errors = [];
for (const row of rows) {
try {
const result = await generatePortraitThumb(row);
if (result.skipped) skipped++;
else generated++;
} catch (err) {
errors.push({ id: row.id, name: row.name, error: err.message });
}
}
console.log(`Portrait thumbs: ${generated} generated, ${skipped} skipped, ${errors.length} errors`);
if (errors.length) {
for (const e of errors.slice(0, 20)) {
console.warn(` #${e.id} ${e.name}: ${e.error}`);
}
}
await pool.end();
if (errors.length) {
printCliResult({
script: 'regenerate-portrait-thumbs',
ok: false,
summary: `Portrait thumbnail regeneration finished with ${errors.length} error(s).`,
details: [
`Generated: ${generated}, skipped: ${skipped}`,
...errors.slice(0, 5).map((e) => ` #${e.id} ${e.name}: ${e.error}`),
],
});
}
printCliResult({
script: 'regenerate-portrait-thumbs',
ok: true,
summary: 'Portrait thumbnail regeneration complete.',
details: [`Generated: ${generated}, skipped: ${skipped}`],
});
})().catch((err) => {
console.error(err);
pool.end().finally(() => {
printCliResult({
script: 'regenerate-portrait-thumbs',
ok: false,
summary: err.message || String(err),
});
});
});