code icon Code

Convert Slides to Markdown

Convert a PDF slide deck to structured markdown using Gemini's multimodal capabilities

Source Code

import fs from "fs";
import path from "path";

const ENDPOINT =
  "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent";

const CONVERSION_PROMPT = `You are converting a PDF slide deck to comprehensive markdown. Your goal is to create a context-friendly document that captures both the text AND the meaning of visual elements.

For each slide, output:

## Slide [number]: [title if present, or brief description]

### Content
[All text content - titles, bullets, paragraphs, captions. Preserve hierarchy.]

### Visual Elements
[For each chart, diagram, image, or visual:]
- Charts: Describe what the data SHOWS, not just the chart type. "Revenue increased 35% from Q2 to Q3" not "bar chart with two bars"
- Diagrams: Explain relationships and flow. "User request flows through API gateway to microservices" not "boxes with arrows"
- Images: Describe content and relevance to the slide topic
- Tables: Reproduce as markdown tables when possible

### Layout Notes
[Only include if layout conveys meaning, e.g., "comparison layout showing before/after", "timeline progression left to right"]

---

Use horizontal rules (---) between slides. Start with a title extracted from the first slide or filename.

Important:
- Preserve ALL text verbatim - don't summarize bullets
- For visual elements, explain MEANING not just appearance
- Skip layout notes if they don't add information
- Output clean markdown, no code fences around the result`;

const [pdfPath, outputDir] = process.argv.slice(2);

if (!pdfPath) {
  console.error("Error: pdfPath is required");
  process.exit(1);
}

if (!outputDir) {
  console.error("Error: outputDir is required");
  process.exit(1);
}

async function main() {
  // Validate PDF exists
  if (!fs.existsSync(pdfPath)) {
    console.error(`Error: PDF file not found: ${pdfPath}`);
    process.exit(1);
  }

  const fileName = path.basename(pdfPath, ".pdf");
  console.log(`Converting slide deck: ${fileName}`);

  // Read PDF as base64
  const pdfBuffer = fs.readFileSync(pdfPath);
  const base64Pdf = pdfBuffer.toString("base64");
  const fileSizeMB = (pdfBuffer.length / (1024 * 1024)).toFixed(2);
  console.log(`PDF size: ${fileSizeMB} MB`);

  // Build request
  const body = {
    contents: [
      {
        parts: [
          { text: CONVERSION_PROMPT },
          {
            inline_data: {
              mime_type: "application/pdf",
              data: base64Pdf,
            },
          },
        ],
      },
    ],
    generationConfig: {
      temperature: 0.2,
      maxOutputTokens: 65536,
    },
  };

  console.log("Sending to Gemini for processing...");

  const response = await fetch(ENDPOINT, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "x-goog-api-key": "PLACEHOLDER_TOKEN",
    },
    body: JSON.stringify(body),
  });

  const responseText = await response.text();

  if (!response.ok) {
    console.error(`API error (${response.status}): ${responseText}`);
    process.exit(1);
  }

  const data = JSON.parse(responseText);

  // Extract markdown from response
  const parts = data?.candidates?.[0]?.content?.parts || [];
  const textPart = parts.find((p) => p.text);

  if (!textPart) {
    console.error("No text response from API");
    console.error(JSON.stringify(data, null, 2));
    process.exit(1);
  }

  const markdown = textPart.text;

  // Count slides (by counting "## Slide" headers)
  const slideCount = (markdown.match(/## Slide \d+/g) || []).length;

  // Save output
  fs.mkdirSync(outputDir, { recursive: true });
  const outputPath = path.join(outputDir, `${fileName}.md`);
  fs.writeFileSync(outputPath, markdown);

  console.log(`✓ Converted ${slideCount} slides`);
  console.log(`✓ Saved to: ${outputPath}`);

  console.log(
    JSON.stringify({
      success: true,
      inputFile: pdfPath,
      outputFile: outputPath,
      slideCount: slideCount,
      outputSizeKB: Math.round(markdown.length / 1024),
    })
  );
}

main().catch((err) => {
  console.error("Failed:", err.message);
  process.exit(1);
});