Replace entire repo content with shahikitchen-v1-prodcution.zip (v1.1 production)

This commit is contained in:
root
2026-06-25 13:44:21 +00:00
parent 284dcfa120
commit 6cd5aaa048
850 changed files with 15616 additions and 26143 deletions
+150
View File
@@ -0,0 +1,150 @@
/**
* 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> = {
'tikka-boti': 'chicken-tikka',
};
/** 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();