merge-to-pptx.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import { existsSync, readdirSync, readFileSync } from "fs";
  2. import { join, basename, extname } from "path";
  3. import PptxGenJS from "pptxgenjs";
  4. interface SlideInfo {
  5. filename: string;
  6. path: string;
  7. index: number;
  8. promptPath?: string;
  9. }
  10. function parseArgs(): { dir: string; output?: string } {
  11. const args = process.argv.slice(2);
  12. let dir = "";
  13. let output: string | undefined;
  14. for (let i = 0; i < args.length; i++) {
  15. if (args[i] === "--output" || args[i] === "-o") {
  16. output = args[++i];
  17. } else if (!args[i].startsWith("-")) {
  18. dir = args[i];
  19. }
  20. }
  21. if (!dir) {
  22. console.error("Usage: bun merge-to-pptx.ts <slide-deck-dir> [--output filename.pptx]");
  23. process.exit(1);
  24. }
  25. return { dir, output };
  26. }
  27. function findSlideImages(dir: string): SlideInfo[] {
  28. if (!existsSync(dir)) {
  29. console.error(`Directory not found: ${dir}`);
  30. process.exit(1);
  31. }
  32. const files = readdirSync(dir);
  33. const slidePattern = /^(\d+)-slide-.*\.(png|jpg|jpeg)$/i;
  34. const promptsDir = join(dir, "prompts");
  35. const hasPrompts = existsSync(promptsDir);
  36. const slides: SlideInfo[] = files
  37. .filter((f) => slidePattern.test(f))
  38. .map((f) => {
  39. const match = f.match(slidePattern);
  40. const baseName = f.replace(/\.(png|jpg|jpeg)$/i, "");
  41. const promptPath = hasPrompts ? join(promptsDir, `${baseName}.md`) : undefined;
  42. return {
  43. filename: f,
  44. path: join(dir, f),
  45. index: parseInt(match![1], 10),
  46. promptPath: promptPath && existsSync(promptPath) ? promptPath : undefined,
  47. };
  48. })
  49. .sort((a, b) => a.index - b.index);
  50. if (slides.length === 0) {
  51. console.error(`No slide images found in: ${dir}`);
  52. console.error("Expected format: 01-slide-*.png, 02-slide-*.png, etc.");
  53. process.exit(1);
  54. }
  55. return slides;
  56. }
  57. function findBasePrompt(): string | undefined {
  58. const scriptDir = import.meta.dir;
  59. const basePromptPath = join(scriptDir, "..", "references", "base-prompt.md");
  60. if (existsSync(basePromptPath)) {
  61. return readFileSync(basePromptPath, "utf-8");
  62. }
  63. return undefined;
  64. }
  65. async function createPptx(slides: SlideInfo[], outputPath: string) {
  66. const pptx = new PptxGenJS();
  67. pptx.layout = "LAYOUT_16x9";
  68. pptx.author = "paper-slide-deck";
  69. pptx.subject = "Generated Slide Deck";
  70. const basePrompt = findBasePrompt();
  71. let notesCount = 0;
  72. for (const slide of slides) {
  73. const s = pptx.addSlide();
  74. const imageData = readFileSync(slide.path);
  75. const base64 = imageData.toString("base64");
  76. const ext = extname(slide.filename).toLowerCase().replace(".", "");
  77. const mimeType = ext === "png" ? "image/png" : "image/jpeg";
  78. s.addImage({
  79. data: `data:${mimeType};base64,${base64}`,
  80. x: 0,
  81. y: 0,
  82. w: "100%",
  83. h: "100%",
  84. sizing: { type: "cover", w: "100%", h: "100%" },
  85. });
  86. if (slide.promptPath) {
  87. const slidePrompt = readFileSync(slide.promptPath, "utf-8");
  88. const fullNotes = basePrompt ? `${basePrompt}\n\n---\n\n${slidePrompt}` : slidePrompt;
  89. s.addNotes(fullNotes);
  90. notesCount++;
  91. }
  92. console.log(`Added: ${slide.filename}${slide.promptPath ? " (with notes)" : ""}`);
  93. }
  94. await pptx.writeFile({ fileName: outputPath });
  95. console.log(`\nCreated: ${outputPath}`);
  96. console.log(`Total slides: ${slides.length}`);
  97. if (notesCount > 0) {
  98. console.log(`Slides with notes: ${notesCount}${basePrompt ? " (includes base prompt)" : ""}`);
  99. }
  100. }
  101. async function main() {
  102. const { dir, output } = parseArgs();
  103. const slides = findSlideImages(dir);
  104. const dirName = basename(dir) === "slide-deck" ? basename(join(dir, "..")) : basename(dir);
  105. const outputPath = output || join(dir, `${dirName}.pptx`);
  106. console.log(`Found ${slides.length} slides in: ${dir}\n`);
  107. await createPptx(slides, outputPath);
  108. }
  109. main().catch((err) => {
  110. console.error("Error:", err.message);
  111. process.exit(1);
  112. });