首页
看点啥
插画图片
首页 看点啥 从零用Codex搭建一个脚本分镜图片生成器的流程步骤

从零用Codex搭建一个脚本分镜图片生成器的流程步骤

2026-07-10 0

零、效果图

一、为什么做这个工具

做短视频、做动画、做漫画脚本的人,几乎都绕不开一个环节——分镜

传统流程是:编剧写好脚本 → 画师根据文字描述画出每一镜的画面 → 反复修改。这个过程慢、贵、且沟通成本高。

于是我想:能不能让 AI 直接根据每一句分镜描述生成图片?

答案是肯定的。现在的图像生成模型(GPT-Image-2、豆包 Seedream 系列)已经能根据文字描述生成相当可用的画面。我要做的,就是把"输入文字 → 出图 → 保存"这个流程封装成一个简单好用的 Web 工具。

二、需求是怎么一步步清晰的

刚开始我的想法很朴素:"调用 OpenAI 的画图 API,输入提示词,出图保存"。

但真正动手前,我和 AI 助手做了一轮 brainstorming,把需求一点点抠清楚。这个过程很重要,模糊的需求一定会导致返工

第 1 轮:交互形态

命令行还是 Web 页面?

我选了 Web 页面。理由很简单:分镜创作是个视觉活,命令行黑框框看着没感觉,Web 页面能直接预览图片、能下载、能反复调整。

第 2 轮:技术栈

前端用什么框架?

我选了 纯 HTML + 原生 JavaScript。不引任何前端框架。为什么?因为这个工具逻辑极简——一个输入框、一个按钮、一张图。上 React 纯属杀鸡用牛刀,反而增加构建复杂度。原生 JS 一个 IIFE 就搞定了。

后端用 Flask。Python 生态对 API 调用友好,Flask 又是最轻量的 Web 框架,单文件就能跑。

第 3 轮:API Key 怎么管

环境变量还是页面输入?

最初我选了页面输入(方便演示),但很快意识到这有安全隐患。最后改成 .env 配置文件 + python-dotenv 自动加载.env.gitignore 排除,密钥永不进版本库。这是更工程化的做法。

第 4 轮:用哪个模型

这是关键转折点。我一开始说"调 OpenAI 的画图 API",但实际我想用的是 gpt-image-2、gpt-image-2-vip、gpt-image-2-highdoubao-seedream-4-0/4-5/5-0 这几个模型。

这些模型并非 OpenAI 官方直出,而是通过一个第三方代&理服务提供的(兼容 OpenAI 接口格式)。这一步确认后,后面所有 API 调用逻辑都围绕第三方的文档来写。

第 5 轮:要不要做视频

豆包 Seedream 系列其实也能生成视频。但我果断说 只做图片

