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);
});