tools/add_lego.js: set cover from largest image and omit it from body
This commit is contained in:
@@ -132,6 +132,58 @@ function escapeMarkdownAlt(text) {
|
|||||||
.replace(/\]/g, '\\]');
|
.replace(/\]/g, '\\]');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lightweight dimensions readers for PNG/JPEG/GIF. Fallback to file size.
|
||||||
|
async function getImageDimensions(filePath) {
|
||||||
|
const fd = await fs.open(filePath, 'r');
|
||||||
|
try {
|
||||||
|
const stat = await fd.stat();
|
||||||
|
const size = stat.size || 0;
|
||||||
|
const hdr = Buffer.alloc(64);
|
||||||
|
const { bytesRead } = await fd.read(hdr, 0, hdr.length, 0);
|
||||||
|
const b = hdr.slice(0, bytesRead);
|
||||||
|
// PNG
|
||||||
|
if (b.length >= 24 && b[0] === 0x89 && b[1] === 0x50 && b[2] === 0x4E && b[3] === 0x47) {
|
||||||
|
const width = b.readUInt32BE(16);
|
||||||
|
const height = b.readUInt32BE(20);
|
||||||
|
return { width, height, area: width * height, size };
|
||||||
|
}
|
||||||
|
// GIF
|
||||||
|
if (b.length >= 10 && b[0] === 0x47 && b[1] === 0x49 && b[2] === 0x46) {
|
||||||
|
const width = b.readUInt16LE(6);
|
||||||
|
const height = b.readUInt16LE(8);
|
||||||
|
return { width, height, area: width * height, size };
|
||||||
|
}
|
||||||
|
// JPEG: scan segments for SOF markers
|
||||||
|
if (b.length >= 4 && b[0] === 0xFF && b[1] === 0xD8) {
|
||||||
|
let pos = 2;
|
||||||
|
while (pos + 3 < size) {
|
||||||
|
const markerBuf = Buffer.alloc(4);
|
||||||
|
await fd.read(markerBuf, 0, 4, pos);
|
||||||
|
if (markerBuf[0] !== 0xFF) { pos += 1; continue; }
|
||||||
|
const marker = markerBuf[1];
|
||||||
|
const hasLen = marker !== 0xD8 && marker !== 0xD9;
|
||||||
|
let segLen = 0;
|
||||||
|
if (hasLen) segLen = markerBuf.readUInt16BE(2);
|
||||||
|
// SOF0..SOF15 except DHT(0xC4), JPG(0xC8), DAC(0xCC)
|
||||||
|
if (marker >= 0xC0 && marker <= 0xCF && ![0xC4, 0xC8, 0xCC].includes(marker)) {
|
||||||
|
const segHdr = Buffer.alloc(7);
|
||||||
|
await fd.read(segHdr, 0, segHdr.length, pos + 4);
|
||||||
|
const height = segHdr.readUInt16BE(1);
|
||||||
|
const width = segHdr.readUInt16BE(3);
|
||||||
|
return { width, height, area: width * height, size };
|
||||||
|
}
|
||||||
|
if (!hasLen || segLen < 2) { pos += 2; continue; }
|
||||||
|
pos += 2 + segLen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { width: 0, height: 0, area: 0, size };
|
||||||
|
} catch (_) {
|
||||||
|
return { width: 0, height: 0, area: 0, size: 0 };
|
||||||
|
} finally {
|
||||||
|
await fd.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function readYamlTitle(yamlPath) {
|
async function readYamlTitle(yamlPath) {
|
||||||
try {
|
try {
|
||||||
const content = await fs.readFile(yamlPath, 'utf8');
|
const content = await fs.readFile(yamlPath, 'utf8');
|
||||||
@@ -327,6 +379,23 @@ async function main() {
|
|||||||
imageFiles = Array.from(downloadedImages);
|
imageFiles = Array.from(downloadedImages);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Choose cover: prefer downloaded this run; fallback to all images
|
||||||
|
let coverFile = null;
|
||||||
|
const coverCandidates = (downloadedImages.size > 0)
|
||||||
|
? Array.from(downloadedImages).filter((b) => imageFiles.includes(b))
|
||||||
|
: imageFiles.slice();
|
||||||
|
if (coverCandidates.length > 0) {
|
||||||
|
let best = null;
|
||||||
|
let bestScore = -1;
|
||||||
|
for (const base of coverCandidates) {
|
||||||
|
const full = path.join(imagesDir, base);
|
||||||
|
const dim = await getImageDimensions(full);
|
||||||
|
const score = (dim.area && dim.area > 0) ? dim.area : (dim.size || 0);
|
||||||
|
if (score > bestScore) { bestScore = score; best = base; }
|
||||||
|
}
|
||||||
|
coverFile = best;
|
||||||
|
}
|
||||||
|
|
||||||
let body = '';
|
let body = '';
|
||||||
if (imageFiles.length > 0) {
|
if (imageFiles.length > 0) {
|
||||||
const ordered = [
|
const ordered = [
|
||||||
@@ -335,6 +404,7 @@ async function main() {
|
|||||||
];
|
];
|
||||||
const lines = [];
|
const lines = [];
|
||||||
for (const base of ordered) {
|
for (const base of ordered) {
|
||||||
|
if (coverFile && base === coverFile) continue;
|
||||||
const nameNoExt = base.replace(/\.[^.]+$/, '');
|
const nameNoExt = base.replace(/\.[^.]+$/, '');
|
||||||
const yamlPath = path.join(dataImagesDir, `${nameNoExt}.yaml`);
|
const yamlPath = path.join(dataImagesDir, `${nameNoExt}.yaml`);
|
||||||
const altRaw = (await readYamlTitle(yamlPath)) || `${pageTitle}`;
|
const altRaw = (await readYamlTitle(yamlPath)) || `${pageTitle}`;
|
||||||
@@ -345,13 +415,16 @@ async function main() {
|
|||||||
body = lines.join('\n') + '\n';
|
body = lines.join('\n') + '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const coverLine = coverFile ? `cover: images/${coverFile}\n` : '';
|
||||||
const md = `---\n` +
|
const md = `---\n` +
|
||||||
`title: "${pageTitle.replace(/"/g, '\\"')}"\n` +
|
`title: "${pageTitle.replace(/"/g, '\\"')}"\n` +
|
||||||
`date: ${date}\n` +
|
`date: ${date}\n` +
|
||||||
|
coverLine +
|
||||||
`---\n\n` +
|
`---\n\n` +
|
||||||
body;
|
body;
|
||||||
await fs.writeFile(indexPath, md, 'utf8');
|
await fs.writeFile(indexPath, md, 'utf8');
|
||||||
console.log(`[create] ${path.relative(ROOT, indexPath)} with ${imageFiles.length} image reference(s)`);
|
const coverInfo = coverFile ? `, cover=images/${coverFile}` : '';
|
||||||
|
console.log(`[create] ${path.relative(ROOT, indexPath)} with ${imageFiles.length} image reference(s)${coverInfo}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Report downloaded images that are not referenced in an existing index.md
|
// Report downloaded images that are not referenced in an existing index.md
|
||||||
|
|||||||
Reference in New Issue
Block a user