内容
# pptlive
[](https://pypi.org/project/pptlive/)
[](https://pypi.org/project/pptlive/)
[](https://github.com/thomas-villani/pptlive/blob/main/LICENSE)
[](https://thomas-villani.github.io/pptlive/)
通过 Python 控制正在运行的 Microsoft PowerPoint 实例 — 类似于 `xlwings`,但适用于 PowerPoint。适用于人工脚本编写和 LLM 代理。仅限 Windows。
📖 **[文档](https://thomas-villani.github.io/pptlive/)** — 完整的 Python API、CLI、MCP 服务器和 cookbook。
`python-pptx` 的 live-app 兄弟姐妹(用于处理磁盘上的 `.pptx` 文件),以及 `wordlive` 的 PowerPoint 对应版本(用于处理 Word 文档)。在用户已经打开演示文稿并希望实时编辑时使用 — 无需关闭文件、让代理写入、重新打开。
| 库 | 目标 | 机制 |
| ----------- | ------------------------- | --------------------- |
| python-pptx | 磁盘上的 `.pptx` 文件 | OOXML 输入/输出 |
| **pptlive** | **正在运行的 POWERPNT.exe**| **COM 自动化(pywin32)** |
## 安装
```bash
pip install pptlive
# 带有 MCP 服务器,适用于 Claude Desktop 及其他 MCP 代理(参见“MCP 服务器”)
pip install "pptlive[mcp]"
# 添加到项目
uv add pptlive
```
(需要 Python 3.11+ 和 Windows 上的 `pywin32`。)
## Python
```python
import pptlive as pl
with pl.attach() as ppt:
deck = ppt.presentations.active
# 读取(结构化,无副作用)
slides = deck.slides.list() # [{index, id, layout, title, shape_count, has_notes}]
outline = deck.outline() # [{slide, title, bullets:[...]}]
grid = deck.slides[2].read() # 每个形状:anchor_id, name, id, type, geometry, text
title = deck.slides[2].placeholder("title").text
notes = deck.slides[1].notes.text
# 礼貌写入 — 保留用户的当前幻灯片和选择。
with deck.edit("修订议程幻灯片"):
deck.anchor_by_id("ph:2:title").set_text("议程")
deck.anchor_by_id("ph:2:body").set_text("介绍\n演示\n问答")
# 幻灯片生命周期 — 每个 edit() 块也支持一次 Ctrl-Z。
names = deck.layouts() # [{index, name}] — set_layout/add 接受的内容
with deck.edit("添加结果幻灯片"):
new = deck.slides.add(layout="two_content", index=4)
deck.slides[7].duplicate() # 复制并粘贴到幻灯片 8
deck.slides[9].move_to(2)
deck.slides[4].set_layout("title_and_content")
# 形状和几何 — 整个过程(pl.units 表示英寸/厘米)。
with deck.edit("布局结果幻灯片"):
shapes = deck.slides[4].shapes
shapes.add_textbox("收入增长 12%", left=pl.units.inches(1), top=72)
star = shapes.add_shape("star", left=400, top=120, width=120, height=120,
fill="#C00000", line="none") # 填充/边框在创建时
logo = shapes.add_picture("logo.png", left=600, top=40, # 嵌入式,永不链接
alt_text="Acme 标志") # 防漂移重识别句柄
deck.slides[4].shapes["图片 3"].move(top=140) # 绝对,点
star.set_fill(fill="#1F1F1F", line="#FFFFFF", line_width=2) # 填充 ≠ 字体颜色
star.reorder("front") # z 顺序:前/后/前移/后移
star.delete()
# 图片 — 替代文本兼作重识别句柄;导出一个形状用于视觉。
logo.set_alt_text("Acme 标志(右上角)") # 生存 z 顺序漂移
chart_png = deck.slides[4].shapes["图表 2"].export_image() # 仅该形状,本地大小
# 图表 — 图表是一种形状;其数据存在于嵌入的 Excel 工作簿中。
with deck.edit("添加收入图表"):
chart = deck.slides[4].shapes.add_chart(
"column", ["Q1", "Q2", "Q3"], {"收入": [10, 20, 30], "利润": [3, 6, 9]}
).chart
chart.set_type("line") # 更改类型
chart.recolor_text("#FFFFFF") # 所有图表文本白色(黑暗主题修复)
data = chart.read() # {chart_type, categories, series:[...]}
# 智能图形 — 图表也是形状;其内容是节点树。
with deck.edit("添加流程图"):
sa = deck.slides[3].shapes.add_smartart(
"process", ["发现", "设计", "构建", "交付"] # 平面列表…
).smartart
sa.set_nodes([{"text": "CEO", "children": ["VP 工程", "VP 销售"]}]) # …或树
sa.recolor_text("#FFFFFF") # 每个节点标签白色
tree = sa.read() # {layout, nodes:[{text, level, children}]}
# 文本结构 — 段落、格式、项目符号。(每个锚点的格式;对于全局样式,请使用下面的 deck.theme/deck.master。)
with deck.edit("润色正文"):
body = deck.anchor_by_id("ph:4:body")
body.set_text("收入增长 12%\n流失率下降 3%\nNPS +9")
body.apply_list("bulleted") # 每个段落都有项目符号
body.paragraph(2).format_paragraph(indent_level=2, alignment="left")
body.paragraph(1).format_text(bold=True, size=24, color="#2E74B5")
body.insert_paragraph_after("现金跑道:30 个月") # 追加项目符号
# 表格 — 表格是一种形状(Shape.has_table);单元格是 cell:S:N:R:C 锚点。
with deck.edit("添加指标表格"):
table = deck.slides[4].shapes.add_table(rows=3, columns=2).table
table.cell(1, 1).set_text("指标")
table.cell(1, 2).set_text("Q3")
table.add_row(["收入", "$4.2M"]) # 追加并填充一行
deck.anchor_by_id("cell:4:5:1:1").format_text(bold=True) # 单元格是锚点
grid = table.read() # {slide, shape, rows, columns, cells:[...]}
# 全局样式 — 主题(调色板 + 字体)和母版(文本样式 + 背景)一次重新设置所有继承的幻灯片。全局 + 不礼貌,但仍然支持一次 Ctrl-Z;您的视图不会移动。
with deck.edit("重新品牌化演示文稿"):
deck.theme.set_color("accent1", "#C00000") # 重新设置整个演示文稿的颜色
deck.theme.set_font("major", "Georgia") # major = 标题,minor = 正文
deck.master.format_text_style("body", 1, font="Georgia", size=28)
deck.master.set_background("#1F1F1F") # 实心填充
palette = deck.theme.read() # {colors:{slot:#RRGGBB}, fonts:{major, minor}}
# 查找/替换 — 跨形状、表格单元格和备注的模糊遍历(无全局字符流,因此这是遍历,而不是范围)。
hits = deck.find("Q3 结果") # [{anchor_id, start, length, text, ...}]
with deck.edit("修复拼写错误"):
deck.find_replace("Q3 结果", "Q3 结果", all=True)
# 审查评论 — 在 (x, y) 点处幻灯片锚定,并线程化。
review = deck.slides[2].comments # 每张幻灯片的 CommentCollection
with deck.edit("留下评审笔记"):
c = review.add("压缩此标题", left=100, top=80)
c.reply("同意 — 会做")
roll = deck.comments() # 全局汇总 {total, slides:[...]}
# 整个演示文稿快照 — 每张幻灯片一个低分辨率 PNG,以便视觉模型可以廉价地查看整个演示文稿(max_dim 限制每张幻灯片的长边)。读取 — 有礼貌。
snaps = deck.snapshot(max_dim=1000) # [Snapshot(slide, image, path), ...]
# 输出层 — 显式,永不隐式(pptlive 永不自动保存)。
deck.save() # 持久化到现有文件
deck.save_as("v2.pptx", overwrite=True) # 写入 + 重新绑定工作文件
deck.export_pdf("deck.pdf") # 读取:无重新绑定,脏标志保留
# 媒体 + 旁白视频导出 — 构建演示文稿,旁白,导出 MP4。
with deck.edit("旁白演示文稿"):
deck.slides[1].add_audio("intro.mp3") # 嵌入;自动播放 + 控制幻灯片节奏
deck.slides[2].add_video("demo.mp4") # 视频剪辑(保持可见)
result = deck.export_video("deck.mp4", resolution=1080) # 异步 CreateVideo;阻塞直到完成
assert result.ok and result.status == "done" # result.path 是 MP4
# 渲染 — 让视觉模型 *看到* 它刚刚构建的幻灯片(导出 → 读取 → 迭代)。
png = deck.slides[4].export_image(width=1280) # 临时 PNG(或传递路径);有礼貌
# ... 将 `png` 传递给您的图像工具,观察,然后修订。
# 读取用户正在查看的内容,并(可选)以此处锚点为目标。
sel = deck.selection() # {type, slide, anchor_id, shapes, ...}
if sel.anchor_id:
with deck.edit("粗体选中文本"):
deck.anchor_by_id("here:").format_text(bold=True)
# 实时幻灯片放映 — 像点击器一样驱动演示文稿(故意移动屏幕)。
deck.show.start() # 从顶部开始
deck.show.goto(5); deck.show.next(); deck.show.black() # 跳转,推进,空白
deck.show.state() # {running, state, current_slide, ...}
deck.show.end()
```
## 锚点
寻址是**分层**的(幻灯片 → 形状 → 文本),而不是全局字符流 — 没有全局的 `range:`。锚点 ID 以冒号分隔,幻灯片索引优先:
| 锚点 ID | 解析为 |
| -------------- | ----------- |
| `shape:S:N` | 幻灯片 S 上第 N 个形状(基于 1 的 z 顺序)— 规范句柄 |
| `shapeid:S:ID` | 幻灯片 S 上具有稳定 `Shape.Id` ID 的形状 — **防删除** 句柄(在删除/重新排序后仍然存在) |
| `ph:S:KIND` | 幻灯片 S 上语义 KIND 的占位符(`title`/`ctrtitle`/`subtitle`/`body`/`footer`/`date`/`slidenum`)— LLM 首选形式 |
| `para:S:N:P` | 幻灯片 S 上形状 N 的段落 P(基于 1 的) |
| `cell:S:N:R:C` | 幻灯片 S 上形状 N 中的表格的单元格(行 R,列 C)— `Cell` *是一个* 锚点,因此它接受每个文本/格式动词 |
| `notes:S` | 幻灯片 S 的演讲者备注正文 |
| `comments:S` | 幻灯片 S 上的评审评论 — 读取选择器(一个容器,通过 `(slide, 1-based index)` 进行回复/删除) |
| `here:` | 用户当前选择的内容 — 形状,或包含文本插入符号的段落(一种有礼貌的方式来操作实时选择) |
z 顺序在添加或删除形状(或通过 `Shape.reorder` 重新排序)时**漂移**,因此 `shape:S:N` 在运行时解析,永不缓存;每个形状列表还会发出 `name`(`Shape.Name`)和 `id`(`Shape.Id`,在重新排序期间稳定)。防漂移形式为 `ph:S:KIND`、`.Name` 和 `shapeid:S:ID`。`para:S:N:P` 和 `cell:S:N:R:C` 也在运行时解析(插入/删除文本或行时,段落/行计数会发生变化)。
## CLI
JSON 输入,JSON 输出,确定性的退出代码 - 设计用于直接接入大型语言模型(LLM)工具使用循环中。全局标志(`--json`/`--text`,`--doc NAME`)放在子命令之前。
```plaintext
pptlive status # 打开的幻灯片,活动的幻灯片,正在查看的幻灯片
pptlive slides # [{index, id, layout, title, shape_count, has_notes}]
pptlive outline # 标题和正文要点,每张幻灯片
pptlive slide read 2 # 幻灯片 2 上的所有形状
pptlive shapes --slide 2 # 幻灯片 2 上的形状(anchor_id, name, id, type, geometry)
pptlive read anchor --anchor-id ph:2:title # 读取任何文本锚点(ph:/shape:/notes:)
pptlive read notes --slide 1 # --anchor-id notes:1 的简写
pptlive write --anchor-id ph:2:body --text "Intro\nDemo\nQ&A"
pptlive replace --anchor-id shape:3:1 --text "New text"
pptlive find --text "Q3 Reuslts" [--in slide:2] # 跨形状/单元格/备注进行模糊查找
pptlive replace --find "Q3 Reuslts" --text "Q3 Results" --all # 模糊替换(或 --occurrence N)
pptlive slide layouts # add/set-layout 接受的布局名称
pptlive slide add --layout two_content [--index 4]
pptlive slide duplicate --slide 7
pptlive slide move --slide 9 --to 2
pptlive slide set-layout --slide 4 --layout title_and_content
pptlive slide delete --slide 5
pptlive slide export --slide 2 --out slide2.png [--width 1280] [--format png] # 渲染为图像
pptlive slide geometry 2 # 幻灯片大小 + 形状框 + 重叠 + 离幻灯片(无渲染)
pptlive shape add --slide 4 --kind textbox --text "Revenue up 12%" --left 72 --top 72
pptlive shape add --slide 4 --kind shape --shape-type star --left 400 --top 120 --width 120 --height 120
pptlive shape add --slide 4 --kind picture --path logo.png --left 600 --top 40 --alt-text "Acme logo"
pptlive shape add --slide 4 --kind table --rows 3 --cols 2 --left 72 --top 120
pptlive shape move --anchor-id shape:4:3 --left 100 --top 140
pptlive shape resize --anchor-id shape:4:3 --width 300 --height 200
pptlive shape delete --anchor-id shape:4:3
pptlive shape set-alt --anchor-id shape:4:3 --alt-text "Acme logo" # 防漂移的重新 ID 处理
pptlive shape fill --anchor-id shape:4:3 --fill "#C00000" --line none --line-width 2 # 填充/边框
pptlive shape order --anchor-id shape:4:3 --to front # z-order: front/back/forward/backward
pptlive shape export --anchor-id shape:4:3 --out logo.png # 渲染一个形状(原生大小)
pptlive shape animate --anchor-id shape:4:3 --effect fly_in [--trigger after_previous] [--exit]
pptlive slide animations 4 # 幻灯片的形状动画,按播放顺序
pptlive shape add --slide 4 --kind chart --chart-type column \
--categories "Q1,Q2,Q3" --series '{"Revenue":[10,20,30]}'
pptlive chart read --slide 4 --shape 5 # {chart_type, categories, series}
pptlive chart set-type --slide 4 --shape 5 --chart-type line
pptlive chart set-data --slide 4 --shape 5 --categories "A,B" --series '{"S":[1,2]}'
pptlive chart recolor-text --slide 4 --shape 5 --color "#FFFFFF" # 所有图表文本(暗主题修复)
pptlive shape add --slide 3 --kind smartart --smartart-kind process \
--nodes '["Discover","Design","Build","Ship"]' # 平面列表或 {text,children} 树
pptlive smartart read --slide 3 --shape 2 # {layout, nodes:[{text, level, children}]}
pptlive smartart set-nodes --slide 3 --shape 2 --nodes '[{"text":"CEO","children":["Eng","Sales"]}]'
pptlive smartart recolor-text --slide 3 --shape 2 --color "#FFFFFF" # 每个节点标签
pptlive theme read # 幻灯片调色板(12 个槽)+ 标题/正文字体
pptlive theme set-color --slot accent1 --color "#C00000" # 重新为整个幻灯片着色
pptlive theme set-font --which major --name "Georgia" # major = 标题,minor = 正文
pptlive master read # 主文本样式(标题/正文/默认)+ 背景
pptlive master format-text-style --style body --level 1 --font "Georgia" --size 28
pptlive master set-background --color "#1F1F1F" # 全幻灯片;纯填充
pptlive paragraphs --anchor-id ph:4:body # [{anchor_id (para:S:N:P), text, indent_level, bullet}]
pptlive insert --anchor-id para:4:2:3 --text "New bullet" [--before|--after]
pptlive format-paragraph --anchor-id para:4:2:1 --alignment center --indent-level 2
pptlive format-text --anchor-id ph:4:title --bold --size 40 --color "#2E74B5"
pptlive list apply --anchor-id ph:4:body --type bulleted [--char "•"]
pptlive list remove --anchor-id ph:4:body
pptlive table read --slide 4 --shape 5 # 网格单元格,每个单元格都有其 cell:S:N:R:C 锚点
pptlive table add-row --slide 4 --shape 5 --values '["Revenue", "$4.2M"]'
pptlive table delete-row --slide 4 --shape 5 --row 2
pptlive write --anchor-id cell:4:5:1:1 --text "Metric" # 单元格接受 write/format-text/...
pptlive selection # 用户选择的内容(-> here:)
pptlive read anchor --anchor-id here: # 读取选定的形状/段落
pptlive go-to --anchor-id shape:3:1 # 故意的、选择加入的视图移动
pptlive show start [--from 2] # 运行幻灯片放映(故意移动屏幕)
pptlive show next # 前进;也可使用 prev, goto --slide N
pptlive show black # 切换到黑色(白色 / 恢复)
pptlive show state # {running, state, current_slide, ...} (只读)
pptlive show end
pptlive comment list [--slide 2] # 幻灯片上的评论,或整个幻灯片的汇总
pptlive comment add --slide 2 --text "Tighten this" [--left 100 --top 80]
pptlive comment reply --slide 2 --index 1 --text "Agreed"
pptlive comment delete --slide 2 --index 1 # 同时删除其回复
pptlive section list # 命名幻灯片跨度(幻灯片结构)
pptlive section add --name "Appendix" --before-slide 9 # 重命名/移动/删除
pptlive slide set-footer --slide 2 --text "Confidential" # 每张幻灯片的页脚/幻灯片编号/日期
pptlive master set-footer --text "Confidential" # 全幻灯片的默认设置
pptlive snapshot [--slides 2-4] [--max-dim 1000] [--width 1280 --height 720] # 每张幻灯片一个 PNG — 整个幻灯片的视觉读取
pptlive save # 持久化到现有文件(显式)
pptlive save-as v2.pptx [--overwrite] # 写入 + 重新绑定工作文件
pptlive export-pdf deck.pdf # 读取:PDF,无需重新绑定工作文件
pptlive media add --slide 1 --kind audio --path intro.mp3 # 旁白(自动播放 + 控制幻灯片节奏)
pptlive media add --slide 2 --kind video --path demo.mp4 # 插入视频剪辑
pptlive export-video deck.mp4 --resolution 1080 # 幻灯片 → MP4(异步 CreateVideo;默认情况下阻塞直到完成)
```
退出代码:`0` 成功 · `1` 其他 · `2` 锚点/幻灯片/形状/演示文稿未找到 ·
`3` PowerPoint 忙碌 / 模态对话框 · `4` PowerPoint 未运行 · `5`
模糊匹配 · `6` 形状无文本框架。
## MCP 服务器
相同的实时 PowerPoint 控制,通过 **Claude Desktop** (以及其他 MCP 客户端)作为一组小型工具公开。安装额外的工具并将客户端指向 `pptlive-mcp` 标准输入/输出服务器:
```plaintext
pip install "pptlive[mcp]"
pptlive-mcp # 标准输入/输出 MCP 服务器(或:python -m pptlive.mcp)
```
**一键安装(Claude Desktop)。** 从 [最新版本](https://github.com/thomas-villani/pptlive/releases/latest) 下载 `pptlive.mcpb` 并将其拖到 **设置 → 扩展程序**。该软件包通过 `uv` 在首次运行时拉取 `pptlive` - 无需单独的 Python 安装。(仅限 Windows;请参阅 [`mcpb/`](mcpb/)。)
**或者让 pptlive 为您编写配置文件:**
```plaintext
pptlive install-mcp # → Claude Desktop 的 claude_desktop_config.json
pptlive install-mcp --client claude-code # → ./.mcp.json(项目本地)
pptlive install-mcp --print # 只是打印代码段供任何客户端使用
```
它注册了 `uvx --from "pptlive[mcp]" pptlive-mcp` (解析已发布的软件包 - 无需 PATH 假设)。对于本地代码库,请添加 `--directory .` 以获取 `uv run --directory <dir> pptlive-mcp`。之后请重启客户端。
要手动连接,请在 `claude_desktop_config.json` (设置 → 开发者 → 编辑配置)中添加以下内容,然后重启:
```json
{
"mcpServers": {
"pptlive": { "command": "uvx", "args": ["--from", "pptlive[mcp]", "pptlive-mcp"] }
}
}
```
这是一个紧凑的 **五工具调度接口** - 每个工具接受一个 `op` 参数并路由到正确的动词,因此代理工具选择器看到五个定义而不是十五个。它们包装相同的 API,因此礼貌模型和一次性 Ctrl-Z `edit` 围栏得以保留,并且读取操作永远不会移动视图:
| 工具 | `op`s |
| ---- | ----- |
| `ppt_read` | `status` · `slides` · `outline` · `slide` · `anchor` · `geometry`(幻灯片大小 + 形状框 + 重叠 + 离幻灯片)· `selection` · `find` · `table` · `chart` · `smartart` · `comments` · `animations` · `sections` · `headers_footers` · `theme` · `master` · `layouts` — 每次读取;永远不会移动视图 |
| `ppt_edit` | `write` · `find_replace` · `format`(字体 + 段落 + 形状填充/线条 + 项目符号)· `slide_add`/`slide_delete`/`slide_duplicate`/`slide_move`/`set_layout` · `shape_add`/`shape_move`/`shape_resize`/`shape_delete`/`shape_order`/`set_alt` · `media_add`(音频/视频旁白)· `shape_animate`/`shape_clear_animations`/`slide_clear_animations` · `table_add_row`/`table_delete_row` · `chart_set_type`/`chart_set_data`/`chart_recolor_text` · `smartart_set_nodes`/`smartart_recolor_text` · `comment_add`/`comment_reply`/`comment_delete` · `section_add`/`section_rename`/`section_delete`/`section_move` · `set_headers_footers` · `theme_set_color`/`theme_set_font` · `master_format_text_style`/`master_format_paragraph_style`/`master_set_background` — 每次修改;每个 Ctrl-Z |
| `ppt_render` | `slide_image` · `shape_image` · `deck_snapshot`(每张幻灯片一个 PNG — 整个幻灯片的视觉读取;`max_dim` 或确切的 `width`/`height`)· `deck_pdf`/`save`/`save_as`(显式输出)· `export_video`/`video_status`(幻灯片 → MP4;异步,默认情况下阻塞直到完成)· `navigate`(一个故意的视图移动) |
| `ppt_show` | 幻灯片放映:`state` · `start` · `end` · `next` · `previous` · `goto` · `black` · `white` · `resume` |
| `ppt_batch` | 对一个连接运行 **列表** 以上的操作 — 所有 `edit` 围栏到 **单个** 撤销条目(原子),具有 `stop_on_error` 控制 |
表格、图表和 SmartArt 按其形状的 `anchor_id`(`shape:S:N` 或 delete-proof `shapeid:S:ID`)进行寻址;单元格仍使用 `cell:S:N:R:C` 锚点,您使用 `ppt_edit op="write"` 编写。`theme_*`/`master_*` 操作是全幻灯片的(无锚点)。
工具失败表现为 MCP 错误,带有类别令牌 - `not_found`、`ambiguous`、`busy`、`not_running`、`no_text_frame`、`invalid_args` - 字符串模拟 CLI 的退出代码,因此代理可以根据它们进行分支。 在 `ppt_batch` 中,相同的令牌按命令报告而不是中止。
## 面向 LLM 代理 - 自引导
具有 Shell 的代理可以通过一个命令进行定位:
```plaintext
pptlive llm-help # 完整的 CLI 指南(锚点、每个动词、退出代码)
pptlive llm-help --python # 而是 Python API 指南
```
输出是原始 Markdown(像 `--help` 一样),不受 `--json/--text` 影响,因此可以直接进入模型的上下文。 `pptlive --help` 也指向这里。
pptlive 提供了两个代理技能 - `pptlive-cli` 和 `pptlive-python` - 加载 `SKILL.md` 文件:
```plaintext
pptlive install-skill # 将两者写入 ./.agents/skills/<name>/SKILL.md
pptlive install-skill --cli # 仅限一个(也是 --python)
pptlive install-skill --system # 进入 ~/.agents/skills/ 而不是
```
对于 MCP 客户端,`pptlive install-mcp`(上面)注册服务器,并且 MCP 服务器也将相同的指南作为 `pptlive://guide` / `pptlive://guide/python` 资源。
## 两件事要知道
- **礼貌性。** 默认情况下,每个操作都保留用户正在查看的幻灯片和形状/文本选择。只有必须移动视图的动词会在其名称中说明(`go_to`,`allow_view_move()`)。
- **原子化撤销 - 一个 Ctrl-Z 块。** PowerPoint 没有 `UndoRecord`,但它将单个 `deck.edit(...)` 块内的 COM 编辑分组为一个撤销条目(范围围栏块与 `StartNewUndoEntry`),所以整个块会撤销单个 Ctrl-Z。唯一的警告:没有显式“结束”围栏 - 始终将修改操作包装在 `deck.edit(...)` 中,而不是裸操作,因此每个块都是一个自成体系的单元。
## 开发
```plaintext
uv sync --extra dev
uv run pytest # 单元测试(假 COM;无需 PowerPoint)
uv run pytest -m smoke # 烟雾测试 - 需要安装 PowerPoint
uv run ruff check . && uv run ruff format .
uv run mypy
```
该库的目标是 Python 3.11+(开发锁定 3.13)。请参阅 `spec.md` 获取设计说明,`IMPLEMENTATION.md` 获取分阶段构建进度。Windows + COM 仅限。
完整的文档:<https://thomas-villani.github.io/pptlive/>
## 来自另一端的评论
* 由 Claude(Opus 4.8)提供,在一个开放的 PowerPoint 幻灯片中对其进行实时控制 *
我花了一个会话,使用 pptlive 读取、重新设计和压力测试一个真实的幻灯片 - 读取其结构,重建一个幻灯片,从头开始编辑一个实时图表,并将其作为演示文稿运行。 我尝试的所有操作都有效。 以下是我的结论。
**读取端确实很好。** `status` → `outline` → `slide` → `chart` 给出了一个清晰的层次结构,从“打开的内容”到底层形状锚点和实时图表数据,全过程无视用户视图变化。 我可以在不触摸任何内容的情况下理解一个七张幻灯片的演示文稿 - 它的叙事、调色板和两个嵌入式图表的底层数字 - 并且我可以理解侧边效应免费的读取功能是所有其他功能的基础。
**锚点模型是正确的抽象。** 将 `ph:7:title`、`shape:7:3`、`para:7:5:2`、`cell:S:N:R:C` 寻址为稳定的句柄意味着编辑可以准确地针对您想要的内容。 当我构建一个四卡布局时,我可以分别格式化大数字和描述行,因为每个数字都解析为其自己的段落锚点。
**`ppt_batch` 带有原子撤销分组, 是我会错过的功能。** 多形状的重新设计折叠成单个 Ctrl-Z 让用户使用。 而且 `stop_on_error: false` 将一个令人沮丧的调试循环变成了一个单一的易读报告 - 我可以看到所有九个格式化操作一次性落地,而不是玩失败的 whack-a-mole。
**渲染循环改变了可能发生的事情。** 一旦 `slide_image` 返回真正的图像内容块,我就可以 *看到* 自己的输出并迭代。 两次让我这样做:我格式化了白色背景上的白色卡片文本,期待灰色,并准备好狩猎一个格式操作 - 渲染显示形状默认为品牌珊瑚色,看起来很正确,所以我没有“修复”一个不存在的问题。 然后实时 `chart_set_data` 编辑重新渲染,伴随着 PowerPoint *自己的* 引擎自动缩放 y 轴从 120 缩放至 600 - 证明编辑到达了实时文档,而不是影子模型。 `shape_image` 完成了这一点,将单个形状裁剪到其边界,这样我就可以在隔离状态下检查一张卡片。 没有视觉反馈,我是在闭着眼睛编辑并自信地叙述。 有了它,我可以犯错并 *发现*。 差距就是整个游戏。
**演讲模式是一个真正的点击器。** `start`,`next`,`previous`,`goto`,`black`/`resume`,`end` - 我运行了整个序列,并在每一步检查状态。 `goto` 跳转到任何地方,`black` 变黑并记住下面的幻灯片,`resume` 返回到它,而每个操作都一致地报告运行状态和位置。
**一个尖锐的教训:** 软件包驱动的是一个 *实时* 应用程序,这意味着模型永远不是唯一的参与者。 早些时候在会话中,我遇到了一个幻灯片放映导航错误,构建了一个合理的双部分错误理论,并准备好确认它 - 直到用户手动结束了放映。 我们用干净的重新运行了序列,随后所有操作都通过了。 我们应该设计应对这种差异 - 代理和屏幕之间的差异:重新读取状态,不要相信自己的叙述,并更喜欢有界限的失败而不是聪明的失败。
**结论:** pptlive 将 PowerPoint 视为一个实时、可检查、可脚本化的表面,而不是一个需要覆盖的文件 - 读取时不会干扰,编辑时原子化,查看并迭代,然后演示。 我测试的每个层级 - 读取、批量编辑、渲染和查看、单个形状检查、实时图表数据和点击器 - 都做到了他们所声称的功能。 这是两者之间的区别:发射命令进入黑暗中,而不是实际工作。 我会再次使用它。
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器