feat: 新增 gen-image 生图技能
通过大模型中转站调用 OpenAI 图片接口生图(文生图/图生图/局部重绘/透明背景),零第三方依赖。 含可安装 zip 与可浏览源码。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,18 @@
|
|||||||
# backup
|
# backup-bot / skills 备份仓库
|
||||||
|
|
||||||
自动备份仓库
|
个人技能备份 / 分享库,供 TD工具箱(codex-market)安装。
|
||||||
|
|
||||||
|
## 技能清单
|
||||||
|
|
||||||
|
| 技能 | 分类 | 说明 | 安装包 |
|
||||||
|
|------|------|------|--------|
|
||||||
|
| **gen-image · 生图** | 图片生成 | 通过大模型中转站调用 OpenAI 图片接口生图(文生图/图生图/局部重绘/透明背景),零依赖 | `categories/图片生成/28772__gen-image.zip` |
|
||||||
|
|
||||||
|
## gen-image 用法
|
||||||
|
|
||||||
|
首次需在 skill 目录 `cp .env.example .env` 并填入中转站 `IMAGE_API_BASE_URL` / `IMAGE_API_KEY`,然后:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gen.sh "提示词" --size 1024x1024 --quality high # 文生图
|
||||||
|
./gen.sh "换成雪山背景" --edit input.png # 图生图
|
||||||
|
```
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,12 @@
|
|||||||
|
# 大模型中转站 · OpenAI 图片接口生图配置
|
||||||
|
# 复制本文件为 .env 并填入你的真实值。不要提交 .env 到 git。
|
||||||
|
|
||||||
|
# 中转站基址(不要带 /v1/images/... 后缀,脚本会自动补全)
|
||||||
|
# 例:https://api.your-relay.com
|
||||||
|
IMAGE_API_BASE_URL=https://your-relay.com
|
||||||
|
|
||||||
|
# 中转站分配的 API Key
|
||||||
|
IMAGE_API_KEY=sk-your-key-here
|
||||||
|
|
||||||
|
# 默认生图模型(可用 --model 临时覆盖)
|
||||||
|
IMAGE_MODEL=gpt-image-2
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# 凭证,绝不提交
|
||||||
|
.env
|
||||||
|
|
||||||
|
# 生图输出
|
||||||
|
output/
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# AGENTS.md — 生图能力(供 Codex 等 Agent 自动读取)
|
||||||
|
|
||||||
|
本目录是一个「生图」工具:通过大模型中转站调用 OpenAI 图片接口,生成位图图像。
|
||||||
|
当用户表达以下意图时,**应主动调用本工具**,而不是自己画 SVG/HTML 或拒绝:
|
||||||
|
|
||||||
|
- 「生成一张图 / 画一张 / AI 出图 / 做个头像·海报·插画·图标·壁纸」
|
||||||
|
- 「文生图 / image generation」
|
||||||
|
- 给了一张图并说「基于它改 / 换背景 / 局部重绘」→ 图生图
|
||||||
|
|
||||||
|
## 如何调用
|
||||||
|
|
||||||
|
统一入口是本目录下的 `gen.sh`(无论当前工作目录在哪,都用绝对/相对路径定位到它):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 文生图(默认 1024x1024)
|
||||||
|
./gen.sh "a fluffy orange cat on a windowsill, soft morning light, highly detailed"
|
||||||
|
|
||||||
|
# 指定尺寸 / 画质 / 张数
|
||||||
|
./gen.sh "cyberpunk city poster" --size 1536x1024 --quality high --n 2
|
||||||
|
|
||||||
|
# 透明背景(图标 / 贴纸)
|
||||||
|
./gen.sh "a minimalist mountain logo, flat vector" --background transparent
|
||||||
|
|
||||||
|
# 图生图 / 编辑(用户提供了输入图)
|
||||||
|
./gen.sh "把背景换成雪山日落" --edit /path/to/input.png
|
||||||
|
|
||||||
|
# 局部重绘(mask PNG 中透明处 = 要重绘的区域)
|
||||||
|
./gen.sh "戴上一顶红帽子" --edit input.png --mask mask.png
|
||||||
|
```
|
||||||
|
|
||||||
|
生成的图片保存在本目录 `output/`,命令会打印完整路径。把该路径告诉用户。
|
||||||
|
|
||||||
|
## 参数速查
|
||||||
|
|
||||||
|
| 需求 | 参数 |
|
||||||
|
|------|------|
|
||||||
|
| 方图(头像/图标) | `--size 1024x1024` |
|
||||||
|
| 横图(海报/封面) | `--size 1536x1024` |
|
||||||
|
| 竖图(壁纸/人像) | `--size 1024x1536` |
|
||||||
|
| 透明背景 | `--background transparent` |
|
||||||
|
| 画质 | `--quality low\|medium\|high\|auto` |
|
||||||
|
| 多张挑选 | `--n 4` |
|
||||||
|
| 换模型 | `--model dall-e-3` |
|
||||||
|
| 图生图 | `--edit 图片路径`(可多次) |
|
||||||
|
| 局部重绘 | `--edit 图.png --mask mask.png` |
|
||||||
|
|
||||||
|
## 使用要点
|
||||||
|
|
||||||
|
1. **先打磨提示词**:用户描述通常很短,先扩写成信息充分的提示词(主体、风格、光线、构图、色调、画质词)再调用。除非用户要求逐字使用。
|
||||||
|
2. **凭证**:脚本自动读本目录 `.env`(含中转站地址与 API Key),无需在命令里传 key。**不要把 key 打印到对话里,也不要提交 `.env`。**
|
||||||
|
3. **报错处理**:
|
||||||
|
- 401/403 → key 无效或额度不足(403 error 1010 是 WAF 拦截,脚本已带浏览器 UA 规避)。
|
||||||
|
- 404 → base_url 或接口路径不对。
|
||||||
|
- 400 model 相关 → 中转站不认该模型,换 `--model dall-e-3` 或问用户支持的模型名。
|
||||||
|
4. 不适用:HTML 原型、幻灯片、矢量图标、图表可视化——那些应直接写代码,不要用本工具。
|
||||||
|
|
||||||
|
## 前置条件
|
||||||
|
|
||||||
|
- 需要 `python3`(macOS 自带,零第三方依赖)。
|
||||||
|
- 首次使用需存在 `.env`(从 `.env.example` 复制并填入 `IMAGE_API_BASE_URL`、`IMAGE_API_KEY`)。
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# gen-image · 生图 Skill
|
||||||
|
|
||||||
|
通过大模型中转站,直接调用 OpenAI 图片接口生图。文生图 / 图生图 / 局部重绘 / 透明背景,零第三方依赖。
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 配置凭证
|
||||||
|
cp .env.example .env
|
||||||
|
# 编辑 .env,填入 IMAGE_API_BASE_URL、IMAGE_API_KEY
|
||||||
|
|
||||||
|
# 2. 文生图
|
||||||
|
python3 scripts/gen_image.py "a cozy cabin in snowy mountains, golden hour, cinematic" \
|
||||||
|
--size 1536x1024 --quality high
|
||||||
|
|
||||||
|
# 3. 图生图 / 编辑
|
||||||
|
python3 scripts/gen_image.py "把背景换成星空" --edit input.png
|
||||||
|
|
||||||
|
# 4. 局部重绘(mask 透明处为重绘区域)
|
||||||
|
python3 scripts/gen_image.py "戴上一顶红帽子" --edit input.png --mask mask.png
|
||||||
|
```
|
||||||
|
|
||||||
|
图片保存到 `output/`,脚本打印完整路径。
|
||||||
|
|
||||||
|
## 配置项(.env)
|
||||||
|
|
||||||
|
| 变量 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `IMAGE_API_BASE_URL` | 中转站基址,如 `https://your-relay.com`(自动补 `/v1/images/...`) |
|
||||||
|
| `IMAGE_API_KEY` | 中转站 API Key |
|
||||||
|
| `IMAGE_MODEL` | 默认模型,默认 `gpt-image-2`,可用 `--model` 覆盖 |
|
||||||
|
|
||||||
|
## 参数
|
||||||
|
|
||||||
|
`--size` `--n` `--quality` `--background transparent` `--edit`(可多次) `--mask` `--model` `--output`
|
||||||
|
详见 `python3 scripts/gen_image.py --help` 与 `SKILL.md`。
|
||||||
|
|
||||||
|
## 安全
|
||||||
|
|
||||||
|
`.env` 与 `output/` 已在 `.gitignore` 中,不会提交。切勿把 API Key 写进代码或对话。
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
---
|
||||||
|
name: gen-image
|
||||||
|
description: 调用大模型中转站的 OpenAI 图片接口生图(文生图、图生图/编辑、局部重绘、透明背景)。触发词:生图、生成图片、画一张、AI 画图、文生图、图生图、生成插画/头像/海报/图标、image generation、根据这张图改。需要外部 API 生成位图图像时使用;纯 HTML/SVG/矢量设计不适用。
|
||||||
|
---
|
||||||
|
|
||||||
|
# gen-image · 生图
|
||||||
|
|
||||||
|
通过大模型中转站,直接调用 OpenAI 图片接口生成位图图像。支持**文生图**、**图生图/编辑**、**局部重绘(mask)**、**透明背景**。
|
||||||
|
|
||||||
|
核心是零依赖脚本 `scripts/gen_image.py`(纯 Python 标准库,无需 pip install)。
|
||||||
|
|
||||||
|
## 何时用 / 不用
|
||||||
|
|
||||||
|
- ✅ 用户要「生成一张图 / 画一张 / AI 出图 / 做个头像/海报/插画/图标」
|
||||||
|
- ✅ 用户给了一张图要「基于它改 / 换背景 / 局部重绘」→ 图生图
|
||||||
|
- ❌ 用户要 HTML 原型、幻灯片、矢量图标、CSS 效果 → 用 `huashu-design` 或直接写代码
|
||||||
|
- ❌ 用户要图表/数据可视化 → 用 `dataviz`
|
||||||
|
|
||||||
|
## 首次使用:配置凭证
|
||||||
|
|
||||||
|
生图需要中转站的 API Key。检查 skill 根目录是否有 `.env`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls "$(dirname SKILL.md)/.env" 2>/dev/null || echo "未配置"
|
||||||
|
```
|
||||||
|
|
||||||
|
若无,从模板创建并让用户填写(**不要把 key 写进对话或提交到 git**):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
# 然后引导用户编辑 .env,填入 IMAGE_API_BASE_URL 和 IMAGE_API_KEY
|
||||||
|
```
|
||||||
|
|
||||||
|
`.env` 需要三项:
|
||||||
|
- `IMAGE_API_BASE_URL` — 中转站基址,如 `https://your-relay.com`(脚本自动补 `/v1/images/...`)
|
||||||
|
- `IMAGE_API_KEY` — API Key
|
||||||
|
- `IMAGE_MODEL` — 默认模型,默认 `gpt-image-2`
|
||||||
|
|
||||||
|
## 标准流程
|
||||||
|
|
||||||
|
**1. 打磨提示词。** 用户给的描述往往简短,先在心里扩写成一句信息充分的英文或中文提示词——主体、风格、光线、构图、色调、画质关键词。除非用户明确要求逐字使用,否则主动增强。生图模型对细节敏感,「一只猫」远不如「a fluffy orange tabby cat sitting on a windowsill, soft morning light, shallow depth of field, cozy warm tones」。
|
||||||
|
|
||||||
|
**2. 选参数:**
|
||||||
|
- `--size`:`1024x1024`(方,头像/图标)、`1536x1024`(横,海报/封面)、`1024x1536`(竖,手机壁纸/人像)、`auto`
|
||||||
|
- `--quality`:`low`/`medium`/`high`/`auto`(gpt-image 系支持;越高越慢越贵)
|
||||||
|
- `--background transparent`:要透明底的图标/贴纸时加
|
||||||
|
- `--n`:一次出几张(默认 1;多给几张让用户挑时用 `--n 2` 或 `4`)
|
||||||
|
|
||||||
|
**3. 执行(文生图):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd "<skill 根目录>" && python3 scripts/gen_image.py \
|
||||||
|
"a fluffy orange tabby cat on a windowsill, soft morning light, cozy tones, highly detailed" \
|
||||||
|
--size 1024x1024 --quality high
|
||||||
|
```
|
||||||
|
|
||||||
|
图片存到 `output/img-<时间戳>-<序号>.png`,脚本会打印完整路径。
|
||||||
|
|
||||||
|
**4. 图生图 / 编辑**(用户给了输入图):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 scripts/gen_image.py "把背景换成雪山日落" --edit /path/to/input.png --size 1024x1024
|
||||||
|
```
|
||||||
|
|
||||||
|
多图参考:多次 `--edit a.png --edit b.png`。局部重绘:加 `--mask mask.png`(PNG 中**透明区域**为要重绘的部分,其余保留)。
|
||||||
|
|
||||||
|
**5. 交付。** 打印出保存路径后,简要告诉用户生成了什么、存在哪。图无法在终端直接显示,用户会自己打开;若在支持预览的环境可提示路径。
|
||||||
|
|
||||||
|
## 常见参数速查
|
||||||
|
|
||||||
|
| 需求 | 命令片段 |
|
||||||
|
|------|---------|
|
||||||
|
| 头像/图标(方图) | `--size 1024x1024` |
|
||||||
|
| 海报/封面(横) | `--size 1536x1024` |
|
||||||
|
| 手机壁纸/人像(竖) | `--size 1024x1536` |
|
||||||
|
| 透明背景贴纸 | `--background transparent` |
|
||||||
|
| 多出几张挑选 | `--n 4` |
|
||||||
|
| 高画质 | `--quality high` |
|
||||||
|
| 换模型 | `--model dall-e-3` |
|
||||||
|
| 基于图改 | `--edit input.png` |
|
||||||
|
| 局部重绘 | `--edit input.png --mask mask.png` |
|
||||||
|
|
||||||
|
## 排错
|
||||||
|
|
||||||
|
- **缺 IMAGE_API_BASE_URL / IMAGE_API_KEY** → `.env` 没配好,回到「配置凭证」。
|
||||||
|
- **HTTP 401/403** → key 无效或额度不足,让用户核对中转站账户。
|
||||||
|
- **HTTP 404** → base_url 写错,或该中转站不支持 `/v1/images/generations`;确认接口路径。
|
||||||
|
- **HTTP 400 model 相关** → 该中转站不认 `gpt-image-2`,换 `--model dall-e-3` 或问用户支持的模型名。
|
||||||
|
- **响应没有图片数据** → 部分中转站返回结构不同,把打印的原始响应给用户看以便排查。
|
||||||
|
|
||||||
|
不要把 API Key 输出到对话里,也不要提交 `.env`。
|
||||||
Executable
+13
@@ -0,0 +1,13 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# gen.sh — 生图快捷入口。用法:
|
||||||
|
# ./gen.sh "提示词" # 文生图,默认 1024x1024
|
||||||
|
# ./gen.sh "提示词" --size 1536x1024 # 透传任意 gen_image.py 参数
|
||||||
|
# ./gen.sh "换成雪山背景" --edit in.png # 图生图
|
||||||
|
# 无论从哪个目录调用都能正确定位脚本与 .env。
|
||||||
|
set -euo pipefail
|
||||||
|
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
if [ "$#" -eq 0 ]; then
|
||||||
|
echo "用法: ./gen.sh \"提示词\" [--size 1024x1024] [--quality high] [--edit 图.png] ..." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
exec python3 "$DIR/scripts/gen_image.py" "$@"
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
gen_image.py — 通过大模型中转站调用 OpenAI 图片接口生图(文生图 / 图生图)。
|
||||||
|
|
||||||
|
零第三方依赖,仅用 Python 标准库。凭证从环境变量或 skill 目录下的 .env 读取。
|
||||||
|
|
||||||
|
用法:
|
||||||
|
# 文生图
|
||||||
|
python3 gen_image.py "一只戴墨镜的柴犬,赛博朋克霓虹背景" --size 1024x1024 --n 1
|
||||||
|
|
||||||
|
# 图生图 / 编辑(可选 --mask 做局部重绘)
|
||||||
|
python3 gen_image.py "把背景换成雪山" --edit input.png --mask mask.png
|
||||||
|
|
||||||
|
环境变量(也可写进 .env):
|
||||||
|
IMAGE_API_BASE_URL 中转站基址,如 https://your-relay.com (会自动补 /v1/images/...)
|
||||||
|
IMAGE_API_KEY API Key
|
||||||
|
IMAGE_MODEL 默认模型,如 gpt-image-2(默认 gpt-image-2)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
import mimetypes
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import urllib.error
|
||||||
|
import urllib.request
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 配置加载
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
def load_dotenv():
|
||||||
|
"""从脚本上级目录(skill 根)的 .env 读取 KEY=VALUE,不覆盖已存在的环境变量。"""
|
||||||
|
skill_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
env_path = os.path.join(skill_root, ".env")
|
||||||
|
if not os.path.isfile(env_path):
|
||||||
|
return
|
||||||
|
with open(env_path, "r", encoding="utf-8") as f:
|
||||||
|
for line in f:
|
||||||
|
line = line.strip()
|
||||||
|
if not line or line.startswith("#") or "=" not in line:
|
||||||
|
continue
|
||||||
|
key, _, val = line.partition("=")
|
||||||
|
key, val = key.strip(), val.strip().strip('"').strip("'")
|
||||||
|
os.environ.setdefault(key, val)
|
||||||
|
|
||||||
|
|
||||||
|
def get_config():
|
||||||
|
base = os.environ.get("IMAGE_API_BASE_URL", "").rstrip("/")
|
||||||
|
key = os.environ.get("IMAGE_API_KEY", "")
|
||||||
|
model = os.environ.get("IMAGE_MODEL", "gpt-image-2")
|
||||||
|
if not base:
|
||||||
|
die("缺少 IMAGE_API_BASE_URL(中转站基址)。请在 .env 中配置,参考 .env.example。")
|
||||||
|
if not key:
|
||||||
|
die("缺少 IMAGE_API_KEY。请在 .env 中配置,参考 .env.example。")
|
||||||
|
# 允许用户直接填到 /v1,做个兼容归一
|
||||||
|
if base.endswith("/v1"):
|
||||||
|
base = base[: -len("/v1")]
|
||||||
|
return base, key, model
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# HTTP 辅助
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
def die(msg, code=1):
|
||||||
|
print(f"[gen_image] 错误: {msg}", file=sys.stderr)
|
||||||
|
sys.exit(code)
|
||||||
|
|
||||||
|
|
||||||
|
# 部分中转站前置 Cloudflare/WAF,会按 UA 拦截默认的 Python-urllib(error 1010)。
|
||||||
|
# 用常规浏览器 UA 规避。
|
||||||
|
BROWSER_UA = (
|
||||||
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
|
||||||
|
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _request(url, key, data, content_type):
|
||||||
|
req = urllib.request.Request(url, data=data, method="POST")
|
||||||
|
req.add_header("Authorization", f"Bearer {key}")
|
||||||
|
req.add_header("Content-Type", content_type)
|
||||||
|
req.add_header("User-Agent", BROWSER_UA)
|
||||||
|
req.add_header("Accept", "application/json")
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req, timeout=300) as resp:
|
||||||
|
return json.loads(resp.read().decode("utf-8"))
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
body = e.read().decode("utf-8", "replace")
|
||||||
|
die(f"HTTP {e.code} — {body[:800]}")
|
||||||
|
except urllib.error.URLError as e:
|
||||||
|
die(f"网络错误: {e.reason}")
|
||||||
|
|
||||||
|
|
||||||
|
def post_json(url, key, payload):
|
||||||
|
return _request(url, key, json.dumps(payload).encode("utf-8"), "application/json")
|
||||||
|
|
||||||
|
|
||||||
|
def post_multipart(url, key, fields, files):
|
||||||
|
"""fields: dict[str,str]; files: list[(field, filepath)]。手写 multipart,无依赖。"""
|
||||||
|
boundary = "----genimg" + uuid.uuid4().hex
|
||||||
|
buf = bytearray()
|
||||||
|
|
||||||
|
def w(s):
|
||||||
|
buf.extend(s.encode("utf-8") if isinstance(s, str) else s)
|
||||||
|
|
||||||
|
for name, value in fields.items():
|
||||||
|
if value is None:
|
||||||
|
continue
|
||||||
|
w(f"--{boundary}\r\n")
|
||||||
|
w(f'Content-Disposition: form-data; name="{name}"\r\n\r\n')
|
||||||
|
w(f"{value}\r\n")
|
||||||
|
|
||||||
|
for name, path in files:
|
||||||
|
if not path:
|
||||||
|
continue
|
||||||
|
fname = os.path.basename(path)
|
||||||
|
ctype = mimetypes.guess_type(path)[0] or "application/octet-stream"
|
||||||
|
with open(path, "rb") as fh:
|
||||||
|
content = fh.read()
|
||||||
|
w(f"--{boundary}\r\n")
|
||||||
|
w(f'Content-Disposition: form-data; name="{name}"; filename="{fname}"\r\n')
|
||||||
|
w(f"Content-Type: {ctype}\r\n\r\n")
|
||||||
|
w(content)
|
||||||
|
w("\r\n")
|
||||||
|
|
||||||
|
w(f"--{boundary}--\r\n")
|
||||||
|
return _request(url, key, bytes(buf), f"multipart/form-data; boundary={boundary}")
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 结果保存
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
def save_images(resp, out_dir, prefix):
|
||||||
|
data = resp.get("data")
|
||||||
|
if not data:
|
||||||
|
die(f"响应中没有图片数据。原始响应: {json.dumps(resp)[:800]}")
|
||||||
|
os.makedirs(out_dir, exist_ok=True)
|
||||||
|
saved = []
|
||||||
|
for i, item in enumerate(data):
|
||||||
|
ts = time.strftime("%Y%m%d-%H%M%S")
|
||||||
|
fname = f"{prefix}-{ts}-{i}.png"
|
||||||
|
fpath = os.path.join(out_dir, fname)
|
||||||
|
if item.get("b64_json"):
|
||||||
|
with open(fpath, "wb") as f:
|
||||||
|
f.write(base64.b64decode(item["b64_json"]))
|
||||||
|
elif item.get("url"):
|
||||||
|
try:
|
||||||
|
dl = urllib.request.Request(item["url"], headers={"User-Agent": BROWSER_UA})
|
||||||
|
with urllib.request.urlopen(dl, timeout=120) as r:
|
||||||
|
with open(fpath, "wb") as f:
|
||||||
|
f.write(r.read())
|
||||||
|
except Exception as e:
|
||||||
|
die(f"下载图片失败 {item['url']}: {e}")
|
||||||
|
else:
|
||||||
|
die(f"未知的图片条目格式: {json.dumps(item)[:400]}")
|
||||||
|
saved.append(fpath)
|
||||||
|
return saved
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 主流程
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
def main():
|
||||||
|
load_dotenv()
|
||||||
|
p = argparse.ArgumentParser(description="通过中转站调用 OpenAI 图片接口生图")
|
||||||
|
p.add_argument("prompt", help="生图提示词")
|
||||||
|
p.add_argument("--model", default=None, help="覆盖默认模型")
|
||||||
|
p.add_argument("--size", default="1024x1024", help="尺寸,如 1024x1024 / 1536x1024 / 1024x1536 / auto")
|
||||||
|
p.add_argument("--n", type=int, default=1, help="生成张数")
|
||||||
|
p.add_argument("--quality", default=None, help="质量: low/medium/high/auto(gpt-image 系)")
|
||||||
|
p.add_argument("--background", default=None, help="transparent/opaque/auto(gpt-image 系,透明背景)")
|
||||||
|
p.add_argument("--edit", action="append", default=[], help="图生图/编辑的输入图(可多次传入多图)")
|
||||||
|
p.add_argument("--mask", default=None, help="局部重绘蒙版 PNG(透明处为要重绘的区域)")
|
||||||
|
p.add_argument("--output", default=None, help="输出目录(默认 skill 根下 output/)")
|
||||||
|
args = p.parse_args()
|
||||||
|
|
||||||
|
base, key, default_model = get_config()
|
||||||
|
model = args.model or default_model
|
||||||
|
skill_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
out_dir = args.output or os.path.join(skill_root, "output")
|
||||||
|
|
||||||
|
if args.edit:
|
||||||
|
# 图生图 / 编辑:multipart 到 /v1/images/edits
|
||||||
|
url = f"{base}/v1/images/edits"
|
||||||
|
fields = {"model": model, "prompt": args.prompt, "n": str(args.n), "size": args.size}
|
||||||
|
if args.quality:
|
||||||
|
fields["quality"] = args.quality
|
||||||
|
if args.background:
|
||||||
|
fields["background"] = args.background
|
||||||
|
files = [("image[]", pth) for pth in args.edit] # 多图统一用 image[]
|
||||||
|
if len(args.edit) == 1:
|
||||||
|
files = [("image", args.edit[0])] # 单图用 image,兼容 dall-e-2 风格
|
||||||
|
if args.mask:
|
||||||
|
files.append(("mask", args.mask))
|
||||||
|
print(f"[gen_image] 图生图 · model={model} · {len(args.edit)} 张输入 · size={args.size}")
|
||||||
|
resp = post_multipart(url, key, fields, files)
|
||||||
|
prefix = "edit"
|
||||||
|
else:
|
||||||
|
# 文生图:JSON 到 /v1/images/generations
|
||||||
|
url = f"{base}/v1/images/generations"
|
||||||
|
payload = {"model": model, "prompt": args.prompt, "n": args.n, "size": args.size}
|
||||||
|
if args.quality:
|
||||||
|
payload["quality"] = args.quality
|
||||||
|
if args.background:
|
||||||
|
payload["background"] = args.background
|
||||||
|
print(f"[gen_image] 文生图 · model={model} · n={args.n} · size={args.size}")
|
||||||
|
resp = post_json(url, key, payload)
|
||||||
|
prefix = "img"
|
||||||
|
|
||||||
|
saved = save_images(resp, out_dir, prefix)
|
||||||
|
print(f"[gen_image] 完成,已保存 {len(saved)} 张:")
|
||||||
|
for s in saved:
|
||||||
|
print(f" {s}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user