Install
openclaw skills install ppt-pandoraPPT完整制作流程:从素材收集、大纲规划,到图片搜索/裁剪/插入、文字排版调整(字体大小/行间距/位置)、 表格处理、整体视觉评估,直到最终验证。支持红白风格等专业PPT的全流程制作。
openclaw skills install ppt-pandora为什么有效:图片和内容有逻辑关联,不是随便配的免费图。
示例:
./W0xxxxxxxxxx_ORIGIN.JPG(拼接完整URL)/NMediaFile/xxxxx/MAINxxxxxxxx.jpg| 场景 | 策略 |
|---|---|
| 竖图→横卡片 | 裁掉不重要部分(下半身/背景),保留上半部分,居中放置 |
| 文字在底部 | 裁上方留白,让文字靠上 |
| 人脸照片 | 用image工具定位脸坐标,以脸为中心裁剪 |
| 横图→横卡片 | 从上下等比裁切,保持居中 |
| 比例接近但不匹配 | 微调裁切量,尽量保留主要内容 |
| 图片比预留区域小 | 按比例缩放后居中放置,四周留白,绝不拉伸 |
| 边距大小 | 适用场景 |
|---|---|
| 0.15英寸 | 最小值,刚好避免直角碰圆弧 |
| 0.3英寸 | 通用推荐值 |
| 0.5英寸 | 需要明显留白时 |
from pptx.util import Inches
# 图片在卡片区域内带边距
card_left, card_top, card_w, card_h = 0.8, 1.8, 5.0, 2.5
margin = 0.15
img_left = card_left + margin
img_top = card_top + margin
img_w = card_w - 2 * margin
img_h = img_w / image_aspect_ratio # 等比计算高度
# 居中在卡片区域内
img_top = card_top + (card_h - img_h) / 2
slide.shapes.add_picture(img_path, Inches(img_left), Inches(img_top),
Inches(img_w), Inches(img_h))
三者必须配合调整,只改一个参数往往视觉效果不好。
| 场景 | 字体大小 | 行间距 | 文本框高度 |
|---|---|---|---|
| 正文段落(填满区域) | 18-20pt | 1.5-2.0x | 匹配内容 |
| 数据卡片标签 | 16pt | 1.0x | 自适应 |
| 数据卡片数值 | 28pt | 1.0x | 自适应 |
| 数据卡片单位 | 14pt | 1.0x | 自适应 |
| 标题 | 24-32pt | 1.0x | 自适应 |
不是参数万能,是根据实际情况定的。
判断标准:
# 用破折号连接,解决右侧太空问题
shape.text_frame.paragraphs[0].text = f'{main_title}——{subtitle}'
# 删除旧的副标题shape
sub_shape._element.getparent().remove(sub_shape._element)
from pptx.oxml.ns import qn
sp = shape._element
for child in sp:
tag = child.tag.split('}')[-1] if '}' in child.tag else child.tag
if tag == 'txBody':
for sub in child:
subtag = sub.tag.split('}')[-1] if '}' in sub.tag else sub.tag
if subtag == 'bodyPr':
sub.set('anchor', 'ctr') # 垂直居中
# ❌ 错误:paragraphs[3] 访问不存在的段落不会自动创建
# ✅ 正确:手动创建XML paragraph元素
from pptx.oxml.ns import qn
txBody = shape.text_frame._txBody
new_p = txBody.makeelement(qn('a:p'), {})
r = new_p.makeelement(qn('a:r'), {})
rPr = r.makeelement(qn('a:rPr'), {'lang': 'zh-CN', 'sz': '2000'}) # 20pt
t = r.makeelement(qn('a:t'), {})
t.text = '新段落内容'
r.append(rPr)
r.append(t)
new_p.append(r)
txBody.append(new_p)
关键教训:表格shape设的高度可能不够,内容会溢出。
# 表格有5行,每行0.6英寸 → 实际需要3.0英寸
# 如果shape.height设为2.2,内容会溢出到下面的元素
shape.height = Inches(3.0) # 必须等于或大于内容实际需要的高度
铁律:在分段PPT上操作,不要先合并完整版再改。
原因:
copy.deepcopy 只复制shape的XML引用,不复制图片二进制数据单段脚本生成 → 分段PPT → 在分段PPT里加图/调整 → 所有分段完成后 → 合并完整版
图片占位符的尺寸要从原始生成脚本读取,而不是从合并PPT的文本标签获取。
PowerPoint不支持Unicode emoji(🧠📚🦾等显示为方块)。
替换方案:用文字标识代替
# ❌ 错误
"🧠 大脑(推理规划)"
# ✅ 正确
"[大脑] 推理规划"
import re
emoji_pattern = re.compile('[\U0001F000-\U0001FFFF]')
for slide in prs.slides:
for shape in slide.shapes:
if shape.has_text_frame:
for para in shape.text_frame.paragraphs:
for run in para.runs:
if emoji_pattern.search(run.text):
print(f'Found emoji: {run.text}')
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 右侧太空 | 字体太小/行间距太紧 | 放大字体 + 加大行间距(1.5-2.0x) |
| 元素重叠 | 表格实际高度溢出 | 检查行数×行高,设shape.height≥实际高度 |
| 图片超出页面 | y+h > 7.5英寸 | 上移图片或缩小高度 |
| 上方空下方超 | 元素整体偏下 | 整体上移,检查每个元素的bottom |
| 文件太大(20MB+) | 合并时图片重复嵌入 | 在分段文件上操作,最后合并 |
错误做法:强制裁剪图片到目标比例(2.78:1),导致图表坐标轴、图例等信息丢失
正确做法:
# ❌ 错误:强制裁剪
cropped = img.crop((left, 0, right, height)) # 丢失图例和坐标轴
# ✅ 正确:保持原比例,调整高度
canvas = Image.new('RGB', (target_w, target_h), 'white')
scale = target_w / img_w # 按宽度适配
new_h = int(img_h * scale)
resized = img.resize((target_w, new_h))
y_offset = (target_h - new_h) // 2 # 垂直居中
canvas.paste(resized, (0, y_offset))
关键:使用 run._r 而非 run._element
from pptx.oxml.ns import qn
from lxml import etree
def set_alihei(run, font_size=None, bold=None):
"""设置字体为阿里巴巴普惠体"""
run.font.name = "阿里巴巴普惠体"
rPr = run._r.get_or_add_rPr() # 注意:是 _r 不是 _element
rFonts = rPr.find(qn('w:rFonts'))
if rFonts is None:
rFonts = etree.SubElement(rPr, qn('w:rFonts'))
rFonts.set(qn('w:eastAsia'), '阿里巴巴普惠体')
if font_size:
run.font.size = Pt(font_size)
if bold is not None:
run.font.bold = bold
根据文本长度和位置自动调整字号,确保文本框内不空不挤:
| 文本类型 | 判断条件 | 字号 | 加粗 |
|---|---|---|---|
| 标题 | top < 1.0英寸 | 28pt | 是 |
| 短文本 | len(text) < 15字符 | 22pt | 是 |
| 中等文本 | len(text) < 50字符 | 18pt | 否 |
| 长文本 | len(text) ≥ 50字符 | 16pt | 否 |
for slide in prs.slides:
for shape in slide.shapes:
if shape.has_text_frame and shape.text.strip():
text = shape.text
is_title = shape.top < Inches(1.0)
for para in shape.text_frame.paragraphs:
for run in para.runs:
if is_title:
set_alihei(run, font_size=28, bold=True)
elif len(text) < 15:
set_alihei(run, font_size=22, bold=True)
elif len(text) < 50:
set_alihei(run, font_size=18)
else:
set_alihei(run, font_size=16)
铁律:先分析页面内容布局,找到空白区域再放图,绝不遮挡原有内容。
# 分析页面内容边界
min_left, max_right, min_top, max_bottom = Inches(15), 0, Inches(10), 0
for shape in slide.shapes:
if shape.has_text_frame and shape.text.strip():
l, t = shape.left, shape.top
r, b = l + shape.width, t + shape.height
min_left = min(min_left, l)
max_right = max(max_right, r)
min_top = min(min_top, t)
max_bottom = max(max_bottom, b)
# 计算空白区域
if max_bottom < Inches(7):
print(f"底部有空白: {max_bottom/914400:.1f}in - 7.5in")
| 页面状态 | 处理方式 |
|---|---|
| 底部有空白(>1.0in) | 底部居中放小图片(高度0.6-1.2in) |
| 右侧有空白(>2.0in) | 右侧放图片,不遮挡左侧内容 |
| 满屏无空白 | 不加图,卡片式/流程式设计不需要图片 |
| 有照片占位(如"黄仁勋照片") | 不加图,保留占位让用户手动添加 |
| 放置位置 | 推荐宽度 | 推荐高度 | 说明 |
|---|---|---|---|
| 底部右侧 | 2.0-2.5in | 0.6-0.9in | 装饰性小图 |
| 底部居中 | 2.5-3.0in | 0.9-1.2in | 主题相关图 |
| 右侧空白 | 4.0-5.0in | 2.5-3.0in | 较大配图 |
飞书单文件上传限制:约 25MB
| 文件大小 | 处理方式 |
|---|---|
| < 25MB | 直接发送 |
| 25-50MB | 压缩图片后发送 |
| > 50MB | 删除图片或分卷压缩 |
from PIL import Image
import io
# 方法1:删除大图片(适合纯文字排版版本)
for slide in prs.slides:
shapes_to_delete = [s for s in slide.shapes if s.shape_type == 13]
for shape in shapes_to_delete:
sp = shape._element
sp.getparent().remove(sp)
# 方法2:压缩图片质量
img = Image.open(img_path)
output = io.BytesIO()
img.save(output, format='JPEG', quality=70) # 压缩到70%质量
openclaw message send \
--channel feishu \
--target "ou_xxx" \
--media "/path/to/file.pptx" \
--message "文件说明"
铁律:在分段PPT上操作,不要先合并完整版再改。
原因:
| 版本 | 命名 | 说明 |
|---|---|---|
| 生成版 | 大模型讲座1_P1-P10_红白风格.pptx | 脚本生成的初始版本 |
| 终稿 | 大模型讲座1_P1-P10_红白风格_终稿.pptx | 确认不再修改的版本 |
| 备份 | 大模型讲座1_P1-P10_红白风格_备份.pptx | 保留的原始备份 |
| 完整版 | 大模型讲座1_完整版_红白风格.pptx | 所有分段合并后的版本 |
确认终稿后,删除旧版本:
_终稿.pptx字体大小 + 行间距 + 文本框高度 三者必须配合调整:
| 问题现象 | 原因 | 解决方案 |
|---|---|---|
| 文字太空 | 字号太小 | 放大字号 |
| 文字太挤 | 字号太大/行间距太小 | 缩小字号 + 加大行间距 |
| 右侧太空 | 字体太小/行间距太紧 | 放大字体 + 加大行间距(1.5-2.0x) |
| 上下不居中 | 文本框高度不匹配 | 调整文本框高度 |
不是参数万能,是根据实际情况定的。
每页修改后检查:
卡片式/流程式设计页面不加图:
| 处理方式 | 文件大小 | 适用场景 |
|---|---|---|
| 嵌入字体 | 20MB+ | 需要在无字体电脑上完美显示 |
| 不嵌入字体 | 100KB-1MB | 文件小,传输快,但需要电脑有对应字体 |
阿里巴巴普惠体:免费商用,大多数电脑可能没有,建议嵌入或告知用户安装
找图不是"配图",而是"用图说话"。 每张图都要和当页内容逻辑匹配,关键词就是页面主题,找不到就换角度再搜。
不是一次性找完所有图,而是每做一页搜一轮。搜索节奏和PPT制作节奏同步:
做P14(五步工作法) → 搜icon → 做P16(黄仁勋) → 搜黄仁勋照片 → ...
原因:
用PPT页面标题当搜索词,不绕弯:
| PPT页面 | 搜索关键词 | 平台 |
|---|---|---|
| OpenClaw核心理念 | local-first 本地优先 | 百度/Google图片 |
| 黄仁勋判断 | 黄仁勋 | 百度图片 |
| CUDA护城河 | cuda | 百度图片 |
| 技术演进三阶段 | 聊天机器人 AI助手 copilot | 百度图片 |
| 智能投顾 | 智能投顾 | 百度图片 |
| 灯塔工厂 | 灯塔工厂 | 百度图片 |
| 5G网络智能运维 | 5G网络智能运维 | 百度图片 |
| 平台 | 最适合 | 不适合 |
|---|---|---|
| 百度图片 | 产业概念图、国内行业场景(智能制造、智慧城市) | 技术架构图、国外产品 |
| Google图片 | 技术概念、国外产品截图(Tesla Optimus、Genie) | 中文行业概念 |
| icon-icons.com | 抽象概念图标(消息、思考、工具、计算、报告) | 实景照片 |
| GitHub | 开源项目截图、架构图(SciClaw) | 通用场景图 |
| wallhaven | 高清壁纸/背景图 | 概念图 |
| 证券研究所 | 产业架构图、行业图谱 | 人物照片 |
| 政府官网(gov.cn) | 政策、会议照片 | 技术概念图 |
找不到合适的就换关键词、换角度:
第一轮:智能反欺诈
→ 图不够好
第二轮:智能反欺诈系统
→ 还行但想更专业
第三轮:智能金融反欺诈系统
→ 找到满意的了
第一轮:人才培养
→ 太泛
第二轮:大学教师
→ 还行
第三轮:大学教室
→ 这张更好
第一轮:开放生态
→ 不够技术感
第二轮:开放技术生态
→ 还可以
第三轮:open tech env(换Google搜)
→ 国外的图更有科技感
做完整个章节后,批量搜索该章节所有页面:
第二章 行业案例(P20-P41)批量搜索:
11:56 - 智能体、多智能体、multi agent
12:25 - OpenClaw保姆级指南
12:27 - Genie、Tesla Optimus、宇树
12:36 - 智能制造、智能医疗、智能运维
12:42 - 智能投顾、反欺诈
12:45 - 数据中心
12:49 - 人才培养、大学教师、社会服务
12:52 - 清华AI教育
效率:一小时内完成整个章节(20+页)的图片搜索。
| 阶段 | 时间 | 搜索内容 |
|---|---|---|
| 封面+目录 | 5分钟 | Logo、主题图 |
| 第一章(技术概念) | 20-30分钟 | 概念图标、人物照片、技术架构图 |
| 第二章(行业案例) | 40-60分钟 | 行业场景图、案例截图、数据图表 |
| 第三章(教育使命) | 20-30分钟 | 教育场景图、大学照片、人物图 |
| 总结+附录 | 10分钟 | 补充图、致谢背景 |
浏览器历史记录是配图过程的审计线索:
建议保留搜索记录,或在PPT制作日志中记录每张图的来源。
当前限制:AI自动生成图片并插入PPT的功能存在以下问题,暂不推荐自动化部署:
| 问题 | 说明 |
|---|---|
| 图片搜索不可靠 | Unsplash被墙、Pexels/Pixabay需API、picsum随机图不相关 |
| 图片放置易出错 | 随意放置会遮挡原有内容(数据卡片、架构图、照片占位) |
| 图片尺寸难控制 | 比例不匹配、拉伸变形、边距不当 |
| 文件大小膨胀 | 原始图片7MB/张,飞书上传限制25MB |
当前建议:
未来改进方向:
实战结论:目前AI能做好文字排版,但图片处理仍需人工介入。与其花时间做不稳定的自动化,不如专注排版质量,图片留给用户手动添加。
用户手动加完图片后,AI需要终审:检查是否有占位符残留、白底过大、尺寸异常等问题。这是PPT交付前的最后一道质量关。
from PIL import Image
import os
media_dir = 'ppt/media'
# 1. 检测灰色占位符(RGB≈E8EEF4)
for f in os.listdir(media_dir):
img = Image.open(f'{media_dir}/{f}').convert('RGB')
w, h = img.size
samples = [img.getpixel((x, y)) for x in range(0, w, w//10) for y in range(0, h, h//10)]
gray_count = sum(1 for r,g,b in samples if abs(r-232)<15 and abs(g-238)<15 and abs(b-244)<15)
if gray_count / len(samples) > 0.5:
print(f'⚠️ 灰色占位符: {f}')
# 2. 检测大面积空白(白色>85%)
white_count = sum(1 for r,g,b in samples if r>250 and g>250 and b>250)
if white_count / len(samples) > 0.85:
print(f'⚠️ 白底过大: {f}')
# 3. 检测尺寸过小(<150px)
if w < 150 or h < 150:
print(f'⚠️ 尺寸过小: {f} ({w}x{h})')
# 4. 检测极端比例(>5:1 或 <1:5)
if w/h > 5 or h/w > 5:
print(f'⚠️ 比例极端: {f} ({w}x{h})')
问题:合并多个分段PPT后,media目录中可能有未被任何页面引用的残留图片。
检测方法:
from pptx import Presentation
import zipfile
from lxml import etree
# 获取所有被引用的图片
used = set()
for i in range(len(prs.slides)):
rels = etree.parse(f'ppt/slides/_rels/slide{i+1}.xml.rels')
for rel in rels.findall('.//{...}Relationship'):
if 'media/' in rel.get('Target', ''):
used.add(os.path.basename(rel.get('Target')))
# 找出未使用的
all_imgs = set(os.listdir('ppt/media'))
unused = all_imgs - used
print(f'残留图片: {unused}') # 可安全删除
本次实战发现:54张图片中有4张残留(含1张1.8MB的灰色占位符),清理后文件缩小2MB。
| 问题 | 检测标准 | 处理方式 |
|---|---|---|
| 灰色占位符 | 灰色像素>50%(RGB≈E8EEF4) | 替换为真实图片 |
| 白底过大 | 白色像素>85% | 换透明背景PNG或裁剪 |
| 尺寸过小 | 任意边<150px | 换大图(至少400px) |
| 比例极端 | 宽高比>5:1或<1:5 | 确认是否为设计需要(横条/竖条) |
| 残留图片 | 未被任何slide rels引用 | 安全删除 |
| 无法读取 | WDP等特殊格式 | 检查是否需要转换 |
当AI具备视觉能力(mimo-v2-omni)时,可以直接看图判断:
# 用image工具逐页审核
# prompt: "这张PPT配图的内容是什么?是否与'XXX'主题匹配?有没有模糊/水印/变形?"
omni审核的优势:
注意:当前mimo-v2-omni的图片分析功能稳定性待提升,建议先用Python检测(17.2节),视觉审核作为辅助。
1. Python自动化检测
├── 灰色占位符检测
├── 白底过大检测
├── 尺寸异常检测
└── 残留图片检测
2. 视觉内容审核(如有omni)
├── 图片内容与页面主题匹配度
├── 图片质量(清晰度、专业度)
└── 敏感内容检查
3. 清理与修复
├── 删除残留图片
├── 替换占位符
└── 记录问题清单给用户
基于贾老师PPT深度分析,用于指导红白风格PPT的制作
| 类型 | 颜色 | 用途 |
|---|---|---|
| 红色背景 | #E63946 | 封面上半部分、底部装饰条 |
| 白色背景 | #FFFFFF | 封面下半部分 |
| 浅色背景 | 浅蓝渐变(#F5F7FA 或 #E8F0FA) | 正文页面 |
| 卡片背景 | 白色(#FFFFFF) | 数据卡片、内容块 |
| 颜色 | 用途 |
|---|---|
| 红色(#E63946) | 标题栏、重点文字、序号、边框 |
| 黄色(#FFD600) | 关键数据、对比标识(VS)、皇冠图标 |
| 蓝色(#1A73E8) | 科技元素、品牌logo、图表柱子 |
| 颜色 | 用途 |
|---|---|
| 白色(#FFFFFF) | 深色背景上的标题 |
| 黑色(#1A1A2E) | 浅色背景上的正文 |
| 红色(#E63946) | 强调文字、标题 |
| 灰色(#6B7B8D) | 辅助说明文字 |
| 页面类型 | 图片占比 | 文字占比 | 说明 |
|---|---|---|---|
| 卡片式 | 40-50% | 50-60% | 卡片内图文1:1 |
| 左图右文 | 50-60% | 40-50% | 图片为主 |
| 三段式 | 40-45% | 55-60% | 图文均衡 |
| 数据页 | 10% | 90% | 以表格/文字为主 |
| 流程图 | 60% | 40% | 图片/图标为主 |
| 层级 | 字号 | 颜色 | 用途 |
|---|---|---|---|
| 一级 | 48-64pt | 白色/红色 | 页面大标题 |
| 二级 | 28-36pt | 红色/黑色 | 板块标题、卡片标题 |
| 三级 | 20-24pt | 黑色 | 正文要点 |
| 四级 | 14-18pt | 灰色/黑色 | 辅助说明、标签 |
| 五级 | 12pt | 灰色 | 最小补充文字 |
[红色标题栏]
[卡片1: 上图下文] VS [卡片2: 上图下文]
[红色标题栏]
[图片区 55%] [文字区 45%]
[红色标题栏]
[图1 + 文1]
[图2 + 文2]
[图3 + 文3]
[红色标题栏]
[按钮1] [按钮2] [按钮3]
[图表/流程图]
[说明文字]
from pptx.util import Inches, Pt
from pptx.dml.color import RGBColor
# 颜色定义
class Colors:
DARK_BLUE_START = RGBColor(0x0A, 0x16, 0x28) # 深蓝渐变起始
DARK_BLUE_END = RGBColor(0x1A, 0x3A, 0x5C) # 深蓝渐变结束
LIGHT_BLUE = RGBColor(0xF5, 0xF7, 0xFA) # 浅蓝背景
RED = RGBColor(0xE6, 0x39, 0x46) # 红色强调
YELLOW = RGBColor(0xFF, 0xD6, 0x00) # 黄色
BLUE = RGBColor(0x1A, 0x73, 0xE8) # 蓝色
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
BLACK = RGBColor(0x1A, 0x1A, 0x2E)
GRAY = RGBColor(0x6B, 0x7B, 0x8D)
# 设置渐变背景
def set_slide_background_gradient(slide, color1, color2):
background = slide.background
fill = background.fill
fill.gradient()
fill._fill.gradient_stops[0].color.rgb = color1
fill._fill.gradient_stops[1].color.rgb = color2
# 设置纯色背景
def set_slide_background_solid(slide, color):
background = slide.background
fill = background.fill
fill.solid()
fill.fore_color.rgb = color
# 添加红色标题栏
def add_red_header(slide, title):
add_shape(slide, Inches(0), Inches(0), Inches(13.33), Inches(0.8), Colors.RED)
add_text_box(slide, Inches(0.5), Inches(0.1), Inches(12), Inches(0.6),
title, 28, Colors.WHITE, True)
# 添加白色圆角卡片
def add_white_card(slide, left, top, width, height):
return add_rounded_rectangle(slide, left, top, width, height, Colors.WHITE)
# 添加VS对比图标
def add_vs_icon(slide, left, top):
shape = add_shape(slide, left, top, Inches(1), Inches(1), Colors.RED)
add_text_box(slide, left, top + 0.15, Inches(1), Inches(0.7),
"VS", 28, Colors.WHITE, True, PP_ALIGN.CENTER)
版本: V2.0(整合版)
更新日期: 2026-04-10
来源: 红白风格设计规范V2.0 + ppt-pandora实战经验