AI 日历:两段式工作流实现「台历自由」

2026年2月4日

直接让 AI 画日历,往往是大型翻车现场:不是日期错乱(2 月 30 号?),就是排版像被挤过的牙膏。

> 最稳的解法:把“确定性”交给代码,把“想象力”交给模型;再把“排版落地”也交给代码。


0. TL;DR

0.1 3 分钟复刻(最推荐的路径)

把下面 3 步照抄执行,就能在本地或共绩算力平台批量生成台历成片(输出在 测试comfyui_副本/demo/comfyui-zimage-demo/outputs/):

0.1.1 在共绩算力平台跑(预制镜像)

  1. 共绩算力平台 创建实例/容器

  2. 镜像选择:预制 ComfyUI(生图/Z-Image)镜像(平台预制镜像)

  3. 打开/映射端口(通常 8188),得到一个可访问的 ComfyUI 地址,例如:

    • https://<your-deployment>-8188.<domain>
  4. 把本项目放进实例(git clone 或上传)

  5. 运行:

Terminal window
INSECURE_TLS=1 \
COMFY_BASE_URL="https://<your-comfyui-8188-url>" \
python3 "测试comfyui_副本/demo/comfyui-zimage-demo/batch_generate_calendars_composited.py" --month 2026-01 --force

0.1.2 在本地跑(你已有 ComfyUI 服务也行)

只要你能访问一个 ComfyUI(8188)服务,把 COMFY_BASE_URL 指过去即可(同上命令)。

1. 为什么直接让模型画日历会翻车?

1.1 根因:硬逻辑 + 硬结构 + 软能力错配

结论:想要“可印刷、可交付”的台历页,日历数字不能交给模型。

2. 两段式工作流(基础版):脚本骨架 + Prompt 注入

2.1 第 1 段:脚本先行(把逻辑交给代码)

目录:ai相关好玩的/ai日历/

示例:

Terminal window
python3 "ai相关好玩的/ai日历/generate_calendar.py" --month 2026-01

2.2 第 2 段:咒语拼合(把想象交给模型)

示例:

Terminal window
python3 "ai相关好玩的/ai日历/build_prompt.py" \
--month 2026-01 \
--theme "森林里的小精灵王国" \
--template "ai相关好玩的/ai日历/templates/prompt_template_3d_clay.md"

> 但:只靠提示词约束模型“严格排版”,依旧可能出现 星期乱写 / 多写数字 / 日期错位

想要“更惊艳 + 零翻车”,建议上第 3 段。


3. 三段式工作流(推荐):背景交给模型,日历排版交给代码

3.1 思想:模型不要写字

模型的任务被压缩成一句:只画背景,右侧留白,禁止文字/数字

日期排版由代码完成,所以“日期正确性”变成了工程问题,不再是玄学问题。

3.2 你会用到的脚本

目录:ai相关好玩的/ai日历/

依赖:Pillow(你当前环境已可用)。


4. ComfyUI 批量出图(推荐命令)

目录:测试comfyui_副本/demo/comfyui-zimage-demo/

ComfyUI(Z-Image)端点示例:https://deployment-318-klu0niut-8188.550w.link
若遇到自签证书:设置 INSECURE_TLS=1(仅测试用)。

4.0 在共绩算力平台上跑(预制镜像复刻版)

