powerpoint

Create, read, edit .pptx decks with python-pptx.

  • pptx
  • powerpoint
  • presentations
  • slides
  • office
  • python-pptx

Declared platforms: linux · macos · windows

Install
npx skills add 'https://github.com/NousResearch/hermes-agent/tree/main/skills/productivity/powerpoint'
Download bundle ↓
main · 24fd22bScanned 2026-09-15

Contributors

GitHub-linked commit authors for this SKILL.md at the saved revision. Co-authors and history before file renames are not included.

File history ↗
View on GitHub
← Back to SKILL.md
#!/usr/bin/env python3"""Create a .pptx presentation from a JSON deck spec. Spec format (all positions/sizes in inches, colors as RRGGBB hex):{  "slide_size": "16:9",              // or "4:3" (default "16:9")  "slides": [    {"layout": "title", "title": "My Deck", "subtitle": "Q3 review"},    {"layout": "title_content", "title": "Agenda",     "bullets": ["Top item",                 {"text": "Sub item", "level": 1, "bold": true,                  "size": 18, "color": "CC0000", "font": "Arial",                  "italic": false,                  "link": "https://example.com/agenda"}],     "background": "1F2937",           // solid slide background (hex)     "footer": "Confidential",         // footer placeholder text     "slide_number": true,             // enable slide-number placeholder     "notes": "Speaker notes for this slide"},    {"layout": "blank", "title": "Widgets",     "images":  [{"path": "logo.png", "left": 1, "top": 1, "width": 3}],     "tables":  [{"left": 1, "top": 2, "width": 6, "height": 2,                  "rows": [["H1", "H2"], ["a", "b"]]}],     "shapes":  [{"type": "rounded_rectangle", "left": 8, "top": 1,                  "width": 3, "height": 1, "fill": "4472C4",                  "text": "Callout", "text_color": "FFFFFF"}],     "charts":  [{"type": "bar", "left": 1, "top": 3, "width": 6,                  "height": 3.5, "title": "Sales",                  "categories": ["Q1", "Q2"],                  "series": {"North": [10, 20], "South": [7, 13]}}]}  ]}Layouts: title, title_content, section, two_content, title_only, blankChart types: bar, bar_h, line, pie   Shape types: rectangle,rounded_rectangle, oval, diamond, right_arrow, chevron"""import argparseimport copyimport jsonimport sys from pptx import Presentationfrom pptx.chart.data import CategoryChartDatafrom pptx.dml.color import RGBColorfrom pptx.enum.chart import XL_CHART_TYPEfrom pptx.enum.shapes import MSO_SHAPEfrom pptx.util import Inches, Pt LAYOUTS = {"title": 0, "title_content": 1, "section": 2,           "two_content": 3, "title_only": 5, "blank": 6}CHART_TYPES = {"bar": XL_CHART_TYPE.COLUMN_CLUSTERED,               "bar_h": XL_CHART_TYPE.BAR_CLUSTERED,               "line": XL_CHART_TYPE.LINE_MARKERS,               "pie": XL_CHART_TYPE.PIE}SHAPE_TYPES = {"rectangle": MSO_SHAPE.RECTANGLE,               "rounded_rectangle": MSO_SHAPE.ROUNDED_RECTANGLE,               "oval": MSO_SHAPE.OVAL, "diamond": MSO_SHAPE.DIAMOND,               "right_arrow": MSO_SHAPE.RIGHT_ARROW,               "chevron": MSO_SHAPE.CHEVRON}  def style_run(run, spec):    """Apply font styling from a bullet/text spec dict to a run."""    font = run.font    if spec.get("size"):        font.size = Pt(spec["size"])    if spec.get("bold") is not None:        font.bold = spec["bold"]    if spec.get("italic") is not None:        font.italic = spec["italic"]    if spec.get("font"):        font.name = spec["font"]    if spec.get("color"):        font.color.rgb = RGBColor.from_string(spec["color"])    if spec.get("link"):        run.hyperlink.address = spec["link"]  def add_bullets(text_frame, bullets):    text_frame.clear()    for i, item in enumerate(bullets):        if isinstance(item, str):            item = {"text": item}        para = text_frame.paragraphs[0] if i == 0 else text_frame.add_paragraph()        para.level = int(item.get("level", 0))        run = para.add_run()        run.text = item.get("text", "")        style_run(run, item)  def copy_layout_placeholder(slide, ph_idx):    """Copy a layout placeholder (footer=11, slide number=12) onto the    slide so it actually renders; returns the shape or None if the layout    does not provide it."""    for ph in slide.slide_layout.placeholders:        if ph.placeholder_format.idx == ph_idx:            slide.shapes._spTree.append(copy.deepcopy(ph._element))            for shape in slide.placeholders:                if shape.placeholder_format.idx == ph_idx:                    return shape    return None  def build_slide(prs, spec):    layout_idx = LAYOUTS.get(spec.get("layout", "title_content"), 1)    slide = prs.slides.add_slide(prs.slide_layouts[layout_idx])     if spec.get("background"):        fill = slide.background.fill        fill.solid()        fill.fore_color.rgb = RGBColor.from_string(spec["background"])    if spec.get("slide_number"):        copy_layout_placeholder(slide, 12)    if spec.get("footer"):        shape = copy_layout_placeholder(slide, 11)        if shape is not None:            shape.text_frame.text = spec["footer"]     if spec.get("title") is not None and slide.shapes.title is not None:        slide.shapes.title.text = spec["title"]    if spec.get("subtitle") is not None:        for ph in slide.placeholders:            if ph.placeholder_format.idx == 1:                ph.text = spec["subtitle"]                break    if spec.get("bullets"):        body = next((ph for ph in slide.placeholders                     if ph.placeholder_format.idx != 0), None)        if body is None:            body = slide.shapes.add_textbox(Inches(0.5), Inches(1.5),                                            Inches(9), Inches(5))        add_bullets(body.text_frame, spec["bullets"])     for img in spec.get("images", []):        kwargs = {}        if img.get("width"):            kwargs["width"] = Inches(img["width"])        if img.get("height"):            kwargs["height"] = Inches(img["height"])        slide.shapes.add_picture(img["path"], Inches(img.get("left", 1)),                                 Inches(img.get("top", 1)), **kwargs)     for tbl in spec.get("tables", []):        rows = tbl["rows"]        shape = slide.shapes.add_table(            len(rows), len(rows[0]), Inches(tbl.get("left", 1)),            Inches(tbl.get("top", 2)), Inches(tbl.get("width", 6)),            Inches(tbl.get("height", 2)))        for r, row in enumerate(rows):            for c, val in enumerate(row):                shape.table.cell(r, c).text = str(val)     for shp in spec.get("shapes", []):        shape = slide.shapes.add_shape(            SHAPE_TYPES.get(shp.get("type", "rectangle"), MSO_SHAPE.RECTANGLE),            Inches(shp.get("left", 1)), Inches(shp.get("top", 1)),            Inches(shp.get("width", 2)), Inches(shp.get("height", 1)))        if shp.get("fill"):            shape.fill.solid()            shape.fill.fore_color.rgb = RGBColor.from_string(shp["fill"])        if shp.get("text"):            shape.text_frame.text = shp["text"]            if shp.get("text_color"):                run = shape.text_frame.paragraphs[0].runs[0]                run.font.color.rgb = RGBColor.from_string(shp["text_color"])     for cht in spec.get("charts", []):        data = CategoryChartData()        data.categories = cht["categories"]        for name, values in cht["series"].items():            data.add_series(name, values)        frame = slide.shapes.add_chart(            CHART_TYPES.get(cht.get("type", "bar"),                            XL_CHART_TYPE.COLUMN_CLUSTERED),            Inches(cht.get("left", 1)), Inches(cht.get("top", 2)),            Inches(cht.get("width", 6)), Inches(cht.get("height", 4)), data)        if cht.get("title"):            frame.chart.has_title = True            frame.chart.chart_title.text_frame.text = cht["title"]     if spec.get("notes"):        slide.notes_slide.notes_text_frame.text = spec["notes"]    return slide  def main(argv=None):    if hasattr(sys.stdout, "reconfigure"):        sys.stdout.reconfigure(encoding="utf-8", errors="replace")    parser = argparse.ArgumentParser(        description="Create a .pptx deck from a JSON spec.",        epilog=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)    parser.add_argument("spec", help="path to JSON deck spec")    parser.add_argument("output", help="output .pptx path")    args = parser.parse_args(argv)     with open(args.spec, encoding="utf-8") as fh:        spec = json.load(fh)     prs = Presentation()    if spec.get("slide_size", "16:9") == "16:9":        prs.slide_width, prs.slide_height = Inches(13.333), Inches(7.5)    else:        prs.slide_width, prs.slide_height = Inches(10), Inches(7.5)     for slide_spec in spec.get("slides", []):        build_slide(prs, slide_spec)     prs.save(args.output)    print(json.dumps({"ok": True, "output": args.output,                      "slides": len(prs.slides._sldIdLst)}))    return 0  if __name__ == "__main__":    sys.exit(main()) 
Referenced from SKILL.md