Initial codex-ppt skill

This commit is contained in:
ningzimu
2026-04-26 15:33:32 +08:00
commit d6ac4e4033
23 changed files with 1618 additions and 0 deletions
+87
View File
@@ -0,0 +1,87 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# Virtual Environment
venv/
env/
ENV/
.venv/
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store
# Logs
*.log
# Generated files
*.png
*.jpg
*.jpeg
*.gif
*.bmp
*.pptx
*.pdf
examples/
!assets/
!assets/slides_example.png
!assets/style-previews/
!assets/style-previews/*.png
# Test outputs
test_output/
output/
temp/
# macOS
.DS_Store
.AppleDouble
.LSOverride
# Windows
Thumbs.db
ehthumbs.db
Desktop.ini
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# pytest
.pytest_cache/
.coverage
htmlcov/
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# data
/data
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 lining
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+155
View File
@@ -0,0 +1,155 @@
# Codex PPT Skill
一个面向 Codex 的 PPT 生成 skill。它把文章、报告、论文、课程笔记等内容转换成“整页图片式”的演示文稿先规划大纲和视觉风格再使用 Codex 内置的 `gpt-image-2` 生成每页幻灯片图片,最后用本地脚本组装为 `.pptx`
## 特点
- 使用 Codex 内置的 `gpt-image-2` 生图和编辑图能力生成每页幻灯片图片
- 图片式 PPT每页幻灯片是一张完整 16:9 图片,适合强视觉表达
- 风格参考库:内置商务、清爽专业、电子墨水杂志、手绘技术解释、仪表盘等多种风格说明
- 整套 PPT 保持统一视觉语言,但每页会按内容语义调整版式,避免机械重复
- 本地组装脚本:将 `slide_01.png``slide_02.png` 等图片打包成 PowerPoint
## 目录结构
```text
codex-ppt-skill/
├── README.md
├── LICENSE
├── assets/
│ └── style-previews/
└── skills/
└── codex-ppt/
├── SKILL.md
├── requirements.txt
├── scripts/
│ └── assemble_ppt.py
└── references/
├── 清爽专业风.md
├── 创意杂志风.md
├── 电子墨水杂志风.md
├── 手绘技术解释风.md
├── 数据仪表盘风.md
└── ...
```
## 安装
将 skill 目录复制或链接到 Codex skills 目录:
```bash
mkdir -p ~/.codex/skills
ln -s /path/to/codex-ppt-skill/skills/codex-ppt ~/.codex/skills/codex-ppt
```
安装 PPT 组装脚本依赖:
```bash
python3 -m venv ~/.codex/skills/codex-ppt/.venv
~/.codex/skills/codex-ppt/.venv/bin/python -m pip install -r ~/.codex/skills/codex-ppt/requirements.txt
```
## 使用方式
在 Codex 中提出类似请求:
```text
请使用 codex-ppt 把 /path/to/article.md 做成 10 页左右的 PPT风格偏商务专业。
```
skill 会按以下流程执行:
1. 阅读内容并规划 PPT 大纲
2. 确认页数、标题和每页要点
3. 给出 2-3 个视觉风格选项,并推荐一个让用户确认
4. 使用 `gpt-image-2` 生成 1 页样张,让用户确认风格、版式节奏和文字质量
5. 创建 PPT 项目目录
6. 使用 `gpt-image-2` 逐页生成全部幻灯片图片
7. 检查文字清晰度、风格一致性和内容完整性
8. 生成 `outline.md``speech.md`
9. 使用 `assemble_ppt.py` 组装 `.pptx`
## 生成效果
下面是一套技术分享 PPT 的生成效果示例。每页都是由 `gpt-image-2` 生成的完整 16:9 幻灯片图片,再由本地脚本组装为 PPTX。
![生成 PPT 效果示例](assets/slides_example.png)
## 风格示例
以下是已生成预览图的风格,示例图均由 `gpt-image-2` 生成,用于帮助用户在开始制作前选择视觉方向。
| 清爽专业风 | 创意杂志风 |
| --- | --- |
| ![清爽专业风](assets/style-previews/clean-professional.png) | ![创意杂志风](assets/style-previews/creative-magazine.png) |
| 电子墨水杂志风 | 数据仪表盘风 |
| --- | --- |
| ![电子墨水杂志风](assets/style-previews/e-ink-magazine.png) | ![数据仪表盘风](assets/style-previews/data-dashboard.png) |
| 复古扁平插画风 | 手绘技术解释风 |
| --- | --- |
| ![复古扁平插画风](assets/style-previews/retro-flat-illustration.png) | ![手绘技术解释风](assets/style-previews/handdrawn-technical.png) |
| 手绘白板风 | 温暖手工风 |
| --- | --- |
| ![手绘白板风](assets/style-previews/handdrawn-whiteboard.png) | ![温暖手工风](assets/style-previews/warm-handmade.png) |
## 输出结构
每个 PPT 会生成一个独立项目目录:
```text
{基础目录}/{PPT名称}/
├── origin_image/
│ ├── slide_01.png
│ ├── slide_02.png
│ └── ...
├── outline.md
├── speech.md
└── {PPT名称}.pptx
```
`origin_image/` 只放正式页图片,并按 `slide_01.png``slide_02.png` 这样的顺序命名。样张确认时也直接使用对应页的正式文件名;如果要保留废稿或对比图,放到项目根目录或单独的 `drafts/` 目录,不要放进 `origin_image/`
`speech.md` 会在组装时写入 PPT 的每页备注。建议使用 `## Slide 1: 标题``## Slide 2: 标题` 这样的标题格式,脚本会按页码匹配。
## 手动组装 PPT
如果你已经有一组幻灯片图片,可以直接运行脚本:
```bash
~/.codex/skills/codex-ppt/.venv/bin/python ~/.codex/skills/codex-ppt/scripts/assemble_ppt.py /path/to/base MyPresentation.pptx --init
```
把图片保存到 `/path/to/base/MyPresentation/origin_image/` 后,再组装 PPT
```bash
~/.codex/skills/codex-ppt/.venv/bin/python ~/.codex/skills/codex-ppt/scripts/assemble_ppt.py /path/to/base MyPresentation.pptx --aspect-ratio 16:9
```
脚本会读取:
```text
/path/to/base/MyPresentation/origin_image/
```
只会读取 `slide_01.png``slide_02.png` 这类正式图片;`sample_slide.png`、草稿图、参考图会被忽略。如果项目目录下存在 `speech.md`,脚本会把对应 `Slide N` 段落写入 PPT 备注。
并输出:
```text
/path/to/base/MyPresentation/MyPresentation.pptx
```
## 适用场景
- 技术文章转分享 PPT
- 论文或报告转演示稿
- 课程笔记转课件
- 商业汇报、产品介绍、调研总结
- 需要强视觉统一性的图片式演示文稿
## 许可证
MIT
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

+318
View File
@@ -0,0 +1,318 @@
---
name: codex-ppt
description: Generate image-based PowerPoint decks from articles, reports, papers, notes, or outlines. Use this skill when the user asks Codex to create a visually unified PPT/PPTX deck where each slide is a full-slide generated image, then assemble those images into a PowerPoint file.
---
# Codex PPT
## Overview
This skill creates image-based PPT decks. Each slide is a complete 16:9 image generated with Codex's built-in `gpt-image-2` capability. The image contains the slide title, key points, and visual composition. The generated images are then assembled into a `.pptx` file with `scripts/assemble_ppt.py`.
Use Codex's built-in `gpt-image-2` image generation and image editing capabilities for every slide image.
## Use When
Use this skill when the user asks to:
- Turn an article, report, paper, document, course note, or rough outline into a PPT.
- Create a visually consistent presentation deck.
- Generate slides as full-page images.
- Produce supporting `outline.md` and `speech.md` files.
- Assemble generated slide images into a `.pptx`.
Do not use this skill for ordinary editable PowerPoint layouts where each textbox, chart, or shape must remain separately editable. This workflow prioritizes visual quality and consistency over editability.
## Workflow
### 1. Understand Source Content
Read the user-provided content fully enough to identify:
- Main topic and intended audience
- Presentation goal
- Required or implied page count
- Required style or brand constraints
- Any sections that must be included or excluded
If the user did not specify a page count, choose a practical count based on content length. Typical decks are 8-12 slides.
### 2. Plan The Deck Outline
Create a concise outline before generating images. For each slide, define:
- Slide number
- Slide title
- 3-5 key points
- Optional visual idea
- Layout role and intent, such as cover, agenda, section divider, concept explanation, process, comparison, timeline, data evidence, architecture, case study, summary, or Q&A
Show the outline to the user for confirmation before generating slide images, unless the user explicitly asked you to skip confirmation.
Recommended structure:
```text
Slide 1: Cover
Slide 2: Context / problem
Slide 3-7: Main argument or sections
Slide 8: Summary / recommendation / closing
```
### 3. Confirm A Unified Visual Style
Before generating slide images, discuss the visual style with the user. Prefer a multiple-choice question: offer 2-3 concrete style directions and mark one as your recommendation.
Each style option should briefly specify:
- Color palette
- Layout system
- Typography direction
- Illustration or image treatment
- Decorative elements
- Density and whitespace rules
After the user chooses a style, create one final style direction and keep the visual identity consistent across all slide prompts. Keep color palette, typography, texture, icon/illustration language, and overall mood stable. Do not reuse the same layout on every page.
The `references/` directory contains optional style references. Use them as inspiration, not as rigid templates. Adapt the style to the topic and audience.
Important: a deck should have one coherent visual identity, not one repeated composition. Treat each reference as a style system: stable palette, typography, icon language, texture, and visual mood; variable page layout chosen from the slide's content role. `layout_blueprints` are candidate starting points only. Do not apply the same blueprint to every slide.
Available references:
- `references/清爽专业风.md`
- `references/创意杂志风.md`
- `references/电子墨水杂志风.md`
- `references/数据仪表盘风.md`
- `references/复古扁平插画风.md`
- `references/手绘技术解释风.md`
- `references/手绘白板风.md`
- `references/温暖手工风.md`
Example style confirmation:
```text
我建议用 A因为它最适合这份内容的受众和表达目标。
A. 清爽专业风(推荐):浅色背景、蓝绿强调色、结构清晰,适合汇报、答辩和技术分享。
B. 创意杂志风:大标题、强图片、留白更大胆,适合分享和传播。
C. 数据仪表盘风:指标卡、图表感布局,适合数据密集型报告。
你选哪个?也可以指定要调整的配色、布局或插画方向。
```
### 4. Generate One Sample Slide For Approval
After the outline and style are confirmed, generate exactly one sample slide image with `gpt-image-2` before full production.
Sample slide requirements:
- Use the confirmed style description.
- Prefer a representative content slide over the cover when possible.
- Demonstrate the intended deck rhythm: the sample should show how the chosen style adapts to a real content page, not just a generic fixed template.
- Save it directly as the intended final slide filename, such as `{base_dir}/{deck_name}/origin_image/slide_08.png`.
- Show the sample image to the user.
- Ask the user to confirm the visual style, typography, layout density, and Chinese text quality.
Do not generate the full deck until the user approves the sample slide. If the user requests changes, revise the style description and regenerate that same `slide_XX.png` file first. Once approved, keep that file as the final slide for its page. Do not create `sample_slide.png` in `origin_image/`, because the assembly step is designed around final `slide_XX` filenames.
### 5. Create The Project Directory
Use this output structure:
```text
{base_dir}/{deck_name}/
├── origin_image/
│ ├── slide_01.png
│ ├── slide_02.png
│ └── ...
├── outline.md
├── speech.md
└── {deck_name}.pptx
```
If the user did not specify a destination, use the current working directory or the directory that contains the source file.
You may initialize the directory structure with:
```bash
~/.codex/skills/codex-ppt/.venv/bin/python ~/.codex/skills/codex-ppt/scripts/assemble_ppt.py {base_dir} {deck_name}.pptx --init
```
### 6. Generate All Slide Images
Generate one image per slide with Codex's built-in `gpt-image-2` image generation capability. Every final `slide_XX.png` must be produced by `gpt-image-2`; programmatic rendering or hybrid text overlay is not acceptable for slide image creation.
Use a structured visual brief for each slide. GPT-Image-2 works best when the prompt separates canvas, style, layout, text, visual elements, and constraints instead of relying only on a long style paragraph.
Keep the deck visually coherent but vary slide layouts according to page semantics. Treat style references and `layout_blueprints` as candidate patterns, not fixed templates. Across a normal deck, deliberately mix suitable page types such as:
- cover / section divider
- context or problem framing
- process or timeline
- comparison or tradeoff
- data / evidence / KPI
- architecture or workflow diagram
- summary / conclusion / next steps
Avoid generating every slide as the same three-card layout. For each slide, choose a layout that fits its content and explain that choice in the `layout.intent` field.
```json
{
"type": "16:9 full-slide PowerPoint image",
"language": "Chinese",
"canvas": {
"aspect_ratio": "16:9",
"use_full_canvas": true,
"slide_number": "do not render a slide number"
},
"style": {
"name": "{confirmed style name}",
"visual_direction": "{same final style description for every slide}",
"color_palette": "{main colors and accent colors}",
"typography": "{font personality, hierarchy, weight, text alignment}",
"texture_and_finish": "{flat, paper, dashboard, editorial, whiteboard, etc.}",
"deck_consistency": "same palette, typography, icon language, texture, and mood across all slides"
},
"layout": {
"role": "{cover, agenda, section divider, concept, process, comparison, timeline, data evidence, architecture, case study, summary, Q&A, etc.}",
"intent": "{why this page uses this layout: cover, comparison, timeline, data evidence, workflow, summary, etc.}",
"composition": "{specific layout for this slide}",
"content_zones": "{title zone, body zone, visual zone, footer or callout zones}",
"variation_rule": "same style identity as the deck, but vary composition by slide role; do not repeat the same blueprint on adjacent slides unless the content is part of a deliberate repeated sequence",
"relationship_to_previous_slide": "{new layout, continuation layout, mirrored layout, or deliberate repeated sequence}",
"spacing": "clear hierarchy, coherent alignment, no overlapping elements"
},
"text": {
"title": "{slide title}",
"key_points": ["{point 1}", "{point 2}", "{point 3}"],
"text_quality": "render all Chinese text exactly, clearly, and without garbled characters"
},
"visual_elements": {
"main_visual": "{icons, diagram, chart, illustration, dashboard cards, collage, or other content-specific visual idea}",
"supporting_elements": "{arrows, cards, callouts, decorative elements, labels}"
},
"constraints": [
"The final image itself must contain the title and key points.",
"All text must be readable and correctly spelled.",
"Keep the confirmed style consistent with the rest of the deck.",
"No watermark, no unrelated logo, no extra slide number."
]
}
```
Save images as:
```text
{base_dir}/{deck_name}/origin_image/slide_01.png
{base_dir}/{deck_name}/origin_image/slide_02.png
...
```
After each image is generated, copy or move it into `{base_dir}/{deck_name}/origin_image/` immediately. Do not leave final slide images only in Codex's default generated-images directory.
Final slide image naming rules:
- Rename final slide images strictly by slide order: `slide_01.png`, `slide_02.png`, `slide_03.png`, ...
- Use zero-padded two-digit numbers for normal decks.
- The approved sample slide should already have the correct `slide_XX.png` filename and should be reused directly.
- Keep rejected variants, drafts, or reference images out of `origin_image/`. If you need to preserve them, place them in the project root or a separate `drafts/` directory.
- Before assembling, verify every expected `slide_XX.png` exists in `origin_image/` and that there are no missing or extra final slide images.
For Chinese decks, explicitly ask `gpt-image-2` to render Chinese text accurately and avoid garbled characters.
### 7. Quality Check And Repair
Before assembling the PPT, inspect every slide image. Check:
- Text is readable and not garbled.
- Slide content matches the outline.
- Title and key points are not truncated.
- Visual style is consistent across slides.
- No page number appears unless the user requested one.
- Important elements do not overlap.
If a slide has severe text or layout issues, regenerate it with a more constrained prompt. If a slide is mostly correct but has a localized issue, use Codex's built-in `gpt-image-2` image editing capability when available.
### 8. Write Supporting Files
Create `outline.md` with the final deck outline:
```markdown
# {Deck Title}
## Outline
### Slide 1: {Title}
- {Point}
- {Point}
### Slide 2: {Title}
- {Point}
- {Point}
```
Create `speech.md` with speaker notes. Keep it useful and concise: 1-3 short paragraphs per slide is usually enough.
Use headings that the assembly script can map back to slide numbers:
```markdown
## Slide 1: {Title}
{Speaker notes for slide 1}
## Slide 2: {Title}
{Speaker notes for slide 2}
```
### 9. Assemble The PPT
Run:
```bash
~/.codex/skills/codex-ppt/.venv/bin/python ~/.codex/skills/codex-ppt/scripts/assemble_ppt.py {base_dir} {deck_name}.pptx --aspect-ratio 16:9
```
Important:
- `{base_dir}` is the parent directory of `{deck_name}/`.
- `{deck_name}.pptx` must match the project folder name.
- The script reads images from `{base_dir}/{deck_name}/origin_image/`.
- The script only reads final images named like `slide_01.png`, `slide_02.png`, etc.; drafts and sample files are ignored.
- If `{base_dir}/{deck_name}/speech.md` exists and uses `Slide N` headings, the script writes those notes into the corresponding PPT speaker notes.
- The script writes `{base_dir}/{deck_name}/{deck_name}.pptx`.
### 10. Final Report
Report:
- Project directory
- PPT file path
- Slide image directory
- `outline.md` path
- `speech.md` path
- Number of slides
- Confirm that slide images were generated with `gpt-image-2`
- Confirm that speaker notes from `speech.md` were written into the PPT, if applicable
- Any slides that were regenerated or still have known limitations
## Assembly Script Dependency
`scripts/assemble_ppt.py` requires:
```bash
python3 -m venv ~/.codex/skills/codex-ppt/.venv
~/.codex/skills/codex-ppt/.venv/bin/python -m pip install -r ~/.codex/skills/codex-ppt/requirements.txt
```
The script supports `16:9` and `4:3`. Use `16:9` unless the user requests otherwise.
## Prompting Principles
- Keep one global visual style fixed across the deck.
- Vary slide composition by page role; style consistency does not mean repeating the same layout.
- Use `layout_blueprints` as candidate patterns, not mandatory templates.
- Generate one slide per image request.
- Prefer concrete visual direction over generic words like "beautiful" or "professional".
- For dense content, split across more slides instead of crowding one slide.
- Prioritize clarity over decoration.
@@ -0,0 +1,72 @@
# 创意杂志风
**适用场景:**
- 创意提案
- 品牌展示
- 设计作品集
- 文化活动
- 时尚发布
- 艺术展览
- 创意工作室介绍
**GPT-Image-2 风格 Brief:**
```json
{
"type": "16:9 full-slide PowerPoint image",
"style_name": "创意杂志风",
"best_for": "需要强视觉记忆点、品牌个性或传播感的分享型演示",
"visual_direction": "high-end editorial magazine spread, bold asymmetry, art-directed composition, graphic tension, premium creative layout",
"canvas": {
"aspect_ratio": "16:9",
"background": "white, black, or deep charcoal with intentional negative space",
"composition": "asymmetric editorial layout with a large headline, image/collage zone, and 2-3 supporting text blocks",
"density": "strong contrast between dense editorial blocks and open whitespace"
},
"color_palette": {
"base": "black, white, and gray",
"accent": "one vivid accent such as neon pink #FF006E, lemon yellow #FFED00, or electric cyan #00F5FF",
"support": "optional lavender, coral, or muted fashion tones",
"rule": "use one dominant accent color consistently; do not make the palette chaotic"
},
"typography": {
"title": "oversized bold display sans-serif or editorial serif, can occupy 25-45% of the slide",
"body": "small clean sans-serif blocks with strong alignment",
"emphasis": "keywords may use accent color, rotated labels, vertical text, or extreme scale contrast",
"text_quality": "Chinese headline and key points must be exact and readable"
},
"layout_patterns": [
"oversized headline on one side with collage or abstract image block on the other",
"diagonal color block cutting across the slide",
"magazine cover style with title, subtitle, and three feature teasers",
"editorial grid with one large image crop and small annotation labels"
],
"layout_usage_rule": "Use layout_blueprints as candidate starting points only. Choose and adapt the composition according to each slide's semantic role; avoid repeating the same blueprint on adjacent slides unless it is a deliberate repeated sequence.",
"layout_blueprints": [
{
"name": "editorial hero spread",
"sections": [
{"position": "left 45%", "count": 1, "labels": ["oversized headline"]},
{"position": "right 55%", "count": 1, "labels": ["collage or abstract image zone"]},
{"position": "bottom-left", "count": 3, "labels": ["feature teaser 1", "feature teaser 2", "feature teaser 3"]}
]
},
{
"name": "cover story grid",
"sections": [
{"position": "top", "count": 1, "labels": ["large cover title"]},
{"position": "center", "count": 2, "labels": ["main visual block", "accent typography block"]},
{"position": "right edge", "count": 3, "labels": ["short editorial callouts"]}
]
}
],
"visual_elements": {
"allowed": "bold geometric blocks, cropped photography zones, halftone texture, thin rules, torn-paper collage edges, accent stickers, abstract shapes",
"avoid": "generic corporate icons, overly symmetrical layout, low-contrast text, too many accent colors"
},
"rendering_constraints": [
"The slide should feel designed, not templated.",
"Maintain legibility despite bold composition.",
"No watermark, no unrelated logo, no slide number unless explicitly requested."
]
}
```
@@ -0,0 +1,72 @@
# 复古扁平插画风
**适用场景:**
- 文化创意项目展示
- 品牌故事讲述
- 旅游景点介绍
- 复古产品发布
- 艺术设计作品集
- 创意活动宣传
- 生活方式类演示
**GPT-Image-2 风格 Brief:**
```json
{
"type": "16:9 full-slide PowerPoint image",
"style_name": "复古扁平插画风",
"best_for": "文化创意、品牌故事、生活方式、城市旅游和带叙事感的主题演示",
"visual_direction": "retro flat vector illustration slide, cream paper texture, monoline black outlines, vintage palette, playful handcrafted design",
"canvas": {
"aspect_ratio": "16:9",
"background": "cream/off-white paper #F5F3E8 with subtle grain",
"composition": "panoramic illustration band plus structured content cards or labels",
"density": "moderate, decorative but still clear"
},
"color_palette": {
"base": "cream background and dark slate text #34495E",
"vintage_colors": "coral red #FF6B6B, mint green #95E1D3, mustard yellow #F9CA24, burnt orange #E17055, slate blue #6C7A89",
"line": "uniform black or deep charcoal outline",
"rule": "flat fills only, no glossy 3D, no heavy gradients"
},
"typography": {
"title": "bold retro serif or chunky vintage display type",
"body": "geometric sans-serif, clear and friendly",
"labels": "small badge labels with outlined shapes",
"text_quality": "Chinese text must be exact and readable"
},
"layout_patterns": [
"top panoramic flat illustration with bottom content cards",
"central retro title with three outlined icon cards",
"2.5D simplified scene with callout labels",
"vintage poster composition adapted to presentation readability"
],
"layout_usage_rule": "Use layout_blueprints as candidate starting points only. Choose and adapt the composition according to each slide's semantic role; avoid repeating the same blueprint on adjacent slides unless it is a deliberate repeated sequence.",
"layout_blueprints": [
{
"name": "panoramic illustration plus cards",
"sections": [
{"position": "top third", "count": 1, "labels": ["panoramic flat vector illustration"]},
{"position": "middle", "count": 1, "labels": ["large retro title"]},
{"position": "bottom", "count": 3, "labels": ["outlined content card 1", "outlined content card 2", "outlined content card 3"]}
]
},
{
"name": "retro poster diagram",
"sections": [
{"position": "center", "count": 1, "labels": ["main simplified scene or object"]},
{"position": "around center", "count": 4, "labels": ["callout label", "badge", "mini icon", "decorative note"]},
{"position": "bottom-right", "count": 1, "labels": ["summary plaque"]}
]
}
],
"visual_elements": {
"allowed": "flat vector scenes, 2-3px monoline outlines, simplified buildings, plants, clouds, badges, dotted textures, small geometric decorations",
"avoid": "photorealism, complex shadows, neon palette, overly detailed linework, unreadable decorative type"
},
"rendering_constraints": [
"The slide should feel like a polished retro illustration system, not a random cartoon.",
"Maintain consistent outline weight across objects.",
"No watermark, no unrelated logo, no slide number unless explicitly requested."
]
}
```
@@ -0,0 +1,86 @@
# 手绘技术解释风
**适用场景:**
- 中文技术文章配图
- 技术概念解释
- 课程课件
- 知识卡片
- 产品机制说明
- AI / 软件工程主题分享
- 需要降低理解门槛的复杂概念
**GPT-Image-2 风格 Brief:**
```json
{
"type": "16:9 full-slide PowerPoint image",
"style_name": "手绘技术解释风",
"best_for": "中文技术文章、课程课件、复杂概念解释、知识卡片和软件工程/AI 主题的低压力说明图",
"visual_direction": "clean Chinese handdrawn technical explainer, near-white paper background, thin sketch lines, light pencil hatching, small precise central diagram, restrained pastel markers, lots of whitespace, calm educational tone",
"canvas": {
"aspect_ratio": "16:9",
"background": "near-white paper #FCFBF7, not yellow, no full-page border",
"composition": "small central explanatory diagram with sparse labels, surrounding whitespace, short title and minimal visible text",
"density": "low to moderate; one core idea per slide; avoid dense whiteboard clutter"
},
"color_palette": {
"line": "soft graphite #2F3437 or dark gray ink, thin and slightly irregular",
"accent": "pale blue #BFD7F1, sage green #CFE2D1, light peach #F4C7B8, pale lavender #D8C7EF",
"background": "near-white, clean, not kraft, not cream-heavy",
"rule": "pastel marks are used for emphasis only; keep the page calm and airy"
},
"typography": {
"title": "restrained handwritten Chinese title, medium-large but not poster-sized",
"body": "short handwritten Chinese labels, few words per label, easy to inspect",
"emphasis": "light underline, small bracket, soft marker highlight, or tiny note tag",
"text_quality": "Chinese text must be exact, sparse, and readable; avoid long paragraphs"
},
"layout_patterns": [
"central concept diagram with 3-4 short surrounding labels",
"before-after explanation with two small sketch panels",
"flow diagram with 3 steps and minimal arrows",
"mental model page with one metaphor object and short annotations",
"matrix or decision guide with sparse handwritten notes",
"summary page with one small character or object and three takeaway labels"
],
"layout_usage_rule": "Use layout_blueprints as candidate starting points only. Choose and adapt the composition according to each slide's semantic role; keep visual DNA stable while varying archetypes such as metaphor, process, comparison, matrix, and summary. Avoid repeating the same blueprint on adjacent slides unless it is a deliberate repeated sequence.",
"layout_blueprints": [
{
"name": "small central concept map",
"sections": [
{"position": "top-left", "count": 1, "labels": ["short handwritten title"]},
{"position": "center", "count": 1, "labels": ["small precise concept diagram"]},
{"position": "around center", "count": 4, "labels": ["label 1", "label 2", "label 3", "label 4"]},
{"position": "bottom-right", "count": 1, "labels": ["one-sentence takeaway"]}
]
},
{
"name": "technical before-after",
"sections": [
{"position": "top", "count": 1, "labels": ["short page title"]},
{"position": "left", "count": 1, "labels": ["before sketch panel"]},
{"position": "right", "count": 1, "labels": ["after sketch panel"]},
{"position": "between panels", "count": 1, "labels": ["thin handdrawn arrow"]},
{"position": "bottom", "count": 3, "labels": ["why it matters", "tradeoff", "next step"]}
]
},
{
"name": "one idea teaching card",
"sections": [
{"position": "center-left", "count": 1, "labels": ["metaphor object or tiny engineer/reader character"]},
{"position": "center-right", "count": 3, "labels": ["core idea", "common mistake", "useful rule"]},
{"position": "background", "count": 1, "labels": ["very light pencil hatching and pastel highlight"]}
]
}
],
"visual_elements": {
"allowed": "thin handdrawn arrows, small diagrams, pencil hatching, pastel marker blocks, bracket notes, simple software/AI icons, tiny engineer or reader character at most once",
"avoid": "messy whiteboard frame, marker tray, large cartoon characters, dense handwriting, yellowed paper, decorative stickers, poster-scale title, full-page border, digital UI cards"
},
"rendering_constraints": [
"The slide should feel like a calm handdrawn technical article illustration, not a brainstorming whiteboard.",
"Keep the central drawing small and precise with generous empty space.",
"Use minimal Chinese text and make every visible word correct.",
"No watermark, no unrelated logo, no slide number unless explicitly requested."
]
}
```
@@ -0,0 +1,72 @@
# 手绘白板风
**适用场景:**
- 教学讲解
- 培训课程
- 头脑风暴
- 概念说明
- 技术分享
- 内部研讨
**GPT-Image-2 风格 Brief:**
```json
{
"type": "16:9 full-slide PowerPoint image",
"style_name": "手绘白板风",
"best_for": "概念解释、技术分享、培训课程和需要亲和力的思路拆解",
"visual_direction": "realistic whiteboard explanation slide, marker handwriting, sketched diagrams, friendly teaching atmosphere, authentic whiteboard details",
"canvas": {
"aspect_ratio": "16:9",
"background": "warm off-white whiteboard surface #FAFAF5 with subtle marker smudges",
"composition": "freeform but organized whiteboard layout with title, three concept blocks, arrows, and small doodle illustrations",
"density": "moderate, readable, brainstorming feel without chaos"
},
"color_palette": {
"primary": "black marker for main text",
"accent": "red #E74C3C, blue #3498DB, orange #F39C12 marker annotations",
"support": "green #27AE60, purple #9B59B6, yellow sticky note #FFF9C4",
"rule": "colors should look like real marker ink, not digital neon"
},
"typography": {
"title": "large neat handwritten Chinese marker style",
"body": "clear handwritten marker text, slightly irregular but readable",
"emphasis": "circle, underline, boxed words, sticky-note comments",
"text_quality": "Chinese handwriting must remain accurate and legible"
},
"layout_patterns": [
"three hand-drawn boxes connected by arrows",
"central concept bubble with surrounding notes",
"simple process diagram with sketches and annotations",
"left explanation list plus right hand-drawn diagram"
],
"layout_usage_rule": "Use layout_blueprints as candidate starting points only. Choose and adapt the composition according to each slide's semantic role; avoid repeating the same blueprint on adjacent slides unless it is a deliberate repeated sequence.",
"layout_blueprints": [
{
"name": "whiteboard process diagram",
"sections": [
{"position": "top-left", "count": 1, "labels": ["handwritten title"]},
{"position": "center", "count": 3, "labels": ["step 1 box", "step 2 box", "step 3 box"]},
{"position": "between boxes", "count": 2, "labels": ["hand-drawn arrow", "hand-drawn arrow"]},
{"position": "right edge", "count": 1, "labels": ["sticky-note takeaway"]}
]
},
{
"name": "concept map",
"sections": [
{"position": "center", "count": 1, "labels": ["main concept bubble"]},
{"position": "around center", "count": 4, "labels": ["supporting idea", "risk note", "example sketch", "action item"]},
{"position": "bottom", "count": 1, "labels": ["underlined summary"]}
]
}
],
"visual_elements": {
"allowed": "whiteboard frame, marker tray, colored markers, arrows, boxes, clouds, sticky notes, doodle icons, underlines, circled keywords",
"avoid": "messy illegible handwriting, childish clutter, photoreal people, digital UI cards"
},
"rendering_constraints": [
"The slide should look like a real whiteboard captured cleanly for a presentation.",
"Keep all Chinese text readable despite handwritten style.",
"No watermark, no unrelated logo, no slide number unless explicitly requested."
]
}
```
@@ -0,0 +1,73 @@
# 数据仪表盘风
**适用场景:**
- 数据分析报告
- 业绩展示
- KPI 汇报
- 实时数据展示
- 商业智能BI
- 运营数据看板
**GPT-Image-2 风格 Brief:**
```json
{
"type": "16:9 full-slide PowerPoint image",
"style_name": "数据仪表盘风",
"best_for": "数据密集型汇报、运营分析、KPI 复盘和业务洞察展示",
"visual_direction": "bright modern SaaS analytics dashboard, clean BI interface, lightweight data cards, precise charts, professional and non-oppressive",
"canvas": {
"aspect_ratio": "16:9",
"background": "white #FFFFFF, very light blue-gray #F5F8FC, or mist gray #F8FAFC",
"composition": "dashboard grid with title area, KPI cards, chart panels, and status list",
"density": "medium-high information density with enough card spacing and clear grouping"
},
"color_palette": {
"primary": "data blue #2563EB or #1976D2",
"secondary": "cyan #06B6D4 and soft purple #8B5CF6",
"status": "green #10B981, orange #F59E0B, red #EF4444 used sparingly",
"text": "deep navy #0F172A and neutral gray #64748B",
"rule": "avoid dark control-room backgrounds, heavy neon, and oppressive black panels"
},
"typography": {
"title": "bold clean sans-serif, dashboard header style",
"numbers": "large tabular numerals for KPI values",
"labels": "small but readable sans-serif chart labels",
"text_quality": "Chinese labels and KPI names must be exact and legible"
},
"layout_patterns": [
"top KPI strip with 3-4 cards and trend arrows",
"main area split into workflow cards and a line chart",
"lower area with donut chart, bar chart, and recent activity table",
"large central insight card surrounded by supporting metrics"
],
"layout_usage_rule": "Use layout_blueprints as candidate starting points only. Choose and adapt the composition according to each slide's semantic role; avoid repeating the same blueprint on adjacent slides unless it is a deliberate repeated sequence.",
"layout_blueprints": [
{
"name": "SaaS BI overview",
"sections": [
{"position": "top-left", "count": 1, "labels": ["dashboard title and subtitle"]},
{"position": "top-right", "count": 4, "labels": ["KPI card 1", "KPI card 2", "KPI card 3", "KPI card 4"]},
{"position": "middle-left", "count": 3, "labels": ["process card 1", "process card 2", "process card 3"]},
{"position": "middle-right", "count": 1, "labels": ["line chart panel"]},
{"position": "bottom", "count": 3, "labels": ["donut chart", "bar chart", "recent records table"]}
]
},
{
"name": "single insight dashboard",
"sections": [
{"position": "center", "count": 1, "labels": ["large insight card"]},
{"position": "surrounding", "count": 4, "labels": ["supporting metric", "trend sparkline", "status list", "risk indicator"]}
]
}
],
"visual_elements": {
"allowed": "white cards, soft shadows, pale borders, line charts, bar charts, progress rings, KPI cards, status dots, trend arrows, mini sparklines",
"avoid": "dark monitoring wall, dense cyberpunk glow, unreadable tiny table text, random numbers without structure"
},
"rendering_constraints": [
"The slide should look like a polished SaaS analytics product screenshot adapted for presentation.",
"Charts should be visually plausible and organized, even when illustrative.",
"No watermark, no unrelated logo, no slide number unless explicitly requested."
]
}
```
@@ -0,0 +1,87 @@
# 清爽专业风
**适用场景:**
- 毕业答辩
- 工作总结
- 工作 review
- 技术分享
- 项目复盘
- 晋升述职
- 阶段性成果汇报
**GPT-Image-2 风格 Brief:**
```json
{
"type": "16:9 full-slide PowerPoint image",
"style_name": "清爽专业风",
"best_for": "毕业答辩、工作总结、工作 review、技术分享、项目复盘和晋升述职等需要清晰表达过程、成果、问题和下一步的场景",
"visual_direction": "clean modern professional deck, calm technical presentation, structured evidence-driven layout, readable pragmatic visual system, light background with crisp hierarchy",
"canvas": {
"aspect_ratio": "16:9",
"background": "warm white #FFFFFF, light gray #F8FAFC, or very pale blue #F6F9FF",
"composition": "title zone, structured content zone, evidence/diagram zone, and concise takeaway zone",
"density": "medium information density, enough room for technical details without visual clutter"
},
"color_palette": {
"primary": "professional blue #2563EB or slate blue #334155",
"secondary": "calm teal #0F766E or muted cyan #0891B2",
"accent": "soft amber #F59E0B for highlights and risk/attention notes",
"neutral": "slate gray #475569, light border #E2E8F0, pale card background #F8FAFC",
"rule": "use restrained professional colors; avoid playful neon, luxury gold, or heavy dark backgrounds"
},
"typography": {
"title": "clear bold sans-serif, report-like and authoritative",
"body": "clean sans-serif, strong hierarchy, left-aligned for readability",
"labels": "small but readable labels for timeline, evidence, metrics, and code/process diagrams",
"text_quality": "Chinese text must be exact, readable, and suitable for formal reporting"
},
"layout_patterns": [
"problem-process-result-next steps",
"timeline plus milestone cards",
"technical architecture diagram with key takeaways",
"summary dashboard with achievements, issues, learnings, and plan",
"defense slide with research question, method, evidence, and conclusion"
],
"layout_usage_rule": "Use layout_blueprints as candidate starting points only. Choose and adapt the composition according to each slide's semantic role; avoid repeating the same blueprint on adjacent slides unless it is a deliberate repeated sequence.",
"layout_blueprints": [
{
"name": "work review summary",
"sections": [
{"position": "top", "count": 1, "labels": ["slide title and context subtitle"]},
{"position": "left column", "count": 3, "labels": ["目标", "完成情况", "关键结果"]},
{"position": "right column", "count": 2, "labels": ["问题与风险", "下一步计划"]},
{"position": "bottom", "count": 1, "labels": ["one-sentence takeaway"]}
]
},
{
"name": "technical sharing flow",
"sections": [
{"position": "top-left", "count": 1, "labels": ["topic title and motivation"]},
{"position": "center", "count": 1, "labels": ["architecture / workflow / method diagram"]},
{"position": "right", "count": 3, "labels": ["核心机制", "实践经验", "注意事项"]},
{"position": "bottom", "count": 3, "labels": ["before", "after", "impact"]}
]
},
{
"name": "graduation defense evidence slide",
"sections": [
{"position": "top", "count": 1, "labels": ["研究问题 / 答辩主题"]},
{"position": "left", "count": 1, "labels": ["方法路线图"]},
{"position": "center", "count": 2, "labels": ["实验/项目证据", "关键数据"]},
{"position": "right", "count": 1, "labels": ["结论与贡献"]},
{"position": "bottom", "count": 1, "labels": ["限制与后续工作"]}
]
}
],
"visual_elements": {
"allowed": "timeline, checklist, progress bars, architecture blocks, process arrows, evidence cards, metric badges, issue/risk callouts, code-like panels, simple icons",
"avoid": "overly decorative poster layout, random stock photos, cute stickers, dense unreadable tables, exaggerated marketing style"
},
"rendering_constraints": [
"The slide should look suitable for a real workplace review, thesis defense, or technical sharing session.",
"Prioritize clarity, evidence, and logical flow over decoration.",
"All diagrams and labels should feel purposeful and related to the slide content.",
"No watermark, no unrelated logo, no slide number unless explicitly requested."
]
}
```
@@ -0,0 +1,72 @@
# 温暖手工风
**适用场景:**
- 儿童教育
- 文化活动
- 手工艺展示
- 温馨主题
- 亲子活动
- 艺术工作坊
- 社区公益项目
**GPT-Image-2 风格 Brief:**
```json
{
"type": "16:9 full-slide PowerPoint image",
"style_name": "温暖手工风",
"best_for": "教育、公益、文化、成长、社区和需要温度感的主题演示",
"visual_direction": "warm handmade paper collage slide, tactile paper craft, gentle human-centered storytelling, cozy scrapbook composition",
"canvas": {
"aspect_ratio": "16:9",
"background": "warm cream paper #F5F1E8 or pale wood #E8DCC8 with subtle paper fibers",
"composition": "paper cutout title area, 3 paper note cards for key points, gentle illustration accents",
"density": "airy and comforting, generous breathing room"
},
"color_palette": {
"primary": "warm brown #A67C52 and deep cocoa #5C4033 for text",
"soft_colors": "dusty pink #F5C4B8, sage green #B8D4A8, sky blue #A8D8EA, sunset orange #FFA574, lavender #C9B1D0",
"surface": "cream, handmade paper, kraft paper, pastel sticky notes",
"rule": "all colors should feel soft, matte, and paper-like"
},
"typography": {
"title": "rounded handwritten or friendly display style",
"body": "clear friendly handwritten or rounded sans-serif Chinese text",
"emphasis": "paper labels, stitched tags, small handwritten captions",
"text_quality": "Chinese text must be accurate and legible"
},
"layout_patterns": [
"three torn-paper cards aligned naturally across the slide",
"scrapbook title ribbon with small illustrated stickers",
"central handmade diagram with taped labels",
"soft paper collage with one warm illustration and supporting notes"
],
"layout_usage_rule": "Use layout_blueprints as candidate starting points only. Choose and adapt the composition according to each slide's semantic role; avoid repeating the same blueprint on adjacent slides unless it is a deliberate repeated sequence.",
"layout_blueprints": [
{
"name": "paper note cards",
"sections": [
{"position": "top", "count": 1, "labels": ["torn-paper title ribbon"]},
{"position": "center", "count": 3, "labels": ["paper card 1", "paper card 2", "paper card 3"]},
{"position": "corners", "count": 4, "labels": ["small sticker", "tape strip", "plant doodle", "paper clip"]}
]
},
{
"name": "scrapbook story",
"sections": [
{"position": "left", "count": 1, "labels": ["warm illustration or paper collage scene"]},
{"position": "right", "count": 3, "labels": ["key point note", "example note", "takeaway note"]},
{"position": "bottom", "count": 1, "labels": ["handwritten closing line"]}
]
}
],
"visual_elements": {
"allowed": "paper texture, torn edges, tape, paper clips, buttons, watercolor blocks, simple doodles, plant stickers, soft shadows",
"avoid": "glossy plastic look, corporate sharp edges, harsh black text, neon colors, overly childish clutter"
},
"rendering_constraints": [
"The slide should feel handmade but still presentation-ready.",
"Paper shadows and textures should be subtle and not reduce text legibility.",
"No watermark, no unrelated logo, no slide number unless explicitly requested."
]
}
```
@@ -0,0 +1,86 @@
# 电子墨水杂志风
**适用场景:**
- 线下分享
- 行业内部讲话
- AI / 科技产品发布
- Demo day
- 个人观点型演讲
- 非虚构叙事
- 需要强节奏感的主题演讲
**GPT-Image-2 风格 Brief:**
```json
{
"type": "16:9 full-slide PowerPoint image",
"style_name": "电子墨水杂志风",
"best_for": "线下演讲、观点分享、AI/科技发布、非虚构叙事和需要强个人表达的横向演示",
"visual_direction": "electronic ink editorial presentation, premium magazine layout, serif headline, sans-serif body, monospace metadata, restrained WebGL-like ink flow background only as subtle texture, strong hero/non-hero rhythm",
"canvas": {
"aspect_ratio": "16:9",
"background": "off-white #F7F4EA, ink black #111111, deep indigo #1E2A78, forest ink #163B2F, kraft paper #D8C3A5, or dune sand #D9C29E depending on theme",
"composition": "editorial grid with strong margins, magazine-like hierarchy, hero pages alternating with quieter content pages",
"density": "moderate to low information density, designed for stage readability and narrative pacing"
},
"color_palette": {
"base": "paper-like light background or deep ink background",
"primary": "ink black, off-white, or deep indigo",
"accent": "one restrained accent such as vermilion, electric blue, forest green, or sand gold",
"metadata": "muted gray or low-contrast monochrome",
"rule": "use a curated theme palette; avoid random custom colors and decorative gradients"
},
"typography": {
"title": "large editorial serif Chinese headline or elegant high-contrast display type",
"body": "clean sans-serif, short paragraphs or compact bullets",
"metadata": "small monospace labels for chapter, date, source, index, or tags",
"text_quality": "Chinese text must be exact, readable, and typeset like a magazine spread"
},
"layout_patterns": [
"hero cover with oversized serif headline and subtle ink-flow background",
"chapter divider with one provocative sentence and metadata strip",
"data poster with one huge number, footnote, and tiny supporting labels",
"left text right image with image treated as editorial photography or abstract ink plate",
"big quote page with pull quote, source line, and large whitespace",
"before-after comparison with two editorial columns"
],
"layout_usage_rule": "Use layout_blueprints as candidate starting points only. Choose and adapt the composition according to each slide's semantic role; alternate hero and non-hero pages to create rhythm, and avoid repeating the same blueprint on adjacent slides unless it is a deliberate repeated sequence.",
"layout_blueprints": [
{
"name": "hero editorial opener",
"sections": [
{"position": "left or center", "count": 1, "labels": ["oversized serif headline"]},
{"position": "top or side edge", "count": 3, "labels": ["chapter label", "date/source", "short metadata"]},
{"position": "background", "count": 1, "labels": ["subtle ink-flow or paper texture"]},
{"position": "bottom", "count": 1, "labels": ["one-line thesis"]}
]
},
{
"name": "magazine argument spread",
"sections": [
{"position": "top-left", "count": 1, "labels": ["slide title"]},
{"position": "left column", "count": 2, "labels": ["argument block", "evidence block"]},
{"position": "right half", "count": 1, "labels": ["editorial image, diagram, or abstract visual plate"]},
{"position": "bottom", "count": 1, "labels": ["monospace metadata strip"]}
]
},
{
"name": "data broadsheet",
"sections": [
{"position": "center-left", "count": 1, "labels": ["huge number or keyword"]},
{"position": "right column", "count": 3, "labels": ["supporting fact 1", "supporting fact 2", "supporting fact 3"]},
{"position": "bottom-left", "count": 1, "labels": ["source / caveat / time range"]}
]
}
],
"visual_elements": {
"allowed": "editorial rules, thin dividers, paper grain, subtle ink-flow texture, cropped photography zones, metadata strips, chapter marks, pull quotes, huge numerals",
"avoid": "template-like cards, shiny corporate gradients, cute illustrations, dense tables, dashboard overload, generic stock-photo collage"
},
"rendering_constraints": [
"The slide should feel like an electronic magazine page adapted for a stage presentation.",
"Use large readable typography and strong whitespace.",
"Do not turn every page into a hero cover; alternate page intensity across the deck.",
"No watermark, no unrelated logo, no slide number unless explicitly requested."
]
}
```
+2
View File
@@ -0,0 +1,2 @@
python-pptx>=1.0.2
Pillow>=10.0.0
+415
View File
@@ -0,0 +1,415 @@
#!/usr/bin/env python3
"""
PPT 组装脚本
将一个目录中的幻灯片图片组装成 PowerPoint 演示文稿。
每张图片会被插入为一页幻灯片,充满整个页面。
"""
import argparse
import os
import re
import sys
import tempfile
from typing import Dict, List, Optional
def get_slide_images(ppt_project_dir: str) -> List[str]:
"""
获取幻灯片图片文件列表,按文件名排序
Args:
ppt_project_dir: PPT 项目目录(包含 origin_image 子目录)
Returns:
按顺序排列的图片文件路径列表
"""
# 从 origin_image 子目录读取
origin_image_dir = os.path.join(ppt_project_dir, "origin_image")
if not os.path.exists(origin_image_dir):
print(f"错误origin_image 目录不存在: {origin_image_dir}")
return []
print(f"从 origin_image 目录读取图片: {origin_image_dir}")
# 支持的图片格式
image_extensions = {'.png', '.jpg', '.jpeg', '.gif', '.bmp'}
# 只获取正式幻灯片图片,避免 sample_slide.png、草稿图或参考图被误装入 PPT。
slide_name_pattern = re.compile(r"^slide_(\d+)\.(png|jpe?g|gif|bmp)$", re.IGNORECASE)
image_files = []
for file in os.listdir(origin_image_dir):
file_path = os.path.join(origin_image_dir, file)
if os.path.isfile(file_path):
ext = os.path.splitext(file)[1].lower()
if ext in image_extensions and slide_name_pattern.match(file):
image_files.append(file_path)
# 按文件名排序
image_files.sort()
return image_files
def load_speaker_notes(ppt_project_dir: str) -> Dict[int, str]:
"""
从 speech.md 读取每页演讲备注。
支持以下标题格式:
- ## Slide 1: 标题
- ### Slide 1: 标题
- ## 第 1 页:标题
"""
speech_path = os.path.join(ppt_project_dir, "speech.md")
if not os.path.exists(speech_path):
return {}
with open(speech_path, "r", encoding="utf-8") as f:
content = f.read()
notes: Dict[int, str] = {}
current_slide: Optional[int] = None
current_lines: List[str] = []
heading_pattern = re.compile(r"^#{2,4}\s*(?:Slide\s*(\d+)|第\s*(\d+)\s*页)\b.*$", re.IGNORECASE)
def flush_current() -> None:
if current_slide is None:
return
text = "\n".join(current_lines).strip()
if text:
notes[current_slide] = text
for line in content.splitlines():
match = heading_pattern.match(line.strip())
if match:
flush_current()
current_slide = int(match.group(1) or match.group(2))
current_lines = []
continue
if current_slide is not None:
current_lines.append(line)
flush_current()
return notes
def compress_image_if_needed(
image_path: str,
max_size_mb: float = 2.0,
quality_step: int = 5
) -> Optional[str]:
"""
如果图片超过指定大小,压缩图片并返回临时文件路径
Args:
image_path: 原始图片路径
max_size_mb: 最大文件大小MB
quality_step: 每次降低的质量步进
Returns:
str: 如果需要压缩,返回临时文件路径;否则返回 None
"""
max_size_bytes = max_size_mb * 1024 * 1024
# 检查原始文件大小
file_size = os.path.getsize(image_path)
if file_size <= max_size_bytes:
# 不需要压缩
return None
print(f" 图片大小 {file_size / 1024 / 1024:.2f}MB需要压缩...")
try:
from PIL import Image
# 打开图片
img = Image.open(image_path)
# 转换 RGBA 到 RGB如果需要保存为 JPEG
if img.mode in ('RGBA', 'LA', 'P'):
background = Image.new('RGB', img.size, (255, 255, 255))
if img.mode == 'P':
img = img.convert('RGBA')
background.paste(img, mask=img.split()[-1] if img.mode in ('RGBA', 'LA') else None)
img = background
# 创建临时文件
temp_fd, temp_path = tempfile.mkstemp(suffix='.jpg')
os.close(temp_fd)
# 从高质量开始尝试压缩
quality = 95
while quality > 20:
img.save(temp_path, 'JPEG', quality=quality, optimize=True)
compressed_size = os.path.getsize(temp_path)
if compressed_size <= max_size_bytes:
print(f" 压缩成功: {compressed_size / 1024 / 1024:.2f}MB (质量: {quality})")
return temp_path
quality -= quality_step
# 如果还是太大,尝试调整尺寸
print(f" 质量压缩不足,尝试缩小尺寸...")
scale = 0.9
while scale > 0.3:
new_width = int(img.width * scale)
new_height = int(img.height * scale)
resized_img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
resized_img.save(temp_path, 'JPEG', quality=85, optimize=True)
compressed_size = os.path.getsize(temp_path)
if compressed_size <= max_size_bytes:
print(f" 压缩成功: {compressed_size / 1024 / 1024:.2f}MB (缩放: {scale:.0%})")
return temp_path
scale -= 0.1
# 实在压不下去了,返回最后的结果
print(f" 警告:无法压缩到 {max_size_mb}MB 以下,使用最小尺寸版本")
return temp_path
except ImportError:
print("错误:未安装 Pillow 库")
print("请运行: python3 -m venv ~/.codex/skills/codex-ppt/.venv")
print("然后运行: ~/.codex/skills/codex-ppt/.venv/bin/python -m pip install -r ~/.codex/skills/codex-ppt/requirements.txt")
return None
except Exception as e:
print(f" 警告:图片压缩失败: {e}")
if 'temp_path' in locals() and os.path.exists(temp_path):
os.remove(temp_path)
return None
def create_presentation(
image_files: List[str],
output_path: str,
aspect_ratio: str = "16:9",
speaker_notes: Optional[Dict[int, str]] = None,
) -> bool:
"""
创建 PowerPoint 演示文稿
Args:
image_files: 幻灯片图片文件列表
output_path: 输出 PPT 文件路径
aspect_ratio: 幻灯片宽高比16:9 或 4:3
Returns:
bool: 成功返回 True失败返回 False
"""
try:
try:
from pptx import Presentation
from pptx.util import Inches
except ImportError:
print("错误:未安装 python-pptx 库")
print("请运行: python3 -m venv ~/.codex/skills/codex-ppt/.venv")
print("然后运行: ~/.codex/skills/codex-ppt/.venv/bin/python -m pip install -r ~/.codex/skills/codex-ppt/requirements.txt")
return False
# 创建演示文稿
prs = Presentation()
# 设置幻灯片尺寸
if aspect_ratio == "16:9":
prs.slide_width = Inches(10)
prs.slide_height = Inches(5.625)
elif aspect_ratio == "4:3":
prs.slide_width = Inches(10)
prs.slide_height = Inches(7.5)
else:
print(f"警告:不支持的宽高比 {aspect_ratio},使用默认值 16:9")
prs.slide_width = Inches(10)
prs.slide_height = Inches(5.625)
speaker_notes = speaker_notes or {}
# 添加每一页幻灯片
temp_files_to_cleanup = []
for i, image_path in enumerate(image_files, 1):
if not os.path.exists(image_path):
print(f"警告:图片文件不存在: {image_path}")
continue
# 压缩图片(如果需要)
compressed_path = compress_image_if_needed(image_path, max_size_mb=2.0)
# 使用压缩后的图片或原图
image_to_use = compressed_path if compressed_path else image_path
# 记录临时文件以便后续清理
if compressed_path:
temp_files_to_cleanup.append(compressed_path)
# 使用空白布局(索引 6
blank_slide_layout = prs.slide_layouts[6]
slide = prs.slides.add_slide(blank_slide_layout)
# 将图片添加到幻灯片,充满整个页面
slide.shapes.add_picture(
image_to_use,
left=0,
top=0,
width=prs.slide_width,
height=prs.slide_height
)
note_text = speaker_notes.get(i)
if note_text:
notes_frame = slide.notes_slide.notes_text_frame
notes_frame.clear()
notes_frame.text = note_text
print(f"✓ 已添加第 {i} 页: {os.path.basename(image_path)}")
# 确保输出目录存在
output_dir = os.path.dirname(output_path)
if output_dir and not os.path.exists(output_dir):
os.makedirs(output_dir)
# 保存演示文稿
prs.save(output_path)
print(f"\n✓ PPT 文件已保存: {output_path}")
print(f" 总页数: {len(image_files)}")
if speaker_notes:
matched_notes = sum(1 for i in range(1, len(image_files) + 1) if speaker_notes.get(i))
print(f" 已写入备注: {matched_notes}/{len(image_files)}")
# 清理临时文件
for temp_file in temp_files_to_cleanup:
try:
if os.path.exists(temp_file):
os.remove(temp_file)
except Exception as e:
print(f"警告:清理临时文件失败 {temp_file}: {e}")
return True
except Exception as e:
print(f"错误:创建 PPT 失败: {e}")
import traceback
traceback.print_exc()
# 即使失败也要清理临时文件
if 'temp_files_to_cleanup' in locals():
for temp_file in temp_files_to_cleanup:
try:
if os.path.exists(temp_file):
os.remove(temp_file)
except:
pass
return False
def main():
parser = argparse.ArgumentParser(
description='将幻灯片图片组装成 PowerPoint 演示文稿',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='''
示例用法:
# 基本用法
# 会在 /path/to/base/ 下查找 MyPresentation/ 文件夹
# 从 MyPresentation/origin_image/ 读取图片
# 将 PPT 保存为 MyPresentation/MyPresentation.pptx
python assemble_ppt.py /path/to/base/ MyPresentation.pptx
# 指定 4:3 宽高比
python assemble_ppt.py /path/to/base/ MyPresentation.pptx --aspect-ratio 4:3
# 只初始化目录,不生成 PPT
python assemble_ppt.py /path/to/base/ MyPresentation.pptx --init
文件夹结构要求:
/path/to/base/MyPresentation/
├── origin_image/
│ ├── slide_01.png
│ ├── slide_02.png
│ └── ...
├── speech.md (可选,将写入 PPT 备注)
└── MyPresentation.pptx (将在此生成)
注意:
- 图片文件必须放在 origin_image 子目录中
- 只会读取 slide_01.png、slide_02.png 这类正式图片,其他图片会被忽略
- 图片文件按文件名排序
- 建议图片文件命名为: slide_01.png, slide_02.png, ...
- 每张图片会充满整个幻灯片页面
- 如果项目目录下存在 speech.md会按 Slide N 标题写入每页备注
'''
)
parser.add_argument('base_dir', help='基础目录PPT 项目文件夹的父目录)')
parser.add_argument('output', help='输出 PPT 文件名 (.pptx)')
parser.add_argument('--aspect-ratio', '--ar',
choices=['16:9', '4:3'],
default='16:9',
help='幻灯片宽高比 (默认: 16:9)')
parser.add_argument('--init',
action='store_true',
help='只创建 PPT 项目目录和 origin_image 子目录,不生成 PPT')
args = parser.parse_args()
# 确保输出文件有 .pptx 扩展名
output_filename = args.output
if not output_filename.lower().endswith('.pptx'):
output_filename += '.pptx'
# 获取 PPT 名称(不含扩展名)
ppt_name = os.path.splitext(os.path.basename(output_filename))[0]
# 构建 PPT 项目目录
ppt_project_dir = os.path.join(args.base_dir, ppt_name)
origin_image_dir = os.path.join(ppt_project_dir, "origin_image")
if args.init:
os.makedirs(origin_image_dir, exist_ok=True)
print(f"✓ PPT 项目目录已准备好: {ppt_project_dir}")
print(f"✓ 幻灯片图片目录已准备好: {origin_image_dir}")
sys.exit(0)
if not os.path.exists(ppt_project_dir):
print(f"错误PPT 项目目录不存在: {ppt_project_dir}")
print("如需初始化目录,请添加 --init 参数")
sys.exit(1)
if not os.path.exists(origin_image_dir):
print(f"错误origin_image 目录不存在: {origin_image_dir}")
print("如需初始化目录,请添加 --init 参数")
sys.exit(1)
# 设置输出路径
output_path = os.path.join(ppt_project_dir, output_filename)
# 获取幻灯片图片
print(f"正在扫描 PPT 项目目录: {ppt_project_dir}")
image_files = get_slide_images(ppt_project_dir)
if not image_files:
print("错误:未找到任何图片文件")
print("支持的格式: .png, .jpg, .jpeg, .gif, .bmp")
print(f"\n请将幻灯片图片放置在: {origin_image_dir}/")
sys.exit(1)
print(f"找到 {len(image_files)} 张幻灯片图片\n")
speaker_notes = load_speaker_notes(ppt_project_dir)
if speaker_notes:
print(f"找到 {len(speaker_notes)} 页备注: {os.path.join(ppt_project_dir, 'speech.md')}\n")
# 创建演示文稿
print(f"正在创建 PPT (宽高比: {args.aspect_ratio})...")
print("-" * 50)
success = create_presentation(image_files, output_path, args.aspect_ratio, speaker_notes)
sys.exit(0 if success else 1)
if __name__ == '__main__':
main()