Initial codex-ppt skill
@@ -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
|
||||
@@ -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.
|
||||
@@ -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。
|
||||
|
||||

|
||||
|
||||
## 风格示例
|
||||
|
||||
以下是已生成预览图的风格,示例图均由 `gpt-image-2` 生成,用于帮助用户在开始制作前选择视觉方向。
|
||||
|
||||
| 清爽专业风 | 创意杂志风 |
|
||||
| --- | --- |
|
||||
|  |  |
|
||||
|
||||
| 电子墨水杂志风 | 数据仪表盘风 |
|
||||
| --- | --- |
|
||||
|  |  |
|
||||
|
||||
| 复古扁平插画风 | 手绘技术解释风 |
|
||||
| --- | --- |
|
||||
|  |  |
|
||||
|
||||
| 手绘白板风 | 温暖手工风 |
|
||||
| --- | --- |
|
||||
|  |  |
|
||||
|
||||
## 输出结构
|
||||
|
||||
每个 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
|
||||
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 2.1 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 2.4 MiB |
|
After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 2.3 MiB |
|
After Width: | Height: | Size: 2.3 MiB |
@@ -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."
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,2 @@
|
||||
python-pptx>=1.0.2
|
||||
Pillow>=10.0.0
|
||||
@@ -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()
|
||||