直接让 AI 画日历,往往是大型翻车现场:不是日期错乱(2 月 30 号?),就是排版像被挤过的牙膏。
> 最稳的解法:把“确定性”交给代码,把“想象力”交给模型;再把“排版落地”也交给代码。
0. TL;DR
-
日期永远正确:Python 生成日历骨架(CSV),彻底杜绝日期幻觉
-
画面更惊艳:ComfyUI 只负责生成背景/主视觉(不写字、不写数字)
-
排版更稳定:Pillow 把 CSV 排版成透明 overlay,再叠加到背景图上 → 模型再强也不会“把周三写成 Wod”
0.1 3 分钟复刻(最推荐的路径)
把下面 3 步照抄执行,就能在本地或共绩算力平台批量生成台历成片(输出在 测试comfyui_副本/demo/comfyui-zimage-demo/outputs/):
0.1.1 在共绩算力平台跑(预制镜像)
-
在 共绩算力平台 创建实例/容器
-
镜像选择:预制 ComfyUI(生图/Z-Image)镜像(平台预制镜像)
-
打开/映射端口(通常
8188),得到一个可访问的 ComfyUI 地址,例如:https://<your-deployment>-8188.<domain>
-
把本项目放进实例(
git clone或上传) -
运行:
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 --force0.1.2 在本地跑(你已有 ComfyUI 服务也行)
只要你能访问一个 ComfyUI(8188)服务,把 COMFY_BASE_URL 指过去即可(同上命令)。
1. 为什么直接让模型画日历会翻车?
1.1 根因:硬逻辑 + 硬结构 + 软能力错配
-
日期是硬逻辑:列数/行数/闰年/星期几,一错就全盘错
-
表格对齐是硬结构:一个字符宽度的误差就会导致错位
-
视觉创作是软能力:模型擅长“审美推演”,不擅长“严格排版与核对”
结论:想要“可印刷、可交付”的台历页,日历数字不能交给模型。
2. 两段式工作流(基础版):脚本骨架 + Prompt 注入
2.1 第 1 段:脚本先行(把逻辑交给代码)
目录:ai相关好玩的/ai日历/
generate_calendar.py:生成 6×7 固定网格的日历骨架- 输出
output/calendar_YYYY-MM.csv - 输出
output/calendar_YYYY-MM.md
- 输出
示例:
python3 "ai相关好玩的/ai日历/generate_calendar.py" --month 2026-012.2 第 2 段:咒语拼合(把想象交给模型)
build_prompt.py:把 CSV 注入到绘图 Prompt 模版(多风格模板在ai相关好玩的/ai日历/templates/)
示例:
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日历/
build_background_prompt.py:生成“背景专用 Prompt”(不含日历表格)render_calendar_overlay.py:把calendar_YYYY-MM.csv排版成透明overlay.pngcompose_calendar.py:把background.png + overlay.png合成final.png
依赖:Pillow(你当前环境已可用)。
4. ComfyUI 批量出图(推荐命令)
目录:测试comfyui_副本/demo/comfyui-zimage-demo/
ComfyUI(Z-Image)端点示例:
https://deployment-318-klu0niut-8188.550w.link
若遇到自签证书:设置INSECURE_TLS=1(仅测试用)。
4.0 在共绩算力平台上跑(预制镜像复刻版)
如果你想“最省心地复刻”,推荐直接用 共绩算力平台 的预制镜像:
-
创建实例/容器
-
镜像选择:预制 ComfyUI(生图/Z-Image)镜像
-
暴露端口
8188,拿到你的 ComfyUI 访问地址(下文用https://<your-comfyui-8188-url>表示) -
将本项目放进实例(
git clone或上传) -
运行下面 4.1 的命令即可
兼容性说明:脚本只调用 ComfyUI 标准 API:
/prompt+/history+/view。镜像的启动方式不同没关系,只要 8188 可访问即可。
4.1 批量生成:背景 + 代码排版叠加(日期永远正确)
- 配置(背景风格清单):
calendar_variants_background.json - 脚本(一键跑全流程):
batch_generate_calendars_composited.py
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输出:
-
最终成片:
测试comfyui_副本/demo/comfyui-zimage-demo/outputs/*_final.png -
背景原图:
.../*_bg.png -
透明日历层:
.../*_overlay.png -
背景 Prompt:
.../outputs/_calendar_prompts_composited/*.md
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,新增一条:
id:输出文件前缀title:你希望在画廊里展示的名字theme:这张背景图的审美说明(越具体越稳)width/height/steps/seed:控制比例与复现(seed 用于复刻同一张图)
然后重跑:
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 --force7. 常见问题(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. 复刻清单(文件一览)
ai相关好玩的/ai日历/generate_calendar.pyai相关好玩的/ai日历/build_prompt.pyai相关好玩的/ai日历/build_background_prompt.pyai相关好玩的/ai日历/render_calendar_overlay.pyai相关好玩的/ai日历/compose_calendar.py测试comfyui_副本/demo/comfyui-zimage-demo/calendar_variants_background.json测试comfyui_副本/demo/comfyui-zimage-demo/batch_generate_calendars_composited.py
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 argparseimport calendarimport csvimport datetime as dtfrom dataclasses import dataclassfrom pathlib import Pathfrom 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 argparseimport datetime as dtfrom pathlib import Pathfrom 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 argparseimport csvfrom dataclasses import dataclassfrom pathlib import Pathfrom 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 argparsefrom pathlib import Pathfrom 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.png5) compose_calendar.py 合成 final.png(日期永远正确)"""
from __future__ import annotations
import argparseimport jsonimport osimport subprocessimport sysfrom pathlib import Pathfrom 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 }]