#!/usr/bin/env node /** * Sync sweets videos: ftp-images/bilder-bp/sweets/{dish}/bild/*.mp4 → public/videos/{dishId}.mp4 * Then compress to uniform 640×480, ≤ 200 KB for modal playback. * * Usage: * node scripts/sync-sweets-videos.mjs * node scripts/sync-sweets-videos.mjs --dry-run * node scripts/sync-sweets-videos.mjs --skip-optimize */ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import { spawnSync } from 'child_process'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const ROOT = path.join(__dirname, '..'); const SOURCE_DIR = path.join(ROOT, 'ftp-images/bilder-bp/sweets'); const OUTPUT_DIR = path.join(ROOT, 'public/videos'); const REPORT_PATH = path.join(__dirname, 'sweets-videos-sync-report.json'); const DRY_RUN = process.argv.includes('--dry-run'); const SKIP_OPTIMIZE = process.argv.includes('--skip-optimize'); const FOLDER_TO_DISH = { 'badam-barfi': 'badam-barfi', 'besan-patisa': 'baisan-patisa', 'cham-cham': 'cham-cham', 'chochlate-barfi': 'chocolate-barfi', 'coconut-barfi': 'coconut-barfi', 'gajar-barfi': 'gajar-barfi', 'gulab-jaman': 'gulab-jaman', 'habshi-halwa': 'habshi-halwa', 'jalebi': 'jalebi', 'laddu': 'laddu', 'milk-cake-akhrot': 'milk-cake-akhrot', 'milk-cake-plain': 'milk-cake-plain', 'namak-paray': 'namakpare', 'paira': 'paira', 'patisa': 'patisa', 'pink-barfi': 'pink-barfi', 'pistacho-barfi': 'pistachio-barfi', 'qalakand': 'qalakand', 'ras-gulay': 'ras-gulay', 'ras-malai': 'rasmalai', 'shakar-paray': 'shakar-paray', 'cream-gulab-jaman': 'cream-gulab-jaman', 'lambay-gulab-jaman': 'lambay-gulab-jaman', 'milk-cake-khajoor': 'milk-cake-khajoor', 'plain-barfi': 'plain-barfi', 'baisan-barfi': 'baisan-barfi', 'besan-barfi': 'baisan-barfi', 'basen-barfi': 'baisan-barfi', }; const SKIP_FOLDERS = new Set(['samosa-aloo', 'samosa-keema', 'samosa-chaat']); function normalize(value) { return String(value || '') .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-+|-+$/g, ''); } function pickVideo(files) { const mp4s = files.filter((f) => /\.mp4$/i.test(f)); if (!mp4s.length) return null; return mp4s.sort((a, b) => a.localeCompare(b))[0]; } function runOptimize(fileName) { const r = spawnSync(process.execPath, [path.join(__dirname, 'optimize-sweets-videos.mjs'), fileName], { stdio: 'inherit', cwd: ROOT, }); return r.status === 0; } function main() { console.log('Shahi Kitchen — sweets video sync\n'); if (!fs.existsSync(SOURCE_DIR)) throw new Error(`Missing: ${SOURCE_DIR}`); if (!DRY_RUN) fs.mkdirSync(OUTPUT_DIR, { recursive: true }); const synced = []; for (const folder of fs.readdirSync(SOURCE_DIR, { withFileTypes: true })) { if (!folder.isDirectory()) continue; const folderNorm = normalize(folder.name); if (SKIP_FOLDERS.has(folderNorm)) continue; const dishId = FOLDER_TO_DISH[folderNorm]; if (!dishId) { console.log(`⚠ Unmapped folder: ${folder.name}`); continue; } const mediaDir = ['bild', 'bilder'] .map((sub) => path.join(SOURCE_DIR, folder.name, sub)) .find((d) => fs.existsSync(d)); if (!mediaDir) continue; const videoFile = pickVideo(fs.readdirSync(mediaDir)); if (!videoFile) continue; const src = path.join(mediaDir, videoFile); const outputFile = `${dishId}.mp4`; const dest = path.join(OUTPUT_DIR, outputFile); if (!DRY_RUN) { fs.copyFileSync(src, dest); if (!SKIP_OPTIMIZE) { console.log(` optimizing ${outputFile}…`); runOptimize(outputFile); } } const kb = (DRY_RUN ? fs.statSync(src).size : fs.statSync(dest).size) / 1024; synced.push({ dishId, folder: folder.name, sourceFile: videoFile, outputFile, kb: Number(kb.toFixed(1)) }); const sub = path.basename(mediaDir); console.log(`✓ ${dishId} ← ${folder.name}/${sub}/${videoFile} (${kb.toFixed(0)} KB)`); } const report = { generatedAt: new Date().toISOString(), dryRun: DRY_RUN, synced }; if (!DRY_RUN) fs.writeFileSync(REPORT_PATH, JSON.stringify(report, null, 2)); console.log(`\nSynced: ${synced.length} videos`); if (!DRY_RUN) console.log(`Report: ${REPORT_PATH}`); } main();