Improve timeline UX and add catalog restore scripts for paintings and portraits.
Load the home-page catalog once with a lightweight artists API, batch pan/zoom updates per frame, use dynamic year labels, and speed up movement-flow zoom. Add sync-image-paths and fetch-artist-images plus docs for the post-seed pipeline.
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Download artist portrait images from Wikipedia (and fallbacks) into data/images/portraits/.
|
||||
* Run: npm run fetch-artist-images
|
||||
* Flags: --force (re-fetch even when portrait_path is set), --limit=N
|
||||
*/
|
||||
require('dotenv').config();
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const pool = require('../server/db');
|
||||
const { saveArtistPortrait, findLocalPortraitPath } = require('./image-fetcher');
|
||||
|
||||
const FORCE = process.argv.includes('--force');
|
||||
const LIMIT = parseInt(process.argv.find((a) => a.startsWith('--limit='))?.split('=')[1] || '0', 10);
|
||||
const IMAGE_DIR = path.resolve(process.env.IMAGE_DIR || path.join(__dirname, '../data/images'));
|
||||
|
||||
function portraitFileExists(portraitPath) {
|
||||
return !!portraitPath && fs.existsSync(path.join(IMAGE_DIR, portraitPath));
|
||||
}
|
||||
|
||||
function needsPortrait(artist, force) {
|
||||
if (force) return true;
|
||||
const local = findLocalPortraitPath(artist.name, IMAGE_DIR);
|
||||
if (local) return artist.portrait_path !== local;
|
||||
if (artist.portrait_path) return !portraitFileExists(artist.portrait_path);
|
||||
return true;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const { rows: artists } = await pool.query(`
|
||||
SELECT id, name, wikipedia_title, portrait_path
|
||||
FROM artists
|
||||
ORDER BY name
|
||||
`);
|
||||
|
||||
let targets = artists.filter((a) => needsPortrait(a, FORCE));
|
||||
if (LIMIT > 0) targets = targets.slice(0, LIMIT);
|
||||
|
||||
console.log(
|
||||
`Artists total: ${artists.length}, to fetch: ${targets.length}${FORCE ? ' (force)' : ''}${LIMIT > 0 ? ` (limit ${LIMIT})` : ''}`
|
||||
);
|
||||
|
||||
let updated = 0;
|
||||
let linked = 0;
|
||||
let failed = 0;
|
||||
|
||||
for (const artist of targets) {
|
||||
try {
|
||||
const local = findLocalPortraitPath(artist.name, IMAGE_DIR);
|
||||
if (local) {
|
||||
await pool.query('UPDATE artists SET portrait_path = $1 WHERE id = $2', [local, artist.id]);
|
||||
console.log(`↺ ${artist.name} — ${local}`);
|
||||
linked += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
const wikiTitle = artist.wikipedia_title || artist.name;
|
||||
const saved = await saveArtistPortrait(artist.name, wikiTitle, IMAGE_DIR);
|
||||
if (!saved.path) {
|
||||
console.warn(`✗ ${artist.name} — no portrait found`);
|
||||
failed += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
await pool.query('UPDATE artists SET portrait_path = $1 WHERE id = $2', [saved.path, artist.id]);
|
||||
console.log(`✓ ${artist.name} — ${saved.path} (${saved.source || 'Wikipedia'})`);
|
||||
updated += 1;
|
||||
} catch (err) {
|
||||
console.error(`✗ ${artist.name} — ${err.message}`);
|
||||
failed += 1;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\nDone: ${updated} downloaded, ${linked} linked from disk, ${failed} failed`);
|
||||
await pool.end();
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user