Add catalog bootstrap endpoint, portrait thumbnail pipeline, lazy queued timeline images, gzip compression, and 3D texture throttling with code-split VirtualGallery.
77 lines
2.3 KiB
JavaScript
77 lines
2.3 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 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();
|
|
})().catch((err) => {
|
|
console.error(err);
|
|
pool.end().finally(() => process.exit(1));
|
|
});
|