如果你想“最省心地复刻”,推荐直接用 共绩算力平台 的预制镜像:

  1. 创建实例/容器

  2. 镜像选择:预制 ComfyUI(生图/Z-Image)镜像

  3. 暴露端口 8188,拿到你的 ComfyUI 访问地址(下文用 https://<your-comfyui-8188-url> 表示)

  4. 将本项目放进实例(git clone 或上传)

  5. 运行下面 4.1 的命令即可

兼容性说明:脚本只调用 ComfyUI 标准 API:/prompt + /history + /view。镜像的启动方式不同没关系,只要 8188 可访问即可。

4.1 批量生成:背景 + 代码排版叠加(日期永远正确)

Terminal window
INSECURE_TLS=1 \
COMFY_BASE_URL="https://<your-comfyui-8188-url>" \
python3 "测试comfyui_副本/demo/comfyui-zimage-demo/batch_generate_calendars_composited.py" \
--month 2026-01 --force

输出:


5. 画廊(精选):更惊艳,且日期严格正确

> 以下成片的日历数字与网格 全部由代码排版叠加,不会出现星期乱写/多写数字/日期错位。

5.1 高端品牌 KV(质感静物装置)

<details>

<summary>展开查看背景 Prompt(可复用)</summary>

文件:测试 comfyui_副本/demo/comfyui-zimage-demo/outputs/_calendar_prompts_composited/cal_bg_luxury_a_2026-01.md

</details>

5.2 赛博霓虹(雨夜电影感)

<details>

<summary>展开查看背景 Prompt(可复用)</summary>

文件:测试 comfyui_副本/demo/comfyui-zimage-demo/outputs/_calendar_prompts_composited/cal_bg_cyber_a_2026-01.md

</details>

5.3 纸雕剪纸(工艺感爆棚)

<details>

<summary>展开查看背景 Prompt(可复用)</summary>

文件:测试 comfyui_副本/demo/comfyui-zimage-demo/outputs/_calendar_prompts_composited/cal_bg_papercut_a_2026-01.md

</details>

5.4 国风水墨(高级留白)

<details>

<summary>展开查看背景 Prompt(可复用)</summary>

文件:测试 comfyui_副本/demo/comfyui-zimage-demo/outputs/_calendar_prompts_composited/cal_bg_ink_a_2026-01.md

</details>

5.5 超现实拼贴(杂志封面感)

<details>

<summary>展开查看背景 Prompt(可复用)</summary>

文件:测试 comfyui_副本/demo/comfyui-zimage-demo/outputs/_calendar_prompts_composited/cal_bg_surreal_a_2026-01.md

</details>

5.6 强风格艺术(更“出片”)

下面这 6 张属于“强风格艺术”,更容易做到一眼惊艳;并且日历区域完全由代码排版叠加,所以不会出现日期/星期乱写。

5.6.1 故障艺术 Glitch

<details>

<summary>展开查看背景 Prompt(完整)</summary>

你是顶级视觉设计师与插画导演。你的任务是:生成一张**可印刷的横版台历背景图**,只负责“主视觉/背景”,不负责日期排版。
## 变量
- 月份:2026-01
- 主题(可覆写):强风格故障艺术:RGB 分离、扫描线、像素撕裂、数据扭曲的抽象画面(无文字无数字,右侧留白)
## 关键约束(必须遵守)
- 这张图只画“背景/主视觉”,**禁止生成任何文字**(包括月份、招牌字、标题、UI 小字、印章字、海报字)。
- **禁止生成任何数字**(避免误伤日历数字)。
- 在画面右侧预留一个干净区域用于后续代码叠加日历:右侧约 40% 为浅色/低纹理/高对比底,留白充足。
- 主视觉集中在左侧约 60%,构图高级、干净、可印刷(低噪点、边缘清晰)。
## 风格方向
- 允许自由发挥,但要“第一眼高级”:电影级光影、材质真实、构图克制、色彩统一。
- 主题解读:围绕“强风格故障艺术:RGB 分离、扫描线、像素撕裂、数据扭曲的抽象画面(无文字无数字,右侧留白)”构建一致世界观与道具元素。
## 输出要求
- 只输出最终成图,不要解释文字。

</details>

5.6.2 波普漫画 Pop Art

<details>

<summary>展开查看背景 Prompt(完整)</summary>

你是顶级视觉设计师与插画导演。你的任务是:生成一张**可印刷的横版台历背景图**,只负责“主视觉/背景”,不负责日期排版。
## 变量
- 月份:2026-01
- 主题(可覆写):强风格波普漫画:粗黑描边、Ben-Day 网点、互补色块、高级平面构图(无文字无数字,右侧留白)
## 关键约束(必须遵守)
- 这张图只画“背景/主视觉”,**禁止生成任何文字**(包括月份、招牌字、标题、UI 小字、印章字、海报字)。
- **禁止生成任何数字**(避免误伤日历数字)。
- 在画面右侧预留一个干净区域用于后续代码叠加日历:右侧约 40% 为浅色/低纹理/高对比底,留白充足。
- 主视觉集中在左侧约 60%,构图高级、干净、可印刷(低噪点、边缘清晰)。
## 风格方向
- 允许自由发挥,但要“第一眼高级”:电影级光影、材质真实、构图克制、色彩统一。
- 主题解读:围绕“强风格波普漫画:粗黑描边、Ben-Day 网点、互补色块、高级平面构图(无文字无数字,右侧留白)”构建一致世界观与道具元素。
## 输出要求
- 只输出最终成图,不要解释文字。

</details>

5.6.3 黑白版画 Linocut

<details>

<summary>展开查看背景 Prompt(完整)</summary>

你是顶级视觉设计师与插画导演。你的任务是:生成一张**可印刷的横版台历背景图**,只负责“主视觉/背景”,不负责日期排版。
## 变量
- 月份:2026-01
- 主题(可覆写):强风格黑白版画:木刻/胶版线条、粗犷刀痕、强对比留白(无文字无数字,右侧留白)
## 关键约束(必须遵守)
- 这张图只画“背景/主视觉”,**禁止生成任何文字**(包括月份、招牌字、标题、UI 小字、印章字、海报字)。
- **禁止生成任何数字**(避免误伤日历数字)。
- 在画面右侧预留一个干净区域用于后续代码叠加日历:右侧约 40% 为浅色/低纹理/高对比底,留白充足。
- 主视觉集中在左侧约 60%,构图高级、干净、可印刷(低噪点、边缘清晰)。
## 风格方向
- 允许自由发挥,但要“第一眼高级”:电影级光影、材质真实、构图克制、色彩统一。
- 主题解读:围绕“强风格黑白版画:木刻/胶版线条、粗犷刀痕、强对比留白(无文字无数字,右侧留白)”构建一致世界观与道具元素。
## 输出要求
- 只输出最终成图,不要解释文字。

</details>

5.6.4 蒸汽波 Vaporwave

<details>

<summary>展开查看背景 Prompt(完整)</summary>

你是顶级视觉设计师与插画导演。你的任务是:生成一张**可印刷的横版台历背景图**,只负责“主视觉/背景”,不负责日期排版。
## 变量
- 月份:2026-01
- 主题(可覆写):强风格蒸汽波:粉紫渐变天空、复古 3D 几何、镜面地面、霓虹光晕(无文字无数字,右侧留白)
## 关键约束(必须遵守)
- 这张图只画“背景/主视觉”,**禁止生成任何文字**(包括月份、招牌字、标题、UI 小字、印章字、海报字)。
- **禁止生成任何数字**(避免误伤日历数字)。
- 在画面右侧预留一个干净区域用于后续代码叠加日历:右侧约 40% 为浅色/低纹理/高对比底,留白充足。
- 主视觉集中在左侧约 60%,构图高级、干净、可印刷(低噪点、边缘清晰)。
## 风格方向
- 允许自由发挥,但要“第一眼高级”:电影级光影、材质真实、构图克制、色彩统一。
- 主题解读:围绕“强风格蒸汽波:粉紫渐变天空、复古 3D 几何、镜面地面、霓虹光晕(无文字无数字,右侧留白)”构建一致世界观与道具元素。
## 输出要求
- 只输出最终成图,不要解释文字。

</details>

5.6.5 欧普艺术 Op Art

<details>

<summary>展开查看背景 Prompt(完整)</summary>

你是顶级视觉设计师与插画导演。你的任务是:生成一张**可印刷的横版台历背景图**,只负责“主视觉/背景”,不负责日期排版。
## 变量
- 月份:2026-01
- 主题(可覆写):强风格欧普艺术:黑白几何错视、波纹与棋盘扭曲、极致秩序感(无文字无数字,右侧留白)
## 关键约束(必须遵守)
- 这张图只画“背景/主视觉”,**禁止生成任何文字**(包括月份、招牌字、标题、UI 小字、印章字、海报字)。
- **禁止生成任何数字**(避免误伤日历数字)。
- 在画面右侧预留一个干净区域用于后续代码叠加日历:右侧约 40% 为浅色/低纹理/高对比底,留白充足。
- 主视觉集中在左侧约 60%,构图高级、干净、可印刷(低噪点、边缘清晰)。
## 风格方向
- 允许自由发挥,但要“第一眼高级”:电影级光影、材质真实、构图克制、色彩统一。
- 主题解读:围绕“强风格欧普艺术:黑白几何错视、波纹与棋盘扭曲、极致秩序感(无文字无数字,右侧留白)”构建一致世界观与道具元素。
## 输出要求
- 只输出最终成图,不要解释文字。

</details>

5.6.6 不透明水粉 Gouache

<details>

<summary>展开查看背景 Prompt(完整)</summary>

你是顶级视觉设计师与插画导演。你的任务是:生成一张**可印刷的横版台历背景图**,只负责“主视觉/背景”,不负责日期排版。
## 变量
- 月份:2026-01
- 主题(可覆写):强风格不透明水粉:厚涂笔触、块面构成、复古色彩、边缘干净(无文字无数字,右侧留白)
## 关键约束(必须遵守)
- 这张图只画“背景/主视觉”,**禁止生成任何文字**(包括月份、招牌字、标题、UI 小字、印章字、海报字)。
- **禁止生成任何数字**(避免误伤日历数字)。
- 在画面右侧预留一个干净区域用于后续代码叠加日历:右侧约 40% 为浅色/低纹理/高对比底,留白充足。
- 主视觉集中在左侧约 60%,构图高级、干净、可印刷(低噪点、边缘清晰)。
## 风格方向
- 允许自由发挥,但要“第一眼高级”:电影级光影、材质真实、构图克制、色彩统一。
- 主题解读:围绕“强风格不透明水粉:厚涂笔触、块面构成、复古色彩、边缘干净(无文字无数字,右侧留白)”构建一致世界观与道具元素。
## 输出要求
- 只输出最终成图,不要解释文字。

</details>

5.6.7 彩色玻璃 Stained Glass

<details>

<summary>展开查看背景 Prompt(完整)</summary>

你是顶级视觉设计师与插画导演。你的任务是:生成一张**可印刷的横版台历背景图**,只负责“主视觉/背景”,不负责日期排版。
## 变量
- 月份:2026-01
- 主题(可覆写):强风格彩色玻璃窗:铅条分割、宝石色透光、圣堂般光束与尘埃(无文字无数字,右侧留白)
## 关键约束(必须遵守)
- 这张图只画“背景/主视觉”,**禁止生成任何文字**(包括月份、招牌字、标题、UI 小字、印章字、海报字)。
- **禁止生成任何数字**(避免误伤日历数字)。
- 在画面右侧预留一个干净区域用于后续代码叠加日历:右侧约 40% 为浅色/低纹理/高对比底,留白充足。
- 主视觉集中在左侧约 60%,构图高级、干净、可印刷(低噪点、边缘清晰)。
## 风格方向
- 允许自由发挥,但要“第一眼高级”:电影级光影、材质真实、构图克制、色彩统一。
- 主题解读:围绕“强风格彩色玻璃窗:铅条分割、宝石色透光、圣堂般光束与尘埃(无文字无数字,右侧留白)”构建一致世界观与道具元素。
## 输出要求
- 只输出最终成图,不要解释文字。

</details>

5.6.8 新艺术 Art Nouveau

<details>

<summary>展开查看背景 Prompt(完整)</summary>

你是顶级视觉设计师与插画导演。你的任务是:生成一张**可印刷的横版台历背景图**,只负责“主视觉/背景”,不负责日期排版。
## 变量
- 月份:2026-01
- 主题(可覆写):强风格新艺术:藤蔓曲线、装饰边框、花卉与孔雀羽纹样(无文字无数字,右侧留白)
## 关键约束(必须遵守)
- 这张图只画“背景/主视觉”,**禁止生成任何文字**(包括月份、招牌字、标题、UI 小字、印章字、海报字)。
- **禁止生成任何数字**(避免误伤日历数字)。
- 在画面右侧预留一个干净区域用于后续代码叠加日历:右侧约 40% 为浅色/低纹理/高对比底,留白充足。
- 主视觉集中在左侧约 60%,构图高级、干净、可印刷(低噪点、边缘清晰)。
## 风格方向
- 允许自由发挥,但要“第一眼高级”:电影级光影、材质真实、构图克制、色彩统一。
- 主题解读:围绕“强风格新艺术:藤蔓曲线、装饰边框、花卉与孔雀羽纹样(无文字无数字,右侧留白)”构建一致世界观与道具元素。
## 输出要求
- 只输出最终成图,不要解释文字。

</details>

5.6.9 赛璐璐动画 Cel-shaded

<details>

<summary>展开查看背景 Prompt(完整)</summary>

你是顶级视觉设计师与插画导演。你的任务是:生成一张**可印刷的横版台历背景图**,只负责“主视觉/背景”,不负责日期排版。
## 变量
- 月份:2026-01
- 主题(可覆写):强风格赛璐璐动画:硬边阴影、清晰线稿、青春感构图与速度线(无文字无数字,右侧留白)
## 关键约束(必须遵守)
- 这张图只画“背景/主视觉”,**禁止生成任何文字**(包括月份、招牌字、标题、UI 小字、印章字、海报字)。
- **禁止生成任何数字**(避免误伤日历数字)。
- 在画面右侧预留一个干净区域用于后续代码叠加日历:右侧约 40% 为浅色/低纹理/高对比底,留白充足。
- 主视觉集中在左侧约 60%,构图高级、干净、可印刷(低噪点、边缘清晰)。
## 风格方向
- 允许自由发挥,但要“第一眼高级”:电影级光影、材质真实、构图克制、色彩统一。
- 主题解读:围绕“强风格赛璐璐动画:硬边阴影、清晰线稿、青春感构图与速度线(无文字无数字,右侧留白)”构建一致世界观与道具元素。
## 输出要求
- 只输出最终成图,不要解释文字。

</details>

5.6.10 像素风 Pixel Art

<details>

<summary>展开查看背景 Prompt(完整)</summary>

你是顶级视觉设计师与插画导演。你的任务是:生成一张**可印刷的横版台历背景图**,只负责“主视觉/背景”,不负责日期排版。
## 变量
- 月份:2026-01
- 主题(可覆写):强风格像素艺术:16-bit 游戏场景、像素网格、复古调色板(无文字无数字,右侧留白)
## 关键约束(必须遵守)
- 这张图只画“背景/主视觉”,**禁止生成任何文字**(包括月份、招牌字、标题、UI 小字、印章字、海报字)。
- **禁止生成任何数字**(避免误伤日历数字)。
- 在画面右侧预留一个干净区域用于后续代码叠加日历:右侧约 40% 为浅色/低纹理/高对比底,留白充足。
- 主视觉集中在左侧约 60%,构图高级、干净、可印刷(低噪点、边缘清晰)。
## 风格方向
- 允许自由发挥,但要“第一眼高级”:电影级光影、材质真实、构图克制、色彩统一。
- 主题解读:围绕“强风格像素艺术:16-bit 游戏场景、像素网格、复古调色板(无文字无数字,右侧留白)”构建一致世界观与道具元素。
## 输出要求
- 只输出最终成图,不要解释文字。

</details>

5.6.11 低多边形 Low Poly

<details>

<summary>展开查看背景 Prompt(完整)</summary>

你是顶级视觉设计师与插画导演。你的任务是:生成一张**可印刷的横版台历背景图**,只负责“主视觉/背景”,不负责日期排版。
## 变量
- 月份:2026-01
- 主题(可覆写):强风格低多边形:几何切面、硬朗光影、山川/动物雕塑感(无文字无数字,右侧留白)
## 关键约束(必须遵守)
- 这张图只画“背景/主视觉”,**禁止生成任何文字**(包括月份、招牌字、标题、UI 小字、印章字、海报字)。
- **禁止生成任何数字**(避免误伤日历数字)。
- 在画面右侧预留一个干净区域用于后续代码叠加日历:右侧约 40% 为浅色/低纹理/高对比底,留白充足。
- 主视觉集中在左侧约 60%,构图高级、干净、可印刷(低噪点、边缘清晰)。
## 风格方向
- 允许自由发挥,但要“第一眼高级”:电影级光影、材质真实、构图克制、色彩统一。
- 主题解读:围绕“强风格低多边形:几何切面、硬朗光影、山川/动物雕塑感(无文字无数字,右侧留白)”构建一致世界观与道具元素。
## 输出要求
- 只输出最终成图,不要解释文字。

</details>

5.6.12 分形/万花筒 Fractal

<details>

<summary>展开查看背景 Prompt(完整)</summary>

你是顶级视觉设计师与插画导演。你的任务是:生成一张**可印刷的横版台历背景图**,只负责“主视觉/背景”,不负责日期排版。
## 变量
- 月份:2026-01
- 主题(可覆写):强风格分形万花筒:对称几何、细节无限、虹彩光谱(无文字无数字,右侧留白)
## 关键约束(必须遵守)
- 这张图只画“背景/主视觉”,**禁止生成任何文字**(包括月份、招牌字、标题、UI 小字、印章字、海报字)。
- **禁止生成任何数字**(避免误伤日历数字)。
- 在画面右侧预留一个干净区域用于后续代码叠加日历:右侧约 40% 为浅色/低纹理/高对比底,留白充足。
- 主视觉集中在左侧约 60%,构图高级、干净、可印刷(低噪点、边缘清晰)。
## 风格方向
- 允许自由发挥,但要“第一眼高级”:电影级光影、材质真实、构图克制、色彩统一。
- 主题解读:围绕“强风格分形万花筒:对称几何、细节无限、虹彩光谱(无文字无数字,右侧留白)”构建一致世界观与道具元素。
## 输出要求
- 只输出最终成图,不要解释文字。

</details>

5.6.13 新怪诞涂鸦 Graffiti

<details>

<summary>展开查看背景 Prompt(完整)</summary>

你是顶级视觉设计师与插画导演。你的任务是:生成一张**可印刷的横版台历背景图**,只负责“主视觉/背景”,不负责日期排版。
## 变量
- 月份:2026-01
- 主题(可覆写):强风格街头涂鸦:喷漆肌理、怪诞涂鸦角色、强对比配色(无文字无数字,右侧留白)
## 关键约束(必须遵守)
- 这张图只画“背景/主视觉”,**禁止生成任何文字**(包括月份、招牌字、标题、UI 小字、印章字、海报字)。
- **禁止生成任何数字**(避免误伤日历数字)。
- 在画面右侧预留一个干净区域用于后续代码叠加日历:右侧约 40% 为浅色/低纹理/高对比底,留白充足。
- 主视觉集中在左侧约 60%,构图高级、干净、可印刷(低噪点、边缘清晰)。
## 风格方向
- 允许自由发挥,但要“第一眼高级”:电影级光影、材质真实、构图克制、色彩统一。
- 主题解读:围绕“强风格街头涂鸦:喷漆肌理、怪诞涂鸦角色、强对比配色(无文字无数字,右侧留白)”构建一致世界观与道具元素。
## 输出要求
- 只输出最终成图,不要解释文字。

</details>

5.6.14 立体折纸 Origami

<details>

<summary>展开查看背景 Prompt(完整)</summary>

你是顶级视觉设计师与插画导演。你的任务是:生成一张**可印刷的横版台历背景图**,只负责“主视觉/背景”,不负责日期排版。
## 变量
- 月份:2026-01
- 主题(可覆写):强风格折纸装置:纸张折痕、层叠结构、柔和侧光与投影(无文字无数字,右侧留白)
## 关键约束(必须遵守)
- 这张图只画“背景/主视觉”,**禁止生成任何文字**(包括月份、招牌字、标题、UI 小字、印章字、海报字)。
- **禁止生成任何数字**(避免误伤日历数字)。
- 在画面右侧预留一个干净区域用于后续代码叠加日历:右侧约 40% 为浅色/低纹理/高对比底,留白充足。
- 主视觉集中在左侧约 60%,构图高级、干净、可印刷(低噪点、边缘清晰)。
## 风格方向
- 允许自由发挥,但要“第一眼高级”:电影级光影、材质真实、构图克制、色彩统一。
- 主题解读:围绕“强风格折纸装置:纸张折痕、层叠结构、柔和侧光与投影(无文字无数字,右侧留白)”构建一致世界观与道具元素。
## 输出要求
- 只输出最终成图,不要解释文字。

</details>


6. 如何扩展更多“惊艳风格”(推荐做法)

6.1 只改 JSON,就能批量出新风格

编辑 测试comfyui_副本/demo/comfyui-zimage-demo/calendar_variants_background.json,新增一条:

然后重跑:

Terminal window
INSECURE_TLS=1 \
COMFY_BASE_URL="https://deployment-318-klu0niut-8188.550w.link" \
python3 "测试comfyui_副本/demo/comfyui-zimage-demo/batch_generate_calendars_composited.py" --month 2026-02 --force

7. 常见问题(FAQ,便于 GEO/SEO)

7.1 怎么保证日期永远正确?

generate_calendar.py 生成 CSV(真逻辑),再用 render_calendar_overlay.py + compose_calendar.py 叠加(真排版)。模型不参与日期生成与排版。

7.2 为什么要强制“背景不生成文字/数字”?

因为“文字/数字”是模型最容易乱写的内容;让它只出背景能显著提升稳定性。即便背景里偶尔出现字,最终日历区域也会被不透明白面板彻底遮住。

7.3 如何输出 A4 横版 / 300dpi?

把 variants 里的 width/height 设置成接近 A4 横版比例(例如 3508×2480 对应 300dpi),并留意显存与推理时间。

7.4 如何把“周一开始”改成“周日开始”?

generate_calendar.py 使用 --firstweekday sun;同时在 render_calendar_overlay.py 里把 weekday 标题改为 Sun..Sat 即可。


8. 复刻清单(文件一览)


9. 完整代码与完整 Prompt(可复制粘贴)

你可以直接把这一节复制到你的项目里跑;或者在共绩算力平台选预制 ComfyUI 镜像后,把这些脚本放进去运行。

9.1 日历骨架生成脚本:ai相关好玩的/ai日历/generate_calendar.py

#!/usr/bin/env python3
"""
两段式工作流 · 第 1 段:脚本先行(生成“准确日历骨架”)
输出:
- CSV:42 格(6 行 * 7 列)的结构化日历骨架
- Markdown 表格:便于直接注入 Prompt
仅使用标准库;避免把日期逻辑交给模型,彻底杜绝“2 月 30 号”这类幻觉。
"""
from __future__ import annotations
import argparse
import calendar
import csv
import datetime as dt
from dataclasses import dataclass
from pathlib import Path
from typing import Iterable, List, Sequence
WEEKDAY_EN = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
WEEKDAY_CN = ["一", "二", "三", "四", "五", "六", "日"]
@dataclass(frozen=True)
class Cell:
row: int
col: int
date: dt.date
in_month: bool
@property
def iso(self) -> str:
return self.date.isoformat()
@property
def day(self) -> int:
return self.date.day
@property
def weekday_index(self) -> int:
# Monday=0 ... Sunday=6
return self.date.weekday()
@property
def weekday_en(self) -> str:
return WEEKDAY_EN[self.weekday_index]
@property
def weekday_cn(self) -> str:
return WEEKDAY_CN[self.weekday_index]
def parse_month(month_str: str | None) -> tuple[int, int]:
if not month_str:
today = dt.date.today()
return today.year, today.month
# 支持:YYYY-MM / YYYY/MM / YYYYMM
s = month_str.strip()
for fmt in ("%Y-%m", "%Y/%m", "%Y%m"):
try:
d = dt.datetime.strptime(s, fmt)
return d.year, d.month
except ValueError:
pass
raise SystemExit(f"无法解析月份:{month_str!r},请用 YYYY-MM(如 2026-01)")
def month_cells(year: int, month: int, *, firstweekday: int = 0) -> List[Cell]:
cal = calendar.Calendar(firstweekday=firstweekday) # 0=Mon ... 6=Sun
weeks = cal.monthdatescalendar(year, month)
# 视觉结构固定为 6 行(常见台历布局);不足补齐到 6 行
while len(weeks) < 6:
last_week = weeks[-1]
next_week_start = last_week[-1] + dt.timedelta(days=1)
weeks.append([next_week_start + dt.timedelta(days=i) for i in range(7)])
# 如果极端情况下多于 6 行(理论上不会发生),截断
weeks = weeks[:6]
cells: List[Cell] = []
for r, week in enumerate(weeks):
for c, d in enumerate(week):
cells.append(Cell(row=r, col=c, date=d, in_month=(d.month == month)))
return cells
def cells_to_markdown_table(
cells: Sequence[Cell],
*,
show_outside_days: bool,
header: Sequence[str] = WEEKDAY_EN,
) -> str:
grid: List[List[str]] = [["" for _ in range(7)] for _ in range(6)]
for cell in cells:
if cell.in_month:
grid[cell.row][cell.col] = str(cell.day)
else:
grid[cell.row][cell.col] = str(cell.day) if show_outside_days else ""
header_row = "| " + " | ".join(header) + " |"
sep_row = "| " + " | ".join(["---"] * 7) + " |"
body_rows = ["| " + " | ".join(row) + " |" for row in grid]
return "\n".join([header_row, sep_row, *body_rows]) + "\n"
def write_csv(
cells: Sequence[Cell],
out_path: Path,
*,
month: int,
show_outside_days: bool,
) -> None:
out_path.parent.mkdir(parents=True, exist_ok=True)
with out_path.open("w", encoding="utf-8", newline="") as f:
w = csv.DictWriter(
f,
fieldnames=[
"row",
"col",
"date",
"day",
"in_month",
"weekday_index",
"weekday_en",
"weekday_cn",
"display",
],
)
w.writeheader()
for cell in cells:
display = ""
if cell.in_month:
display = str(cell.day)
elif show_outside_days:
display = str(cell.day)
w.writerow(
{
"row": cell.row,
"col": cell.col,
"date": cell.iso,
"day": cell.day,
"in_month": int(cell.in_month),
"weekday_index": cell.weekday_index,
"weekday_en": cell.weekday_en,
"weekday_cn": cell.weekday_cn,
"display": display,
}
)
def main(argv: Iterable[str] | None = None) -> int:
p = argparse.ArgumentParser(
description="生成准确的日历骨架(CSV/Markdown),用于后续 Prompt 注入。",
)
p.add_argument(
"--month",
default=None,
help="目标月份,格式 YYYY-MM(默认:当前系统月份)",
)
p.add_argument(
"--firstweekday",
default="mon",
choices=["mon", "sun"],
help="一周从哪天开始(默认:mon)",
)
p.add_argument(
"--show-outside-days",
action="store_true",
help="表格里是否显示上/下月的日期数字(默认留空,台历更干净)",
)
p.add_argument(
"--out-csv",
default=None,
help="CSV 输出路径(默认:ai 相关好玩的/ai 日历/output/calendar_YYYY-MM.csv)",
)
p.add_argument(
"--out-md",
default=None,
help="Markdown 表格输出路径(默认:ai 相关好玩的/ai 日历/output/calendar_YYYY-MM.md)",
)
p.add_argument(
"--only",
default="both",
choices=["both", "csv", "md"],
help="只输出 csv 或 md(默认:both)",
)
args = p.parse_args(list(argv) if argv is not None else None)
year, month = parse_month(args.month)
fw = 0 if args.firstweekday == "mon" else 6
cells = month_cells(year, month, firstweekday=fw)
ym = f"{year:04d}-{month:02d}"
default_csv = Path(__file__).resolve().parent / "output" / f"calendar_{ym}.csv"
default_md = Path(__file__).resolve().parent / "output" / f"calendar_{ym}.md"
out_csv = Path(args.out_csv) if args.out_csv else default_csv
out_md = Path(args.out_md) if args.out_md else default_md
if args.only in ("both", "csv"):
write_csv(cells, out_csv, month=month, show_outside_days=args.show_outside_days)
print(f"[ok] CSV 已生成:{out_csv}")
if args.only in ("both", "md"):
header = WEEKDAY_EN if args.firstweekday == "mon" else ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
md = cells_to_markdown_table(
cells,
show_outside_days=args.show_outside_days,
header=header,
)
out_md.parent.mkdir(parents=True, exist_ok=True)
out_md.write_text(md, encoding="utf-8")
print(f"[ok] Markdown 已生成:{out_md}")
return 0
if __name__ == "__main__":
raise SystemExit(main())

9.2 背景 Prompt 模版(完整):ai相关好玩的/ai日历/templates/background_template_generic.md

你是顶级视觉设计师与插画导演。你的任务是:生成一张**可印刷的横版台历背景图**,只负责“主视觉/背景”,不负责日期排版。
## 变量
- 月份:{{YM}}
- 主题(可覆写):{{THEME}}
## 关键约束(必须遵守)
- 这张图只画“背景/主视觉”,**禁止生成任何文字**(包括月份、招牌字、标题、UI 小字、印章字、海报字)。
- **禁止生成任何数字**(避免误伤日历数字)。
- 在画面右侧预留一个干净区域用于后续代码叠加日历:右侧约 40% 为浅色/低纹理/高对比底,留白充足。
- 主视觉集中在左侧约 60%,构图高级、干净、可印刷(低噪点、边缘清晰)。
## 风格方向
- 允许自由发挥,但要“第一眼高级”:电影级光影、材质真实、构图克制、色彩统一。
- 主题解读:围绕“{{THEME}}”构建一致世界观与道具元素。
## 输出要求
- 只输出最终成图,不要解释文字。

9.3 背景 Prompt 生成脚本(完整):ai相关好玩的/ai日历/build_background_prompt.py

#!/usr/bin/env python3
"""
两段式工作流(扩展)· 背景 Prompt 生成器
目的:生成“只有背景/主视觉”的提示词,明确要求 **不生成任何文字/日期**,
并在画面中预留一个干净区域(用于后续代码精确叠加日历排版)。
这样能彻底解决:模型乱写星期、乱写数字、日期错位等问题。
"""
from __future__ import annotations
import argparse
import datetime as dt
from pathlib import Path
from typing import Dict, Iterable
def parse_month(month_str: str | None) -> tuple[int, int]:
if not month_str:
today = dt.date.today()
return today.year, today.month
s = month_str.strip()
for fmt in ("%Y-%m", "%Y/%m", "%Y%m"):
try:
d = dt.datetime.strptime(s, fmt)
return d.year, d.month
except ValueError:
pass
raise SystemExit(f"无法解析月份:{month_str!r},请用 YYYY-MM(如 2026-01)")
def render_template(template_text: str, variables: Dict[str, str]) -> str:
out = template_text
for k, v in variables.items():
out = out.replace("{{" + k + "}}", v)
return out
def default_theme_for_month(year: int, month: int) -> str:
season = (
"冬季" if month in (12, 1, 2) else
"春季" if month in (3, 4, 5) else
"夏季" if month in (6, 7, 8) else
"秋季"
)
return f"{year}{month:02d}月 · {season}主题(可覆写)"
def main(argv: Iterable[str] | None = None) -> int:
p = argparse.ArgumentParser(description="生成“背景专用”的绘图 Prompt(不含日历文字)。")
p.add_argument("--month", default=None, help="目标月份 YYYY-MM(默认:当前系统月份)")
p.add_argument(
"--template",
default=None,
help="背景 Prompt 模版路径(默认:ai 相关好玩的/ai 日历/templates/background_template_generic.md)",
)
p.add_argument("--theme", default=None, help="主题覆写")
p.add_argument(
"--out",
default=None,
help="输出路径(默认:ai 相关好玩的/ai 日历/output/background_prompt_YYYY-MM.md)",
)
args = p.parse_args(list(argv) if argv is not None else None)
year, month = parse_month(args.month)
ym = f"{year:04d}-{month:02d}"
base_dir = Path(__file__).resolve().parent
default_template = base_dir / "templates" / "background_template_generic.md"
default_out = base_dir / "output" / f"background_prompt_{ym}.md"
template_path = Path(args.template) if args.template else default_template
out_path = Path(args.out) if args.out else default_out
if not template_path.exists():
raise SystemExit(f"找不到模版:{template_path}")
theme = args.theme.strip() if args.theme else default_theme_for_month(year, month)
template_text = template_path.read_text(encoding="utf-8")
final_text = render_template(
template_text,
variables={
"YEAR": str(year),
"MONTH": f"{month:02d}",
"YM": ym,
"THEME": theme,
},
).rstrip() + "\n"
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(final_text, encoding="utf-8")
print(f"[ok] 背景 Prompt 已生成:{out_path}")
return 0
if __name__ == "__main__":
raise SystemExit(main())

9.4 日历 overlay 排版脚本(完整):ai相关好玩的/ai日历/render_calendar_overlay.py

#!/usr/bin/env python3
"""
确定性日历排版(第三段,可选但强烈推荐)
输入:calendar_YYYY-MM.csv(第 1 段生成)
输出:一个 RGBA 透明 PNG(只包含日期排版,可叠加到任意背景图)
依赖:Pillow(已在你的环境可用)
"""
from __future__ import annotations
import argparse
import csv
from dataclasses import dataclass
from pathlib import Path
from typing import Dict, Iterable, Tuple
from PIL import Image, ImageDraw, ImageFont # type: ignore
@dataclass(frozen=True)
class Box:
x: int
y: int
w: int
h: int
def parse_box(s: str) -> Box:
# "x,y,w,h"
parts = [p.strip() for p in s.split(",")]
if len(parts) != 4:
raise SystemExit("--box 需要格式:x,y,w,h")
x, y, w, h = (int(float(p)) for p in parts)
return Box(x=x, y=y, w=w, h=h)
def load_grid(csv_path: Path) -> Dict[Tuple[int, int], str]:
grid: Dict[Tuple[int, int], str] = {}
with csv_path.open("r", encoding="utf-8", newline="") as f:
r = csv.DictReader(f)
for row in r:
rr = int(row["row"])
cc = int(row["col"])
grid[(rr, cc)] = (row.get("display") or "").strip()
# ensure full 6x7
for rr in range(6):
for cc in range(7):
grid.setdefault((rr, cc), "")
return grid
def load_font(font_path: Path | None, size: int) -> ImageFont.ImageFont:
if font_path and font_path.exists():
try:
return ImageFont.truetype(str(font_path), size=size)
except Exception:
pass
return ImageFont.load_default()
def draw_calendar_overlay(
*,
width: int,
height: int,
ym: str,
grid: Dict[Tuple[int, int], str],
box: Box,
font_path: Path | None,
weekdays: Tuple[str, ...],
title: bool,
grid_lines: bool,
) -> Image.Image:
img = Image.new("RGBA", (width, height), (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
# Layout inside box
pad = max(12, int(min(box.w, box.h) * 0.04))
x0, y0 = box.x + pad, box.y + pad
x1, y1 = box.x + box.w - pad, box.y + box.h - pad
inner_w, inner_h = max(1, x1 - x0), max(1, y1 - y0)
# Title block
title_h = int(inner_h * 0.16) if title else 0
title_h = min(title_h, 90)
week_h = int(inner_h * 0.12)
week_h = min(week_h, 60)
cells_h = inner_h - title_h - week_h
cells_h = max(cells_h, 1)
cols, rows = 7, 6
cell_w = inner_w / cols
cell_h = cells_h / rows
# Fonts
title_font = load_font(font_path, size=max(22, int(min(inner_w, inner_h) * 0.07)))
week_font = load_font(font_path, size=max(14, int(min(inner_w, inner_h) * 0.035)))
day_font = load_font(font_path, size=max(16, int(min(inner_w, inner_h) * 0.045)))
# Colors
fg = (20, 20, 20, 255)
muted = (70, 70, 70, 255)
line = (20, 20, 20, 35)
# Title
if title:
tx, ty = x0, y0
draw.text((tx, ty), ym, fill=fg, font=title_font)
# Weekdays
wy = y0 + title_h
for c in range(cols):
label = weekdays[c]
cx = x0 + c * cell_w
# center label in column header
bbox = draw.textbbox((0, 0), label, font=week_font)
tw = bbox[2] - bbox[0]
th = bbox[3] - bbox[1]
lx = cx + (cell_w - tw) / 2
ly = wy + (week_h - th) / 2
draw.text((lx, ly), label, fill=muted, font=week_font)
# Cells
gy0 = wy + week_h
if grid_lines:
# border
draw.rectangle([x0, gy0, x0 + inner_w, gy0 + rows * cell_h], outline=line, width=1)
# vertical lines
for c in range(1, cols):
x = x0 + c * cell_w
draw.line([x, gy0, x, gy0 + rows * cell_h], fill=line, width=1)
# horizontal lines
for r in range(1, rows):
y = gy0 + r * cell_h
draw.line([x0, y, x0 + inner_w, y], fill=line, width=1)
for r in range(rows):
for c in range(cols):
text = grid[(r, c)]
if not text:
continue
cx = x0 + c * cell_w
cy = gy0 + r * cell_h
# top-left padding inside cell
inset_x = max(6, int(cell_w * 0.08))
inset_y = max(4, int(cell_h * 0.08))
draw.text((cx + inset_x, cy + inset_y), text, fill=fg, font=day_font)
return img
def main(argv: Iterable[str] | None = None) -> int:
p = argparse.ArgumentParser(description="把 CSV 日历骨架排版成透明 PNG overlay。")
p.add_argument("--csv", required=True, help="calendar_YYYY-MM.csv 路径")
p.add_argument("--ym", required=True, help="月份 YYYY-MM(用于标题)")
p.add_argument("--width", type=int, required=True, help="画布宽度")
p.add_argument("--height", type=int, required=True, help="画布高度")
p.add_argument("--out", required=True, help="输出 overlay PNG 路径")
p.add_argument("--box", default=None, help="日历区域 x,y,w,h(像素)。默认:右侧 40% 区域。")
p.add_argument(
"--font",
default=None,
help="字体文件路径(可选)。默认会尝试使用 博客/assets/fonts/NotoSansSC-wght.ttf",
)
p.add_argument("--no-title", action="store_true", help="不渲染月份标题")
p.add_argument("--no-grid", action="store_true", help="不画网格线(只画数字)")
args = p.parse_args(list(argv) if argv is not None else None)
width, height = int(args.width), int(args.height)
csv_path = Path(args.csv)
out_path = Path(args.out)
if args.box:
box = parse_box(args.box)
else:
# default: right 40% with margins
margin = int(min(width, height) * 0.06)
bw = int(width * 0.42)
box = Box(x=width - margin - bw, y=margin, w=bw, h=height - 2 * margin)
# default font: try blog assets if present
font_path: Path | None
if args.font:
font_path = Path(args.font)
else:
# workspace root from this file: ai 相关好玩的/ai 日历/ -> parents[2] == 共绩
root = Path(__file__).resolve().parents[2]
font_path = root / "博客" / "assets" / "fonts" / "NotoSansSC-wght.ttf"
grid = load_grid(csv_path)
weekdays = ("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")
overlay = draw_calendar_overlay(
width=width,
height=height,
ym=str(args.ym),
grid=grid,
box=box,
font_path=font_path,
weekdays=weekdays,
title=not bool(args.no_title),
grid_lines=not bool(args.no_grid),
)
out_path.parent.mkdir(parents=True, exist_ok=True)
overlay.save(str(out_path))
print(f"[ok] overlay 已生成:{out_path}")
return 0
if __name__ == "__main__":
raise SystemExit(main())

9.5 合成脚本(完整):ai相关好玩的/ai日历/compose_calendar.py

#!/usr/bin/env python3
"""
把“背景图”与“日历 overlay”合成最终台历图。
依赖:Pillow
"""
from __future__ import annotations
import argparse
from pathlib import Path
from typing import Iterable, Tuple
from PIL import Image, ImageDraw # type: ignore
def parse_rgba(s: str) -> Tuple[int, int, int, int]:
# "r,g,b,a"
parts = [p.strip() for p in s.split(",")]
if len(parts) != 4:
raise SystemExit("--panel-color 需要格式:r,g,b,a")
return tuple(int(float(p)) for p in parts) # type: ignore
def main(argv: Iterable[str] | None = None) -> int:
p = argparse.ArgumentParser(description="合成:background + overlay -> final")
p.add_argument("--background", required=True, help="背景 PNG 路径")
p.add_argument("--overlay", required=True, help="透明 overlay PNG 路径")
p.add_argument("--out", required=True, help="输出 final PNG 路径")
p.add_argument(
"--panel",
default="1",
help="是否在日历区域加一块半透明底(提升可读性)。1=是 0=否(默认 1)",
)
p.add_argument(
"--panel-color",
default="255,255,255,255",
help="面板颜色 RGBA(默认纯白不透明 255,255,255,255,用于彻底遮住背景里的误写文字/数字)",
)
p.add_argument(
"--panel-box",
default=None,
help="面板区域 x,y,w,h(像素)。默认:自动取 overlay 的非透明区域外接矩形并扩边。",
)
args = p.parse_args(list(argv) if argv is not None else None)
bg_path = Path(args.background)
ov_path = Path(args.overlay)
out_path = Path(args.out)
bg = Image.open(str(bg_path)).convert("RGBA")
ov = Image.open(str(ov_path)).convert("RGBA")
if bg.size != ov.size:
ov = ov.resize(bg.size, resample=Image.BICUBIC)
if str(args.panel).strip() not in ("0", "", "false", "False", "no", "NO"):
# compute default panel box from overlay alpha bbox
if args.panel_box:
x, y, w, h = (int(float(p.strip())) for p in args.panel_box.split(","))
panel_box = (x, y, x + w, y + h)
else:
alpha = ov.split()[-1]
bbox = alpha.getbbox()
if bbox:
# 默认扩边更大:更彻底隔绝背景“乱写的字/数字”
pad = int(min(bg.size) * 0.035)
panel_box = (
max(0, bbox[0] - pad),
max(0, bbox[1] - pad),
min(bg.size[0], bbox[2] + pad),
min(bg.size[1], bbox[3] + pad),
)
else:
panel_box = None
if panel_box:
panel = Image.new("RGBA", bg.size, (0, 0, 0, 0))
draw = ImageDraw.Draw(panel)
color = parse_rgba(args.panel_color)
draw.rounded_rectangle(panel_box, radius=18, fill=color)
bg = Image.alpha_composite(bg, panel)
out = Image.alpha_composite(bg, ov)
out_path.parent.mkdir(parents=True, exist_ok=True)
out.save(str(out_path))
print(f"[ok] final 已生成:{out_path}")
return 0
if __name__ == "__main__":
raise SystemExit(main())

9.6 ComfyUI 批量全流程脚本(完整):测试comfyui_副本/demo/comfyui-zimage-demo/batch_generate_calendars_composited.py

#!/usr/bin/env python3
"""
更稳定、更“惊艳”的台历生成:背景交给模型,日期排版交给代码(Pillow)
流程:
1) generate_calendar.py 生成 CSV(日期绝对正确)
2) build_background_prompt.py 生成“只画背景”的 Prompt(强制无文字/无数字,右侧留白)
3) ComfyUI 生成背景图(background.png)
4) render_calendar_overlay.py 把 CSV 排版成透明 overlay.png
5) compose_calendar.py 合成 final.png(日期永远正确)
"""
from __future__ import annotations
import argparse
import json
import os
import subprocess
import sys
from pathlib import Path
from typing import Any, Dict, List, Optional
from generate import ( # type: ignore
create_zimage_turbo_workflow,
get_ssl_context,
http_bytes,
http_json,
)
def workspace_root_from_here() -> Path:
# 共绩/测试 comfyui_副本/demo/comfyui-zimage-demo/ -> parents[3] == 共绩
return Path(__file__).resolve().parents[3]
def comfy_generate_png(
*,
base_url: str,
prompt: str,
negative: str,
width: int,
height: int,
steps: int,
seed: Optional[int],
out_path: Path,
) -> None:
base = base_url.rstrip("/")
ctx = get_ssl_context()
wf = create_zimage_turbo_workflow(
prompt=prompt,
negative_prompt=negative,
width=width,
height=height,
steps=steps,
seed=seed,
)
resp = http_json(f"{base}/prompt", method="POST", body={"prompt": wf}, ctx=ctx, timeout=240)
images = resp.get("images")
if isinstance(images, list) and images and isinstance(images[0], str):
import base64
out_path.write_bytes(base64.b64decode(images[0]))
return
prompt_id = resp.get("prompt_id")
if not prompt_id:
raise RuntimeError(f"unexpected response keys={list(resp.keys())}")
history_url = f"{base}/history/{prompt_id}"
item = None
for _ in range(120):
data = http_json(history_url, ctx=ctx, timeout=60)
item = data.get(prompt_id)
outputs = (item or {}).get("outputs") or {}
if item and outputs:
break
import time
time.sleep(2)
if not item:
raise RuntimeError("history timeout: no record for prompt_id")
outputs = item.get("outputs") or {}
save_out = outputs.get("9") or {}
out_images = save_out.get("images") or []
if not out_images:
for v in outputs.values():
imgs = (v or {}).get("images") or []
if imgs:
out_images = imgs
break
if not out_images:
raise RuntimeError(f"no images found in outputs. outputs_keys={list(outputs.keys())}")
img0 = out_images[0]
filename = img0.get("filename")
subfolder = img0.get("subfolder", "")
ftype = img0.get("type", "output")
from urllib.parse import urlencode
view_url = f"{base}/view?{urlencode({'filename': filename, 'subfolder': subfolder, 'type': ftype})}"
blob = http_bytes(view_url, ctx=ctx, timeout=240)
out_path.write_bytes(blob)
def main() -> int:
p = argparse.ArgumentParser(description="批量生成(背景 + 代码排版)台历图。")
p.add_argument("--month", default="2026-01", help="目标月份 YYYY-MM(默认 2026-01)")
p.add_argument(
"--base-url",
default=os.getenv("COMFY_BASE_URL", "https://deployment-318-klu0niut-8188.550w.link"),
help="ComfyUI Base URL",
)
p.add_argument(
"--variants",
default=str(Path(__file__).resolve().parent / "calendar_variants_background.json"),
help="背景 variants 配置 JSON",
)
p.add_argument("--force", action="store_true", help="强制重跑")
args = p.parse_args()
root = workspace_root_from_here()
# Paths to ai 日历 scripts
ai_dir = root / "ai 相关好玩的" / "ai 日历"
gen_csv = ai_dir / "generate_calendar.py"
build_bg = ai_dir / "build_background_prompt.py"
render_ov = ai_dir / "render_calendar_overlay.py"
compose = ai_dir / "compose_calendar.py"
out_dir = Path(__file__).resolve().parent / "outputs"
out_dir.mkdir(parents=True, exist_ok=True)
prompt_dir = out_dir / "_calendar_prompts_composited"
prompt_dir.mkdir(parents=True, exist_ok=True)
variants_path = Path(args.variants)
items: List[Dict[str, Any]] = json.loads(variants_path.read_text(encoding="utf-8"))
# Step 1: generate calendar CSV once
subprocess.run([sys.executable, str(gen_csv), "--month", args.month], check=True, cwd=str(root))
csv_path = ai_dir / "output" / f"calendar_{args.month}.csv"
# A slightly stronger negative prompt for background-only generation
negative = os.getenv(
"NEGATIVE_PROMPT",
"text, letters, words, numbers, digits, watermark, logo, blurry, low quality, lowres",
)
for it in items:
vid = str(it["id"])
title = str(it.get("title", vid))
theme = str(it.get("theme", ""))
width = int(it.get("width", 1536))
height = int(it.get("height", 1088))
steps = int(it.get("steps", 20))
seed = it.get("seed")
seed_val = int(seed) if seed is not None else None
bg_path = out_dir / f"{vid}_{args.month}_bg.png"
ov_path = out_dir / f"{vid}_{args.month}_overlay.png"
final_path = out_dir / f"{vid}_{args.month}_final.png"
prompt_path = prompt_dir / f"{vid}_{args.month}.md"
if final_path.exists() and not args.force:
print(f"[skip] 已存在:{final_path} ({title})")
continue
# Step 2: build background prompt
subprocess.run(
[
sys.executable,
str(build_bg),
"--month",
args.month,
"--theme",
theme,
"--template",
str(ai_dir / "templates" / "background_template_generic.md"),
"--out",
str(prompt_path),
],
check=True,
cwd=str(root),
)
prompt_text = prompt_path.read_text(encoding="utf-8")
# Step 3: generate background
if args.force or (not bg_path.exists()):
comfy_generate_png(
base_url=str(args.base_url),
prompt=prompt_text,
negative=negative,
width=width,
height=height,
steps=steps,
seed=seed_val,
out_path=bg_path,
)
# Step 4: render overlay
subprocess.run(
[
sys.executable,
str(render_ov),
"--csv",
str(csv_path),
"--ym",
args.month,
"--width",
str(width),
"--height",
str(height),
"--out",
str(ov_path),
],
check=True,
cwd=str(root),
)
# Step 5: compose final
subprocess.run(
[
sys.executable,
str(compose),
"--background",
str(bg_path),
"--overlay",
str(ov_path),
"--out",
str(final_path),
],
check=True,
cwd=str(root),
)
print(f"[ok] {title} -> {final_path}")
return 0
if __name__ == "__main__":
raise SystemExit(main())

9.7 强风格艺术 variants(完整):测试comfyui_副本/demo/comfyui-zimage-demo/calendar_variants_background.json

[
{
"id": "cal_bg_luxury_a",
"title": "高端品牌 KV(背景)A",
"theme": "一月冷冽感:冰晶玻璃 + 香槟金金属 + 黑色石材底座(高级香氛 KV)",
"width": 1536,
"height": 1088,
"steps": 20,
"seed": 20260211
},
{
"id": "cal_bg_cyber_a",
"title": "赛博霓虹(背景)A",
"theme": "雨夜未来都市:霓虹反射、电光蓝与品红、体积光与薄雾(无文字无数字)",
"width": 1536,
"height": 1088,
"steps": 20,
"seed": 20260221
},
{
"id": "cal_bg_papercut_a",
"title": "纸雕剪纸(背景)A",
"theme": "多层纸雕冬季森林:雪花、松枝、麋鹿剪影(层叠纸雕,柔光阴影)",
"width": 1536,
"height": 1088,
"steps": 20,
"seed": 20260231
},
{
"id": "cal_bg_ink_a",
"title": "国风水墨(背景)A",
"theme": "雪后远山云雾、松枝与溪流(水墨留白,现代版式区域留白)",
"width": 1536,
"height": 1088,
"steps": 20,
"seed": 20260241
},
{
"id": "cal_bg_surreal_a",
"title": "超现实拼贴(背景)A",
"theme": "冬季月亮像窗户,云像丝绸,山像折纸(高概念拼贴,无文字无数字)",
"width": 1536,
"height": 1088,
"steps": 20,
"seed": 20260251
},
{
"id": "cal_bg_glitch_a",
"title": "故障艺术 Glitch(背景)A",
"theme": "强风格故障艺术:RGB 分离、扫描线、像素撕裂、数据扭曲的抽象画面(无文字无数字,右侧留白)",
"width": 1536,
"height": 1088,
"steps": 20,
"seed": 20260311
},
{
"id": "cal_bg_popart_a",
"title": "波普漫画 Pop Art(背景)A",
"theme": "强风格波普漫画:粗黑描边、Ben-Day 网点、互补色块、高级平面构图(无文字无数字,右侧留白)",
"width": 1536,
"height": 1088,
"steps": 20,
"seed": 20260321
},
{
"id": "cal_bg_linocut_a",
"title": "版画 Linocut(背景)A",
"theme": "强风格黑白版画:木刻/胶版线条、粗犷刀痕、强对比留白(无文字无数字,右侧留白)",
"width": 1536,
"height": 1088,
"steps": 20,
"seed": 20260331
},
{
"id": "cal_bg_vaporwave_a",
"title": "蒸汽波 Vaporwave(背景)A",
"theme": "强风格蒸汽波:粉紫渐变天空、复古 3D 几何、镜面地面、霓虹光晕(无文字无数字,右侧留白)",
"width": 1536,
"height": 1088,
"steps": 20,
"seed": 20260341
},
{
"id": "cal_bg_opart_a",
"title": "欧普艺术 Op Art(背景)A",
"theme": "强风格欧普艺术:黑白几何错视、波纹与棋盘扭曲、极致秩序感(无文字无数字,右侧留白)",
"width": 1536,
"height": 1088,
"steps": 20,
"seed": 20260351
},
{
"id": "cal_bg_gouache_a",
"title": "不透明水粉 Gouache(背景)A",
"theme": "强风格不透明水粉:厚涂笔触、块面构成、复古色彩、边缘干净(无文字无数字,右侧留白)",
"width": 1536,
"height": 1088,
"steps": 20,
"seed": 20260361
},
{
"id": "cal_bg_stainedglass_a",
"title": "彩色玻璃 Stained Glass(背景)A",
"theme": "强风格彩色玻璃窗:铅条分割、宝石色透光、圣堂般光束与尘埃(无文字无数字,右侧留白)",
"width": 1536,
"height": 1088,
"steps": 20,
"seed": 20260411
},
{
"id": "cal_bg_artnouveau_a",
"title": "新艺术 Art Nouveau(背景)A",
"theme": "强风格新艺术:藤蔓曲线、装饰边框、花卉与孔雀羽纹样(无文字无数字,右侧留白)",
"width": 1536,
"height": 1088,
"steps": 20,
"seed": 20260421
},
{
"id": "cal_bg_celshade_a",
"title": "赛璐璐动画 Cel-shaded(背景)A",
"theme": "强风格赛璐璐动画:硬边阴影、清晰线稿、青春感构图与速度线(无文字无数字,右侧留白)",
"width": 1536,
"height": 1088,
"steps": 20,
"seed": 20260431
},
{
"id": "cal_bg_pixelart_a",
"title": "像素风 Pixel Art(背景)A",
"theme": "强风格像素艺术:16-bit 游戏场景、像素网格、复古调色板(无文字无数字,右侧留白)",
"width": 1536,
"height": 1088,
"steps": 20,
"seed": 20260441
},
{
"id": "cal_bg_lowpoly_a",
"title": "低多边形 Low Poly(背景)A",
"theme": "强风格低多边形:几何切面、硬朗光影、山川/动物雕塑感(无文字无数字,右侧留白)",
"width": 1536,
"height": 1088,
"steps": 20,
"seed": 20260451
},
{
"id": "cal_bg_fractal_a",
"title": "分形/万花筒 Fractal(背景)A",
"theme": "强风格分形万花筒:对称几何、细节无限、虹彩光谱(无文字无数字,右侧留白)",
"width": 1536,
"height": 1088,
"steps": 20,
"seed": 20260461
},
{
"id": "cal_bg_graffiti_a",
"title": "新怪诞涂鸦 Graffiti(背景)A",
"theme": "强风格街头涂鸦:喷漆肌理、怪诞涂鸦角色、强对比配色(无文字无数字,右侧留白)",
"width": 1536,
"height": 1088,
"steps": 20,
"seed": 20260471
},
{
"id": "cal_bg_origami_a",
"title": "立体折纸 Origami(背景)A",
"theme": "强风格折纸装置:纸张折痕、层叠结构、柔和侧光与投影(无文字无数字,右侧留白)",
"width": 1536,
"height": 1088,
"steps": 20,
"seed": 20260481
}
]

准备好开始您的 AI 之旅了吗?

读完这篇文章,想必您对 AI 技术有了更深的了解。现在就来体验共绩算力,让您的想法快速变成现实。

✓ 已有 10 万 + 开发者在使用

✓ 99.9% 服务可用性

✓ 开箱即用的容器托管