Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Generate professional slide deck images from content with 9 visual style presets and configurable slide counts.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
scripts/merge-to-pptx.ts
1import { existsSync, readdirSync, readFileSync } from "fs";2import { join, basename, extname } from "path";3import PptxGenJS from "pptxgenjs";45interface SlideInfo {6filename: string;7path: string;8index: number;9promptPath?: string;10}1112function parseArgs(): { dir: string; output?: string } {13const args = process.argv.slice(2);14let dir = "";15let output: string | undefined;1617for (let i = 0; i < args.length; i++) {18if (args[i] === "--output" || args[i] === "-o") {19output = args[++i];20} else if (!args[i].startsWith("-")) {21dir = args[i];22}23}2425if (!dir) {26console.error("Usage: bun merge-to-pptx.ts <slide-deck-dir> [--output filename.pptx]");27process.exit(1);28}2930return { dir, output };31}3233function findSlideImages(dir: string): SlideInfo[] {34if (!existsSync(dir)) {35console.error(`Directory not found: ${dir}`);36process.exit(1);37}3839const files = readdirSync(dir);40const slidePattern = /^(\d+)-slide-.*\.(png|jpg|jpeg)$/i;41const promptsDir = join(dir, "prompts");42const hasPrompts = existsSync(promptsDir);4344const slides: SlideInfo[] = files45.filter((f) => slidePattern.test(f))46.map((f) => {47const match = f.match(slidePattern);48const baseName = f.replace(/\.(png|jpg|jpeg)$/i, "");49const promptPath = hasPrompts ? join(promptsDir, `${baseName}.md`) : undefined;5051return {52filename: f,53path: join(dir, f),54index: parseInt(match![1], 10),55promptPath: promptPath && existsSync(promptPath) ? promptPath : undefined,56};57})58.sort((a, b) => a.index - b.index);5960if (slides.length === 0) {61console.error(`No slide images found in: ${dir}`);62console.error("Expected format: 01-slide-*.png, 02-slide-*.png, etc.");63process.exit(1);64}6566return slides;67}6869function findBasePrompt(): string | undefined {70const scriptDir = import.meta.dir;71const basePromptPath = join(scriptDir, "..", "references", "base-prompt.md");72if (existsSync(basePromptPath)) {73return readFileSync(basePromptPath, "utf-8");74}75return undefined;76}7778async function createPptx(slides: SlideInfo[], outputPath: string) {79const pptx = new PptxGenJS();8081pptx.layout = "LAYOUT_16x9";82pptx.author = "baoyu-slide-deck";83pptx.subject = "Generated Slide Deck";8485const basePrompt = findBasePrompt();86let notesCount = 0;8788for (const slide of slides) {89const s = pptx.addSlide();90const imageData = readFileSync(slide.path);91const base64 = imageData.toString("base64");92const ext = extname(slide.filename).toLowerCase().replace(".", "");93const mimeType = ext === "png" ? "image/png" : "image/jpeg";9495s.addImage({96data: `data:${mimeType};base64,${base64}`,97x: 0,98y: 0,99w: "100%",100h: "100%",101sizing: { type: "cover", w: "100%", h: "100%" },102});103104if (slide.promptPath) {105const slidePrompt = readFileSync(slide.promptPath, "utf-8");106const fullNotes = basePrompt ? `${basePrompt}\n\n---\n\n${slidePrompt}` : slidePrompt;107s.addNotes(fullNotes);108notesCount++;109}110111console.log(`Added: ${slide.filename}${slide.promptPath ? " (with notes)" : ""}`);112}113114await pptx.writeFile({ fileName: outputPath });115console.log(`\nCreated: ${outputPath}`);116console.log(`Total slides: ${slides.length}`);117if (notesCount > 0) {118console.log(`Slides with notes: ${notesCount}${basePrompt ? " (includes base prompt)" : ""}`);119}120}121122async function main() {123const { dir, output } = parseArgs();124const slides = findSlideImages(dir);125126const dirName = basename(dir) === "slide-deck" ? basename(join(dir, "..")) : basename(dir);127const outputPath = output || join(dir, `${dirName}.pptx`);128129console.log(`Found ${slides.length} slides in: ${dir}\n`);130131await createPptx(slides, outputPath);132}133134main().catch((err) => {135console.error("Error:", err.message);136process.exit(1);137});138