Files
cs249r_book/slides/scripts/pdf2pptx.py
Vijay Janapa Reddi f24f1683da feat(slides): deploy slides portal to dev, add PPTX builds, fix all overfull warnings
- Wire up slides-preview-dev.yml to deploy via SSH (matching labs/kits pattern)
- Add slides/ URL rewrite in book-preview-dev.yml for landing page links
- Add pdf2pptx.py conversion script (pdftoppm + python-pptx, 300 DPI)
- Update Makefile with pptx targets (per-chapter, per-volume, all)
- Update slides-build-pdfs.yml and slides-publish-live.yml to build PPTX
  and include PDF+PPTX ZIP archives in GitHub releases
- Fix all 44 overfull box warnings across 17 Vol1 Beamer decks
- Update slides portal pages with direct per-deck PDF/PPTX download links,
  PPTX column in tables, and remove placeholder callouts
- Update teaching.qmd with PPTX build instructions
- Cross-link slides from instructors hub, getting-started, and both syllabi
2026-03-18 13:58:22 -04:00

125 lines
4.2 KiB
Python
Executable File

#!/usr/bin/env python3
"""Convert PDF slide decks to PowerPoint (PPTX) using high-resolution images.
Each PDF page is rendered at 300 DPI via pdftoppm (poppler) and placed as a
full-bleed image on a 16:9 PowerPoint slide. The result is visually identical
to the PDF — suitable for presenting in PowerPoint/Keynote with annotations,
presenter view, etc. Slides are image-based (not editable text).
Requirements:
- pdftoppm (poppler-utils): apt install poppler-utils / brew install poppler
- python-pptx: pip install python-pptx
- Pillow: pip install Pillow
Usage:
python3 pdf2pptx.py input.pdf # -> input.pptx
python3 pdf2pptx.py input.pdf -o output.pptx # explicit output
python3 pdf2pptx.py _build/vol1/*.pdf # batch convert
python3 pdf2pptx.py _build/vol1/*.pdf --dpi 200 # lower DPI (smaller files)
"""
import argparse
import glob
import os
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
def check_dependencies():
if not shutil.which("pdftoppm"):
print("ERROR: pdftoppm not found. Install poppler:", file=sys.stderr)
print(" macOS: brew install poppler", file=sys.stderr)
print(" Ubuntu: sudo apt install poppler-utils", file=sys.stderr)
sys.exit(1)
try:
from pptx import Presentation # noqa: F401
except ImportError:
print("ERROR: python-pptx not found. Install:", file=sys.stderr)
print(" pip install python-pptx Pillow", file=sys.stderr)
sys.exit(1)
def convert_pdf_to_pptx(pdf_path: str, pptx_path: str, dpi: int = 300) -> dict:
"""Convert a single PDF to PPTX. Returns stats dict."""
from pptx import Presentation
from pptx.util import Inches, Emu
with tempfile.TemporaryDirectory() as tmp:
subprocess.run(
["pdftoppm", "-png", "-r", str(dpi), pdf_path, os.path.join(tmp, "slide")],
check=True,
capture_output=True,
)
images = sorted(glob.glob(os.path.join(tmp, "slide-*.png")))
if not images:
return {"ok": False, "error": "pdftoppm produced no images"}
prs = Presentation()
prs.slide_width = Inches(13.333)
prs.slide_height = Inches(7.5)
for img_path in images:
slide = prs.slides.add_slide(prs.slide_layouts[6]) # blank
slide.shapes.add_picture(
img_path, Emu(0), Emu(0), prs.slide_width, prs.slide_height
)
os.makedirs(os.path.dirname(pptx_path) or ".", exist_ok=True)
prs.save(pptx_path)
size_mb = os.path.getsize(pptx_path) / (1024 * 1024)
return {"ok": True, "slides": len(images), "size_mb": size_mb}
def main():
parser = argparse.ArgumentParser(
description="Convert PDF slide decks to PowerPoint (image-based)"
)
parser.add_argument("pdfs", nargs="+", help="PDF file(s) to convert")
parser.add_argument("-o", "--output", help="Output path (single file only)")
parser.add_argument("--dpi", type=int, default=300, help="Render DPI (default: 300)")
parser.add_argument("--output-dir", help="Output directory (batch mode)")
args = parser.parse_args()
check_dependencies()
if args.output and len(args.pdfs) > 1:
print("ERROR: -o/--output only works with a single input file", file=sys.stderr)
sys.exit(1)
total = 0
failed = 0
for pdf in args.pdfs:
if not os.path.isfile(pdf):
print(f" SKIP {pdf} (not found)")
continue
if args.output:
pptx = args.output
elif args.output_dir:
pptx = os.path.join(args.output_dir, Path(pdf).stem + ".pptx")
else:
pptx = str(Path(pdf).with_suffix(".pptx"))
print(f" Converting {pdf}...")
result = convert_pdf_to_pptx(pdf, pptx, dpi=args.dpi)
if result["ok"]:
print(f" -> {pptx} ({result['slides']} slides, {result['size_mb']:.1f} MB)")
total += 1
else:
print(f" FAILED: {result['error']}")
failed += 1
print(f"\nDone: {total} converted, {failed} failed")
if failed:
sys.exit(1)
if __name__ == "__main__":
main()