#!/usr/bin/env node // Assemble rendered slide PNGs into a 16:9 PPTX: one full-bleed image per slide, // native speaker notes, document metadata. // // Usage: // node build.mjs [--notes notes.json] [--title T] [--author A] [--subject S] import { createRequire } from 'node:module'; import { execSync } from 'node:child_process'; import { readdirSync, readFileSync, existsSync } from 'node:fs'; import path from 'node:path'; const require = createRequire(import.meta.url); function req(name) { try { return require(name); } catch {} const g = execSync('npm root -g').toString().trim(); return require(path.join(g, name)); } const args = process.argv.slice(2); const dir = path.resolve(args[0]); const out = path.resolve(args[1]); if (!args[0] || !args[1]) { console.error('usage: node build.mjs [--notes notes.json] [--title T] [--author A] [--subject S]'); process.exit(1); } const opt = (f, d) => { const i = args.indexOf(f); return i >= 0 ? args[i + 1] : d; }; const pptxgen = req('pptxgenjs'); const P = new pptxgen(); P.layout = 'LAYOUT_WIDE'; // 13.333 x 7.5 in P.title = opt('--title', path.basename(out, '.pptx')); P.author = opt('--author', 'ThothII'); P.subject = opt('--subject', ''); const notesPath = opt('--notes', path.join(dir, 'notes.json')); const notes = existsSync(notesPath) ? JSON.parse(readFileSync(notesPath, 'utf8')) : {}; const pngs = readdirSync(dir).filter(f => /^\d+\.png$/.test(f)).sort(); if (!pngs.length) { console.error('no NN.png files in ' + dir); process.exit(1); } for (const f of pngs) { const n = parseInt(f, 10); const s = P.addSlide(); s.background = { color: 'FCFAFA' }; s.addImage({ path: path.join(dir, f), x: 0, y: 0, w: 13.333, h: 7.5 }); if (notes[n]) s.addNotes(notes[n]); } await P.writeFile({ fileName: out }); console.log('built', out, '-', pngs.length, 'slides');