Files
aiturk-hermes-ide/skills/productivity/powerpoint/scripts/pptx_render.py
T

94 lines
3.5 KiB
Python

#!/usr/bin/env python3
"""Render every slide of a .pptx to per-slide PNG images.
Pipeline: LibreOffice (soffice --headless --convert-to pdf) turns the deck
into a PDF, then poppler (pdftoppm, or pdftocairo as an alternate) splits
the PDF into one PNG per slide.
Output is JSON. When both tools are present:
{"rendered": true, "files": ["render/slide-1.png", ...]}
When either tool is missing the script still exits 0 and reports:
{"rendered": false, "missing": ["soffice"], "guidance": "..."}
so callers can degrade gracefully (fall back to pptx_read.py --outline).
"""
import argparse
import glob
import json
import os
import shutil
import subprocess
import sys
import tempfile
def find_tools():
"""Return (soffice, splitter, missing) using shutil.which."""
soffice = shutil.which("soffice")
splitter = shutil.which("pdftoppm") or shutil.which("pdftocairo")
missing = []
if not soffice:
missing.append("soffice")
if not splitter:
missing.append("pdftoppm (or pdftocairo)")
return soffice, splitter, missing
def render(pptx_path, out_dir, prefix, dpi):
soffice, splitter, missing = find_tools()
if missing:
return {
"rendered": False, "missing": missing,
"guidance": "Install LibreOffice (soffice) and poppler-utils "
"(pdftoppm/pdftocairo) to render slides. Without "
"them, verify decks with pptx_read.py --outline "
"instead.",
}
os.makedirs(out_dir, exist_ok=True)
with tempfile.TemporaryDirectory() as tmp:
proc = subprocess.run(
[soffice, "--headless", "--convert-to", "pdf",
"--outdir", tmp, pptx_path],
capture_output=True, text=True, encoding="utf-8",
errors="replace", timeout=300)
pdfs = glob.glob(os.path.join(tmp, "*.pdf"))
if proc.returncode != 0 or not pdfs:
raise SystemExit(f"soffice PDF conversion failed: {proc.stderr}")
pdf = pdfs[0]
out_prefix = os.path.join(out_dir, prefix)
proc = subprocess.run(
[splitter, "-png", "-r", str(dpi), pdf, out_prefix],
capture_output=True, text=True, encoding="utf-8",
errors="replace", timeout=300)
if proc.returncode != 0:
raise SystemExit(f"{os.path.basename(splitter)} failed: "
f"{proc.stderr}")
files = sorted(glob.glob(out_prefix + "*.png"))
return {"rendered": True, "files": files}
def main(argv=None):
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
parser = argparse.ArgumentParser(
description="Render each slide of a .pptx to a PNG via "
"soffice + pdftoppm/pdftocairo.",
epilog=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("pptx", help="path to the .pptx file")
parser.add_argument("--outdir", default="render",
help="directory for PNGs (default: ./render)")
parser.add_argument("--prefix", default="slide",
help="PNG filename prefix (default: slide)")
parser.add_argument("--dpi", type=int, default=100,
help="render resolution (default: 100)")
args = parser.parse_args(argv)
result = render(args.pptx, args.outdir, args.prefix, args.dpi)
print(json.dumps(result, indent=2))
return 0
if __name__ == "__main__":
sys.exit(main())