首页
/ DeepTutor PPTX 技能实战:借助 python-pptx 读取、创建与编辑 PowerPoint 演示文稿

DeepTutor PPTX 技能实战:借助 python-pptx 读取、创建与编辑 PowerPoint 演示文稿

2026-09-08 22:10:32作者:庞眉杨Will

这份指南完整拆解 DeepTutor 内置的 pptx 技能(deeptutor/skills/builtin/pptx/SKILL.md),它让 Agent 能够在沙箱 exec shell 中直接处理用户上传的 .pptx/.ppt 演示文稿:从读取文字与备注、按大纲批量建页,到保留格式的占位符替换、原位换图、插入表格与图表,乃至用 LibreOffice 导出 PDF/图片和最后的原始 OOXML 兜底手段。读完你会掌握一套在 DeepTutor 体系内对 PowerPoint 文件进行“读写改验”全链路操作的实战方法,并理解其背后的 python-pptx 对象模型与 OOXML 包结构。

技能定位:何时触发、在哪里执行

pptx 技能是 DeepTutor 随产品内置(builtin)的一组 SKILL 包之一,与 docxpdfxlsxskill-creator 并列存放于 deeptutor/skills/builtin/ 目录。它的 YAML frontmatter 定义了触发条件与运行前置:

name: pptx
description: Read, create, or edit PowerPoint .pptx decks  build slides from an outline,
  extract slide text/speaker notes, edit shapes/tables/charts, replace images, or
  export to PDF/images. Use whenever a .pptx (or .ppt) file is an input or output,
  or the user mentions a deck, slides, or a presentation.
tags:
- tool
- office
requires:
  sandbox: shell

几个关键点需要结合仓库机制来理解:

  • 何时触发:description 约定“只要用户提到 deck、slides、presentation,或输入/输出涉及 .pptx/.ppt,就应使用本技能”。这与 DeepTutor 的技能加载机制一致——按照 skill-creator 的说明,系统提示词只携带每个技能的 name + description,模型在任务匹配时才用 read_skill 读取完整正文。
  • 运行环境requires.sandbox: shell 表示它依赖 shell 执行沙箱,即 DeepTutor 的 exec_tool。所有工作在沙箱 exec shell 的当前工作区目录中完成(上传的文件就落在那里);Agent 通常写一段 Python heredoc 或临时 .py 脚本并执行。
  • 交付物exec 结束后,工具结果里会出现 Generated artifacts(下载卡片),Agent 需在最终回答中引用其 URL,用户即可下载生成的演示文稿。这一机制由 deeptutor/services/sandbox/artifacts.py 落地:沙箱产物会以 "Generated artifacts (now saved — shown to the user as download cards)" 的形式返回给模型,前端随后把回答中逐字出现的产物文件名自动转成可点击下载链接。
  • 依赖预装:技能正文声称 python-pptx “preinstalled”,仓库证据确实如此——requirements/cli.txtrequirements/dev.txt 均固定了 python-pptx>=1.0.0Dockerfile.runner 也把 python-docxpython-pptxopenpyxl 等办公库一并装入运行时镜像。

心智模型:.pptx 的对象层次与计量单位

在写任何代码前,先建立正确的模型,能避免 90% 的“对着 API 硬试”:

  • 一个演示文稿(presentation)由若干幻灯片(slides)组成;每张幻灯片基于某个版式(layout)构建;版式归属于幻灯片母版(slide master)。版式通过 idx 和类型定义占位符(placeholders)(如标题 title、正文 body、图片 picture)。
  • 一张幻灯片上承载各种形状(shapes):占位符、文本框、图片、表格、图表。
  • 带文字的 shape 向下暴露 .text_frame.paragraphs.runsrun(文本片断)是携带格式的最小单元(字体、字号、加粗、颜色都挂在 run 上)。
  • 所有尺寸单位为 EMU。工具类统一从 from pptx.util import Inches, Pt, Emu 导入,用 Inches(1) / Pt(18) 表达尺寸而无需手算 EMU。

这也是“为什么改文字必须精确到 run 级”的理论基础:直接整体赋值 text_frame.text 会把格式折叠成单个 run,丢失行内样式(详见下文“编辑既有演示文稿”)。

读取与信息提取:把任意 deck 变成结构化文本

技能给出的读取骨架覆盖了“页数/尺寸 → 逐页逐形状 → 表格 → 备注”四层信息:

from pptx import Presentation

