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:
@@ -1807,11 +1807,93 @@ async function saveImageForItem(wikiTitle, subdir, filename, imageDir, options =
|
||||
}
|
||||
}
|
||||
|
||||
const ARTIST_PORTRAIT_WIKI_OVERRIDES = {
|
||||
Zeuxis: 'Zeuxis (painter)',
|
||||
'Ivan Klyun': 'Ivan Kliun',
|
||||
'Jean-Antoine Watteau': 'Antoine Watteau',
|
||||
};
|
||||
|
||||
function artistPortraitWikiCandidates(artistName, wikiTitle) {
|
||||
const seen = new Set();
|
||||
const out = [];
|
||||
for (const title of [
|
||||
ARTIST_PORTRAIT_WIKI_OVERRIDES[artistName],
|
||||
ARTIST_PORTRAIT_WIKI_OVERRIDES[wikiTitle],
|
||||
wikiTitle,
|
||||
artistName,
|
||||
`${artistName} (painter)`,
|
||||
`${wikiTitle} (painter)`,
|
||||
]) {
|
||||
if (title && !seen.has(title)) {
|
||||
seen.add(title);
|
||||
out.push(title);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
async function resolveArtistPortrait(artistName, wikiTitle) {
|
||||
for (const title of artistPortraitWikiCandidates(artistName, wikiTitle)) {
|
||||
const images = await getWikipediaImages(title);
|
||||
if (images?.fullUrl) {
|
||||
return { ...images, wikipedia_title: title };
|
||||
}
|
||||
}
|
||||
|
||||
const search = await searchArtistPortraitFirst(artistName);
|
||||
if (search?.imageUrl) {
|
||||
return {
|
||||
fullUrl: search.imageUrl,
|
||||
source: search.sourceLabel || search.source || 'web search',
|
||||
wikipedia_title: wikiTitle,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function findLocalPortraitPath(artistName, imageDir) {
|
||||
const safeBase = artistName.replace(/[^a-zA-Z0-9_-]/g, '_');
|
||||
for (const ext of ['.jpg', '.jpeg', '.png', '.webp', '.JPG']) {
|
||||
const rel = `portraits/${safeBase}${ext}`;
|
||||
if (fs.existsSync(path.join(imageDir, rel))) return rel.replace(/\\/g, '/');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function saveArtistPortrait(artistName, wikiTitle, imageDir) {
|
||||
const local = findLocalPortraitPath(artistName, imageDir);
|
||||
if (local) return { path: local, source: 'local disk' };
|
||||
|
||||
const resolved = await resolveArtistPortrait(artistName, wikiTitle);
|
||||
if (!resolved?.fullUrl) return { path: null, source: null };
|
||||
|
||||
const portraitsDir = path.join(imageDir, 'portraits');
|
||||
if (!fs.existsSync(portraitsDir)) fs.mkdirSync(portraitsDir, { recursive: true });
|
||||
|
||||
const safeBase = artistName.replace(/[^a-zA-Z0-9_-]/g, '_');
|
||||
const ext = pickExt(resolved.fullUrl);
|
||||
const destPath = path.join(portraitsDir, safeBase + ext);
|
||||
|
||||
try {
|
||||
await downloadImageToFile(resolved.fullUrl, destPath);
|
||||
return {
|
||||
path: path.join('portraits', safeBase + ext).replace(/\\/g, '/'),
|
||||
source: resolved.source,
|
||||
};
|
||||
} catch (err) {
|
||||
console.warn(` Portrait download failed: ${err.message}`);
|
||||
return { path: null, source: resolved.source };
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
resolveImageUrl,
|
||||
resolvePaintingImages,
|
||||
saveImageForItem,
|
||||
savePaintingImages,
|
||||
saveArtistPortrait,
|
||||
findLocalPortraitPath,
|
||||
resolveArtistPortrait,
|
||||
downloadImageToFile,
|
||||
downloadImageForFix,
|
||||
generateThumbnailFromFull,
|
||||
|
||||
Reference in New Issue
Block a user