Files

150 lines
4.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Sync dish photos from ftp-images/ → public/images/dishes/
* - Matches folders by menu item id (e.g. ftp-images/bilder-bp/chicken-biryani/bild/)
* - Center-crops to 4:3, resizes to 1200×900, exports unified JPEG
*/
import { execSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import { menuCategories } from '../infrastructure/menu/static-menu-data';
import { videoBaseName } from '../application/media/asset-resolver';
const ROOT = path.resolve(__dirname, '..');
const FTP_ROOT = path.join(ROOT, 'ftp-images/bilder-bp');
const OUT_DIR = path.join(ROOT, 'public/images/dishes');
const TARGET_W = 1200;
const TARGET_H = 900;
const JPEG_QUALITY = 88;
/** Menu ids that share another dish's ftp photo (main bilder-bp folder) */
const FTP_ALIASES: Record<string, string> = {
};
/** Menu id → folder name under ftp-images/bilder-bp/sweets/ */
const SWEETS_FOLDERS: Record<string, string> = {
'namakpare': 'namak-paray',
'shakar-paray': 'Shakar paray',
'chocolate-barfi': 'chochlate-barfi',
'pistachio-barfi': 'pistacho-barfi',
};
function pickLargestImage(dir: string): string | null {
if (!fs.existsSync(dir)) return null;
const files = fs
.readdirSync(dir, { withFileTypes: true })
.flatMap((entry) => {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) return [];
if (!/\.(jpe?g|png|webp)$/i.test(entry.name)) return [];
return [{ full, size: fs.statSync(full).size }];
})
.sort((a, b) => b.size - a.size);
return files[0]?.full ?? null;
}
function findSourceImage(dishId: string): string | null {
const mainFolder = FTP_ALIASES[dishId] ?? dishId;
const sweetsFolder = SWEETS_FOLDERS[dishId] ?? dishId;
const candidates = [
path.join(FTP_ROOT, mainFolder, 'bild'),
path.join(FTP_ROOT, 'sweets', sweetsFolder, 'bild'),
path.join(FTP_ROOT, 'sweets', sweetsFolder),
];
for (const dir of candidates) {
const found = pickLargestImage(dir);
if (found) return found;
}
return null;
}
function run(cmd: string) {
execSync(cmd, { stdio: 'pipe' });
}
function processToJpeg(src: string, dest: string) {
const tmp = path.join(OUT_DIR, `.tmp-${path.basename(dest)}`);
fs.mkdirSync(OUT_DIR, { recursive: true });
// Normalize to JPEG working copy
run(`sips -s format jpeg "${src}" --out "${tmp}"`);
const width = Number(
execSync(`sips -g pixelWidth "${tmp}"`).toString().match(/pixelWidth: (\d+)/)?.[1]
);
const height = Number(
execSync(`sips -g pixelHeight "${tmp}"`).toString().match(/pixelHeight: (\d+)/)?.[1]
);
if (!width || !height) throw new Error(`Could not read dimensions for ${src}`);
const targetAspect = TARGET_W / TARGET_H;
const sourceAspect = width / height;
let cropW = width;
let cropH = height;
let offsetX = 0;
let offsetY = 0;
if (sourceAspect > targetAspect) {
cropW = Math.round(height * targetAspect);
offsetX = Math.round((width - cropW) / 2);
} else if (sourceAspect < targetAspect) {
cropH = Math.round(width / targetAspect);
offsetY = Math.round((height - cropH) / 2);
}
run(
`sips --cropToHeightWidth ${cropH} ${cropW} --cropOffset ${offsetY} ${offsetX} "${tmp}"`
);
run(
`sips -z ${TARGET_H} ${TARGET_W} -s format jpeg -s formatOptions ${JPEG_QUALITY} "${tmp}" --out "${dest}"`
);
if (fs.existsSync(tmp)) fs.unlinkSync(tmp);
}
function main() {
const written = new Set<string>();
let synced = 0;
let skipped = 0;
for (const category of menuCategories) {
for (const item of category.items) {
const src = findSourceImage(item.id);
if (!src) {
skipped++;
console.log(` skip (no ftp image): ${item.id}`);
continue;
}
const outputs = new Set<string>();
if (item.image) outputs.add(path.join(OUT_DIR, item.image));
if (item.video) {
const base = videoBaseName(item.video);
outputs.add(path.join(OUT_DIR, `${base}-poster.jpg`));
outputs.add(path.join(OUT_DIR, `${base}-optimized-poster.jpg`));
}
for (const out of outputs) {
if (written.has(out)) continue;
processToJpeg(src, out);
written.add(out);
console.log(`${path.basename(out)}${item.id}`);
}
synced++;
}
}
console.log(`\nDone: ${synced} dishes synced, ${skipped} without ftp source, ${written.size} files written.`);
}
main();