prs = Presentation("deck.pptx")
print(len(prs.slides), prs.slide_width, prs.slide_height)  # EMU dims

for i, slide in enumerate(prs.slides, 1):
    print(f"--- slide {i} (layout: {slide.slide_layout.name}) ---")
    for shape in slide.shapes:
        if shape.has_text_frame:
            print(shape.text_frame.text)  # \n-joined paragraphs
        elif shape.has_table:
            for row in shape.table.rows:
                print([c.text for c in row.cells])
    if slide.has_notes_slide:
        notes = slide.notes_slide.notes_text_frame.text
        if notes:
            print("NOTES:", notes)

使用建议:

  • 通过 slide.placeholders 遍历,可查看每个占位符的 idx 与其 placeholder_format.type——当你不确定某模板“第二块占位符到底是正文还是副标题”时,先打印它。
  • 若只需纯文本,直接跨页收集 shape.text_frame.text 即可(输出为段落级 \n 连接),不必关心表格、图片等富元素。
  • slide_width/slide_height 返回的是 EMU 值(如 16:9 的 12192000×6858000),需要换算时除以 914400 得到英寸。

从大纲创建演示文稿

创建的关键第一步是先枚举模板的版式——不同模板的 layout 索引并不固定。python-pptx 默认模板的常见映射是:layout 0 = Title(标题页)、1 = Title+Content(标题+内容)、5 = Title Only6 = Blank,但务必先运行下面这段核实:

from pptx import Presentation
from pptx.util import Inches, Pt

prs = Presentation()  # or Presentation("template.pptx") to inherit a theme
for idx, lay in enumerate(prs.slide_layouts):
    print(idx, lay.name, [(p.placeholder_format.idx, p.name) for p in lay.placeholders])

模板选择上,技能特别指出两种打开方式的差异:Presentation() 使用内置默认模板,而 Presentation("template.pptx")继承该模板的主题与版式——要做带客户品牌色/公司模板的 deck 时务必用后者。

标题页与要点页

# Title slide
s = prs.slides.add_slide(prs.slide_layouts[0])
s.shapes.title.text = "My Deck"
s.placeholders[1].text = "Subtitle"  # idx from the listing above

# Title + bullets
s = prs.slides.add_slide(prs.slide_layouts[1])
s.shapes.title.text = "Agenda"
tf = s.placeholders[1].text_frame
tf.text = "First point"  # first paragraph
for line, lvl in [("Second", 0), ("Sub-point", 1)]:
    p = tf.add_paragraph()
    p.text = line
    p.level = lvl

prs.save("out.pptx")

tf.text = "First point" 会先建立第一个段落,随后用 add_paragraph() 追加,并通过 p.level 控制层级——项目符号与缩进由版式自动渲染。