理由:视频生成耗时长(分钟级)、文件大、还要轮询任务状态,复杂度比图片高一个数量级。YAGNI(You Ain't Gonna Need It)——先把图片做稳,视频以后再说。这个决策让整个项目复杂度降了一个档。

三、读懂 API 文档:从同步到异步的踩坑

这是整个项目最关键的技术转折。

我最初按 OpenAI 标准接口写代码:

resp = requests.post(f"{API_BASE_URL}/v1/images/generations", json=payload, headers=headers)
result = resp.json()
image_url = result["data"][0]["url"]  # 直接拿图

结果发现——ToAPIs 的图像生成是异步的

打开文生图官方文档,返回值长这样:

{
  "id": "task_img_abc123def456",
  "object": "generation.task",
  "model": "gpt-image-2",
  "status": "queued",
  "progress": 0,
  "created_at": 1703884800
}

注意:第一次请求只返回 task_id 和状态,不返回图片! 你得拿着 task_id 去另一个接口轮询:

GET /v1/images/generations/{task_id}

直到 status 变成 completed,才能在 result.data[0].url 拿到真正的图片地址。

文档里还贴心地给了轮询策略建议:

初始等待: 2 秒
轮询间隔: 3 秒
最大等待: 120 秒
典型耗时: 5-30 秒

还有一个坑:生成的图片 URL 有效期只有 24 小时。所以必须在拿到 URL 后立刻下载到本地,不能只存 URL。

于是后端逻辑变成了三步走:

  1. 提交任务 → 拿 task_id
  2. 轮询状态 → 每 3 秒查一次,最多 40 次(约 120 秒)
  3. 下载图片 → 保存到 ./images/,返回本地 URL 给前端
# 提交任务
resp = requests.post(f"{API_BASE_URL}/v1/images/generations", json=payload, headers=headers, timeout=30)
task_id = resp.json().get("id")

# 轮询
for attempt in range(40):
    time.sleep(3)
    status_resp = requests.get(f"{API_BASE_URL}/v1/images/generations/{task_id}", headers=headers, timeout=30)
    status_data = status_resp.json()

    if status_data.get("status") == "completed":
        image_url = status_data["result"]["data"][0]["url"]
        # 下载并保存
        img_resp = requests.get(image_url, timeout=30)
        filename = f"image_{int(time.time())}_{os.urandom(4).hex()}.png"
        with open(os.path.join(IMAGES_DIR, filename), "wb") as f:
            f.write(img_resp.content)
        return jsonify({"image_url": f"/images/{filename}"})
    elif status_data.get("status") == "failed":
        return jsonify({"error": "图片生成失败"}), 500

另外还有一个模型差异细节:Seedream 系列的分辨率参数要走 metadata.resolution,而 GPT-Image-2 系列直接用 resolution 顶层字段。后端要根据 model 名字判断走哪条路:

if model.startswith("doubao-seedream") and resolution:
    payload["metadata"] = {"resolution": resolution}

这种"看文档才发现的细节",是远程 API 集成最耗时的部分。没有捷径,就是读文档 + 试错。

四、前端:从单输入框到多输入框的演进

第一版前端就一个 textarea,用户把所有分镜塞进去,用换行分隔。简单,但不好用:

于是改成 动态多输入框

核心就是一段 DOM 操作:

function addPromptGroup(promptText) {
  var group = document.createElement("div");
  group.className = "prompt-group";

  var textarea = document.createElement("textarea");
  textarea.className = "promptInput";
  // ...

  var removeBtn = document.createElement("button");
  removeBtn.textContent = "删除";
  removeBtn.addEventListener("click", function() {
    group.remove();
  });

  group.appendChild(textarea);
  group.appendChild(removeBtn);
  promptsContainer.appendChild(group);
}

收集所有提示词时遍历一遍:

function getPrompts() {
  var inputs = promptsContainer.querySelectorAll(".promptInput");
  var prompts = [];
  for (var i = 0; i < inputs.length; i++) {
    var val = inputs[i].value.trim();
    if (val.length > 0) prompts.push(val);
  }
  return prompts;
}

五、依次生成 vs 并行生成:两种模式的设计

分镜往往一画就是十几张。如果一张一张串行生成,10 张图每张 20 秒就要等 3 分钟。如果并行,理论上 20 秒就能全拿回来。

所以加了「处理模式」下拉框:

依次生成(sequential)

for (var i = 0; i < prompts.length; i++) {
  var img = await generateOne(prompts[i], model, size, resolution);
  appendResult({ prompt: prompts[i], url: img.image_url, index: i });
}

特点:一张完成才生成下一张。好处是省 API 配额、出错好定位、前端能看到逐张出现的过程。坏处是慢。

并行生成(parallel)

var tasks = prompts.map(function(p, i) {
  return generateOne(p, model, size, resolution)
    .then(function(img) {
      appendResult({ prompt: p, url: img.image_url, index: i });
    })
    .catch(function(err) {
      appendResult({ prompt: p, error: err.message, index: i });
    });
});
await Promise.all(tasks);

特点:所有任务同时发出,谁先回来谁先显示。快,但要注意的限流(429 错误)。如果配额吃紧,建议用依次模式。

两种模式都用 try/catch 包裹单张生成,一张失败不影响其他张——这对分镜这种"批量但单张独立"的场景很重要。

六、那些调试中的小坑

坑 1:点击生成按钮没反应

部署完第一次点按钮,毫无响应。打开浏览器控制台,发现:

GET http://127.0.0.1:5000/script.js 404 (NOT FOUND)

原因:Flask 默认静态文件目录是 static/,访问路径必须是 /static/script.js,不是 /script.js。改一行:






坑 2:API_BASE_URL 写成了完整路径

.env 里我一开始写了:

API_BASE_URL=https://xxxxx.com/v1/images/generations

app.py 里又拼了一遍 /v1/images/generations,导致最终 URL 变成:

https://xxxx.com/v1/images/generations/v1/images/generations

404。改成只写根域名:

API_BASE_URL=https://xxxxx.com

配置项只放根地址,路径在代码里拼——这是惯例,避免重复。

坑 3:pip 命令找不到

PowerShell 里直接敲 pip install 报 "无法识别"。原因是没激活虚拟环境,且系统 Python 没把 pip 加进 PATH。

解法是用 py -m pip 或激活虚拟环境后用 python -m pip

venvScriptsActivate.ps1
pip install -r requirements.txt

七、最终项目结构

画图程序/
├── app.py              # Flask 后端:异步任务 + 轮询 + 本地保存
├── static/
│   ├── index.html      # 前端:多输入框 + 参数选择 + 结果展示
│   └── script.js       # 前端逻辑:多输入框管理、依次/并行生成
├── .env                # 配置:API_KEY + API_BASE_URL(不提交)
├── .env.example        # 配置模板
├── requirements.txt    # flask / requests / python-dotenv
├── .gitignore          # 排除 .env / images/ / venv/
├── README.md           # 使用文档
├── venv/               # 虚拟环境
└── images/             # 运行时生成图片存放

启动:

cd "D:Usersopencode画图程序"
venvScriptsActivate.ps1
python app.py

浏览器打开 http://127.0.0.1:5000,界面长这样:

点生成后,每张图独立卡片展示:序号 + 提示词预览 + 图片 + 任务 ID + 单独下载按钮。

八、几个关键设计决策的复盘

决策 1:为什么不用 React/Vue

这个工具前端逻辑总共不到 150 行 JS。引入 React 要配 Babel、Webpack、JSX,构建链比业务逻辑还复杂。技术选型要看场景,不是越新越好。 原生 JS + IIFE 对这种小工具是最佳解。

决策 2:为什么后端要做"下载图片到本地"这一步

选择生成的图片 URL 只有 24 小时有效期。如果前端直接用这个 URL,第二天分镜文件就全裂图了。所以后端必须把图片下载到本地 images/ 目录,前端拿的是本地路径 /images/xxx.png,永久有效。

这也是为什么有个 /images/ 路由——专门serve本地图片。

决策 3:为什么用 .env 而不是环境变量

环境变量要每次启动前手动 $env:API_KEY = "...",换台机器就忘。.env 文件跟着项目走(虽然不进 git),改一次永久生效,用 python-dotenv 一行加载:

from dotenv import load_dotenv
load_dotenv()
API_KEY = os.getenv("API_KEY")

开发体验比纯环境变量好太多。

最后修改这个文件,修改这两个变量API_BASE_URL 和 API_KEY为实际值:

决策 4:为什么单张失败不阻塞其他张

分镜生成是"批量但单张独立"的场景——第 3 张失败了,第 4、5、6 张照样该生成。所以每张生成单独 try/catch,失败的那张显示错误信息,成功的照常出图。这个容错设计让工具在弱网或 API 抖动时也不至于全盘崩溃。

九、后续可以加什么

这个版本是 MVP(最小可用版本)。如果要继续打磨,我会考虑:

  1. 批量打包下载 —— 生成完 10 张图后,一键打包成 zip 下载,省得一张张点
  2. 提示词模板 —— 内置几套分镜模板(如"电影感""赛博朋克""水墨"),点一下自动填到输入框
  3. 历史记录 —— 把每次生成的 prompt + 图片路径存到 SQLite,方便回溯
  4. 图生图(reference_images) —— 上传一张参考图,让 AI 基于它改风格。ToAPIs 文档里这个能力是现成的,前端加个上传组件即可
  5. 进度条 —— 轮询时把 progress 字段(0-100)回传前端,做个真进度条,而不是干等

十、总结

打开后页面样式

输入提示词后等待样式

生成后结果

这个项目本身不复杂,但有几个点值得新手注意:

  1. 需求要抠清楚再动手 —— 一轮 brainstorming 省下后面 3 轮返工
  2. 读 API 文档比写代码重要 —— 同步还是异步、参数走顶层还是 metadata、URL 有效期多久,这些细节不看文档全是坑
  3. 小工具别上重框架 —— 原生 JS + Flask 单文件,部署简单、改起来快、看代码一目了然
  4. 容错要从单点做 —— 批量任务里每条单独 try/catch,一条挂了不影响其他
  5. 密钥管理要工程化 —— .env + .gitignore,别图省事硬编码
喜欢(0)

上一篇

苹果se一代电池容量为多少毫安

苹果se一代电池容量为多少毫安

下一篇

Claude Code运维实战:用AI Skill构建自动化运维新范式

Claude Code运维实战:用AI Skill构建自动化运维新范式
猜你喜欢