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

132 lines
4.6 KiB
Python

#!/usr/bin/env python3
"""Read a .pptx file: JSON outline, notes, or export embedded images.
Modes:
--outline JSON with per-slide layout, texts, tables, notes,
chart data, and image inventory (default mode).
--notes JSON list of speaker notes per slide.
--images DIR Export every embedded picture to DIR as files.
"""
import argparse
import json
import os
import sys
from pptx import Presentation
from pptx.enum.shapes import MSO_SHAPE_TYPE
from pptx.util import Emu
def iter_shapes(shapes):
"""Yield shapes, descending into groups."""
for shape in shapes:
if shape.shape_type == MSO_SHAPE_TYPE.GROUP:
yield from iter_shapes(shape.shapes)
else:
yield shape
def chart_info(chart):
info = {"type": str(chart.chart_type),
"categories": [str(c) for c in chart.plots[0].categories],
"series": []}
for plot in chart.plots:
for series in plot.series:
try:
name = series.name
except (AttributeError, KeyError):
name = None
info["series"].append({"name": name,
"values": list(series.values)})
return info
def slide_record(index, slide):
rec = {"index": index, "layout": slide.slide_layout.name,
"texts": [], "tables": [], "images": [], "charts": [],
"notes": None}
for shape in iter_shapes(slide.shapes):
if shape.has_text_frame and shape.text_frame.text.strip():
rec["texts"].append(shape.text_frame.text)
if shape.has_table:
rec["tables"].append(
[[cell.text for cell in row.cells]
for row in shape.table.rows])
if shape.shape_type == MSO_SHAPE_TYPE.PICTURE:
try:
img = shape.image
rec["images"].append({"filename": img.filename,
"ext": img.ext,
"size_bytes": len(img.blob)})
except (KeyError, ValueError):
rec["images"].append({"filename": None, "ext": None,
"size_bytes": None,
"note": "linked or unreadable"})
if shape.has_chart:
rec["charts"].append(chart_info(shape.chart))
if slide.has_notes_slide:
rec["notes"] = slide.notes_slide.notes_text_frame.text
return rec
def export_images(prs, out_dir):
os.makedirs(out_dir, exist_ok=True)
written = []
for i, slide in enumerate(prs.slides):
for j, shape in enumerate(iter_shapes(slide.shapes)):
if shape.shape_type != MSO_SHAPE_TYPE.PICTURE:
continue
try:
img = shape.image
except (KeyError, ValueError):
continue
path = os.path.join(out_dir, f"slide{i}_img{j}.{img.ext}")
with open(path, "wb") as fh:
fh.write(img.blob)
written.append(path)
return written
def main(argv=None):
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
parser = argparse.ArgumentParser(
description="Read a .pptx: outline/notes as JSON, export images.")
parser.add_argument("pptx", help="path to the .pptx file")
parser.add_argument("--outline", action="store_true",
help="print full JSON outline (default)")
parser.add_argument("--notes", action="store_true",
help="print speaker notes only")
parser.add_argument("--images", metavar="DIR",
help="export embedded images into DIR")
args = parser.parse_args(argv)
prs = Presentation(args.pptx)
if args.images:
written = export_images(prs, args.images)
print(json.dumps({"ok": True, "exported": written}, indent=2))
return 0
if args.notes:
notes = [slide.notes_slide.notes_text_frame.text
if slide.has_notes_slide else None
for slide in prs.slides]
print(json.dumps({"ok": True, "notes": notes},
indent=2, ensure_ascii=True))
return 0
outline = {
"ok": True,
"slide_size_inches": [round(Emu(prs.slide_width).inches, 3),
round(Emu(prs.slide_height).inches, 3)],
"slide_count": len(prs.slides._sldIdLst),
"layouts_available": [lay.name for lay in prs.slide_layouts],
"slides": [slide_record(i, s) for i, s in enumerate(prs.slides)],
}
print(json.dumps(outline, indent=2, ensure_ascii=True))
return 0
if __name__ == "__main__":
sys.exit(main())