scripts/pptx_read.py
scripts/pptx_read.pyBrowse 8 files
1,048 tokens
4,761 bytes
Token encoding: o200k_base
Snapshot 24fd22b
← Back to SKILL.md
1#!/usr/bin/env python32"""Read a .pptx file: JSON outline, notes, or export embedded images.3 4Modes:5 --outline JSON with per-slide layout, texts, tables, notes,6 chart data, and image inventory (default mode).7 --notes JSON list of speaker notes per slide.8 --images DIR Export every embedded picture to DIR as files.9"""10import argparse11import json12import os13import sys14 15from pptx import Presentation16from pptx.enum.shapes import MSO_SHAPE_TYPE17from pptx.util import Emu18 19 20def iter_shapes(shapes):21 """Yield shapes, descending into groups."""22 for shape in shapes:23 if shape.shape_type == MSO_SHAPE_TYPE.GROUP:24 yield from iter_shapes(shape.shapes)25 else:26 yield shape27 28 29def chart_info(chart):30 info = {"type": str(chart.chart_type),31 "categories": [str(c) for c in chart.plots[0].categories],32 "series": []}33 for plot in chart.plots:34 for series in plot.series:35 try:36 name = series.name37 except (AttributeError, KeyError):38 name = None39 info["series"].append({"name": name,40 "values": list(series.values)})41 return info42 43 44def slide_record(index, slide):45 rec = {"index": index, "layout": slide.slide_layout.name,46 "texts": [], "tables": [], "images": [], "charts": [],47 "notes": None}48 for shape in iter_shapes(slide.shapes):49 if shape.has_text_frame and shape.text_frame.text.strip():50 rec["texts"].append(shape.text_frame.text)51 if shape.has_table:52 rec["tables"].append(53 [[cell.text for cell in row.cells]54 for row in shape.table.rows])55 if shape.shape_type == MSO_SHAPE_TYPE.PICTURE:56 try:57 img = shape.image58 rec["images"].append({"filename": img.filename,59 "ext": img.ext,60 "size_bytes": len(img.blob)})61 except (KeyError, ValueError):62 rec["images"].append({"filename": None, "ext": None,63 "size_bytes": None,64 "note": "linked or unreadable"})65 if shape.has_chart:66 rec["charts"].append(chart_info(shape.chart))67 if slide.has_notes_slide:68 rec["notes"] = slide.notes_slide.notes_text_frame.text69 return rec70 71 72def export_images(prs, out_dir):73 os.makedirs(out_dir, exist_ok=True)74 written = []75 for i, slide in enumerate(prs.slides):76 for j, shape in enumerate(iter_shapes(slide.shapes)):77 if shape.shape_type != MSO_SHAPE_TYPE.PICTURE:78 continue79 try:80 img = shape.image81 except (KeyError, ValueError):82 continue83 path = os.path.join(out_dir, f"slide{i}_img{j}.{img.ext}")84 with open(path, "wb") as fh:85 fh.write(img.blob)86 written.append(path)87 return written88 89 90def main(argv=None):91 if hasattr(sys.stdout, "reconfigure"):92 sys.stdout.reconfigure(encoding="utf-8", errors="replace")93 parser = argparse.ArgumentParser(94 description="Read a .pptx: outline/notes as JSON, export images.")95 parser.add_argument("pptx", help="path to the .pptx file")96 parser.add_argument("--outline", action="store_true",97 help="print full JSON outline (default)")98 parser.add_argument("--notes", action="store_true",99 help="print speaker notes only")100 parser.add_argument("--images", metavar="DIR",101 help="export embedded images into DIR")102 args = parser.parse_args(argv)103 104 prs = Presentation(args.pptx)105 106 if args.images:107 written = export_images(prs, args.images)108 print(json.dumps({"ok": True, "exported": written}, indent=2))109 return 0110 if args.notes:111 notes = [slide.notes_slide.notes_text_frame.text112 if slide.has_notes_slide else None113 for slide in prs.slides]114 print(json.dumps({"ok": True, "notes": notes},115 indent=2, ensure_ascii=True))116 return 0117 118 outline = {119 "ok": True,120 "slide_size_inches": [round(Emu(prs.slide_width).inches, 3),121 round(Emu(prs.slide_height).inches, 3)],122 "slide_count": len(prs.slides._sldIdLst),123 "layouts_available": [lay.name for lay in prs.slide_layouts],124 "slides": [slide_record(i, s) for i, s in enumerate(prs.slides)],125 }126 print(json.dumps(outline, indent=2, ensure_ascii=True))127 return 0128 129 130if __name__ == "__main__":131 sys.exit(main())132