技能在此有一条铁律:文字永远通过占位符/shape 写入,绝不手工书写项目符号字符(;缩进和圆点样式交给版式基于 paragraph.level 生成,手工画 会在更换主题或 Word/PPT 重排时彻底错乱。

补充文本框与图片

任何幻灯片都可以脱离占位符自由放置元素:

tb = s.shapes.add_textbox(Inches(1), Inches(1), Inches(8), Inches(1))
r = tb.text_frame.paragraphs[0].add_run()
r.text = "Hi"
r.font.size = Pt(28)
r.font.bold = True
s.shapes.add_picture("logo.png", Inches(0.5), Inches(0.5), height=Inches(1))  # omit w to keep ratio

注意 add_picture 的用法细节:只给 height(或只给 width)时,python-pptx 会按原图纵横比自动缩放另一维,这是保持 logo、截图不变形的基本功。

编辑既有演示文稿:逐 run 替换,而非整体覆盖

“模板填空”是办公自动化的高频场景。技能强调:必须在 run 层级编辑以保留原 run 的格式——重写 text_frame.text 会把段落折叠成单个 run,丢掉行内格式。

for slide in prs.slides:
    for shape in slide.shapes:
        if not shape.has_text_frame:
            continue
        for para in shape.text_frame.paragraphs:
            for run in para.runs:
                if "{{NAME}}" in run.text:
                    run.text = run.text.replace("{{NAME}}", "Frank")

删除形状/占位符需要下沉到底层 lxml 元素:sp = shape._element; sp.getparent().remove(sp)。技能给出一条实战忠告:如果模板提供的插槽多于实际数据,应把多余 shape 整个删掉,而不是留下空的占位符——空占位符在渲染时往往显示为“click to add text”的灰字,破坏成品观感。

原位替换图片(保持尺寸与位置)

python-pptx 没有直接的“替换图片”setter。技能给出的做法是直接替换相关 image part 的字节:先读取 picture 的 r:embed rId(位于其 <a:blip> 元素上),再覆写该 part 的 blob:

from pptx.oxml.ns import qn

for shape in slide.shapes:
    if shape.shape_type == 13:  # MSO_SHAPE_TYPE.PICTURE
        blip = shape._element.find(".//" + qn("a:blip"))
        rid = blip.get(qn("r:embed"))
        with open("new.png", "rb") as f:
            shape.part.related_part(rid)._blob = f.read()

其底层原理是:图片在 OOXML 里是独立的媒体 part(见 ppt/media/),而 shape 只是通过 relationship + r:embed 引用它;只要新图片与原图尺寸/宽高比相近(或接受拉伸),换 blob 即可保留原有大小与位置属性。

表格与图表

from pptx.util import Inches

tbl = s.shapes.add_table(
    rows=2, cols=2, left=Inches(1), top=Inches(1), width=Inches(6), height=Inches(2)
).table
tbl.cell(0, 0).text = "Header"

from pptx.chart.data import CategoryChartData
from pptx.enum.chart import XL_CHART_TYPE

cd = CategoryChartData()
cd.categories = ["Q1", "Q2", "Q3"]
cd.add_series("Sales", (4.5, 5.5, 6.2))
s.shapes.add_chart(XL_CHART_TYPE.COLUMN_CLUSTERED, Inches(1), Inches(1), Inches(8), Inches(4.5), cd)

图表走“数据先行”的模式:先用 CategoryChartData 构造类别轴与系列数据,再通过 add_chart 指定图表类型与位置尺寸,python-pptx 会生成内嵌图表及其工作簿。

视觉设计规范(仅当用户要“精修版”而非“数据倾倒”)

技能将设计约束限定在“用户明确想要精致 deck”的场景,避免在纯数据输出时画蛇添足,其要点可直接作为检查清单:

  • 配色:选取与主题相关的主色板 + 一个强调色,不要默认用通用蓝;一种颜色应占主导;标题/封底页用深色,内容页用浅色。
  • 版式变化:跨页轮换双栏、数据统计块(stat callout)、引言、章节分隔等不同 layout——从头到尾重复同一个“标题+圆点”版式会被视为低质量产出。多数页面最好有视觉元素(图/表/形状),而非只有标题和要点。
  • 字号体系:标题 36–44pt,节标题 20–24pt,正文 14–16pt;标题和行内标签加粗;正文左对齐,只有标题居中;页面边距保持 ≥ 0.5in。
  • 溢出防护:设置 text_frame.word_wrap = True,警惕替换后的长文本溢出。渲染后(见下节导出)务必“批判性”地目检图片,默认自己存在重叠/溢出/对比度问题并修复,再宣告完成。

导出 PDF / 图片(可选,依赖 LibreOffice)

视觉验收依赖真实渲染。技能约定的导出链路如下,任何一步缺工具都要显式告知并跳过,绝不擅自 pip install(沙箱关闭网络出口,安装也不可行):

command -v soffice >/dev/null || { echo "soffice not available — cannot export"; exit 0; }
soffice --headless --convert-to pdf out.pptx
command -v pdftoppm >/dev/null && pdftoppm -jpeg -r 150 out.pdf slide && ls -1 "$PWD"/slide-*.jpg

要点:

  • pdftoppm(poppler-utils)把 PDF 按 150 DPI 逐页转成 slide-*.jpg,Agent 再用自身的图像查看能力读取这些 JPG 路径做真实目检
  • 每次编辑后都要重新转换——PDF 不会自动反映 .pptx 的变化,忘记重跑就相当于在检查过期版本。
  • sofficepdftoppm 缺失,明确说明并跳过导出,而不是找替代方案硬来。

兼容旧格式:.ppt.pptx

旧版 .ppt 是另一套二进制格式,python-pptx 无法直接打开。处理方式是在有 soffice 时先转换:

command -v soffice >/dev/null && soffice --headless --convert-to pptx old.ppt || echo "need soffice for .ppt"

若环境无 LibreOffice,只能如实告知用户该 .ppt 无法被处理——这是二进制格式边界,不是代码能力问题。

进阶兜底:直接操作原始 OOXML

技能明确把 raw OOXML 定位为“最后的 resort”,仅用于 python-pptx 表达不了的场景,例如精确保真的幻灯片复制、渐变填充、主题色修改、非类型化 XML 元素。在此之前优先利用 python-pptx 已暴露的 shape._element(lxml 元素)做外科手术式的小修,而不是整体解包重拼。

包结构地图

.pptx 本质是 ZIP,关键 part 的分布如下:

  • 幻灯片顺序声明在 ppt/presentation.xml<p:sldIdLst>
  • 幻灯片本体在 ppt/slides/slideN.xml,其关系在 ppt/slides/_rels/slideN.xml.rels
  • 版式/母版分别在 ppt/slideLayoutsppt/slideMasters
  • 媒体资源统一放 ppt/media/
  • 所有 part 的类型在 [Content_Types].xml 中登记。

手工增改 part 的致命红线

一旦手工增删 part,任何一条被破坏,PowerPoint 都会报“文件损坏”:

  1. 每个新 part 都必须在 [Content_Types].xml 声明:幻灯片用 <Override>,媒体扩展名(如 png/jpeg)用 <Default>
  2. 所有跨 part 引用必须走 _rels/*.rels<Relationship>,XML 里的 r:id 必须可解析。新增一张幻灯片 = 写入该 part 及其 .rels + 增加 content-type override + 在 presentation.xml.rels<Relationship> + 在 <p:sldIdLst><p:sldId>
  3. ID 唯一性<p:sldId> 的 id、单张幻灯片内形状 id(<p:cNvPr id=...>)不得重复;sldLayoutId/sldMasterId 全局唯一。
  4. 空白保留:任何 <a:t> 若首尾含空格,必须加 xml:space="preserve",否则 PowerPoint 会吞掉空格。
  5. 解析/序列化:用 lxml 或 defusedxml,严禁朴素的字符串拼接把命名空间搞乱、或把“格式化缩进”变成真实文本节点。

最小文本替换:zip 手术

ZIP 成员无法原地覆写,正确姿势是重建归档、只替换一个 part

import zipfile

target = "ppt/slides/slide1.xml"
with zipfile.ZipFile("in.pptx") as zin:
    xml = zin.read(target).decode().replace("Old title", "New title")
    with zipfile.ZipFile("out.pptx", "w", zipfile.ZIP_DEFLATED) as zout:
        for item in zin.namelist():
            zout.writestr(item, xml.encode() if item == target else zin.read(item))

这段代码遍历原归档所有成员,仅对目标 XML 写出修改后的字节,其余成员原样重写,从而把破坏面控制在最小。

交付前验证:把“能打开”变成硬性门槛

技能要求在宣告完成前完成多层自检,这与 docx 技能(deeptutor/skills/builtin/docx/SKILL.md)的“Verify before returning”哲学一脉相承:

  1. 重新打开输出文件:Presentation("out.pptx") 并断言幻灯片数与关键文本——能干净重开就排除了绝大多数损坏情况;
  2. 对“要好看”的 deck,导出并目检(见导出节)——把渲染图当作存在重叠/溢出/对比度问题的默认前提来审查;
  3. 排查模板遗留文字:检查版式/模板中残留的占位符示例文本(xxxxlorem[insert ...] 等)是否被带入成品,发现即修复。

小结:把 SKILL.md 变成可复用的作业流程

回顾整份 pptx/SKILL.md,它实际封装了一条完整且自洽的作业流水线:读(枚举 + 提取)→ 建(从大纲 + 版式)→ 改(run 级替换 + 原位换图 + 表格图表)→ 美(设计规范)→ 验(导出渲染 + 重开断言)→ 兜底(raw OOXML 手术)。每一步都配套了可直接运行的 python-pptx 代码和明确的环境降级策略(无 soffice 就说 no,绝不 pip install)。

这条流水线的执行载体并非一次性脚本,而是 DeepTutor 的技能机制:SKILL.md 作为可复用的程序性知识包存放在 deeptutor/skills/builtin/,由 deeptutor/services/skill/service.py 负责运行时加载与解析(builtin 层位于 deeptutor/skills/builtin/<name>/SKILL.md),按需读取、按描述匹配触发。理解这套结构后,你既可以把它当作一份 python-pptx 速查手册直接套用,也可以参照 skill-creator 的 frontmatter 规范,沉淀出属于自己的“报告生成”“幻灯片批量制作”等专属技能包。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391