
Apiyi Gpt Image 2 All Gen
- 44 installs
- 33 repo stars
- Updated July 5, 2026
- wuchubuzai2018/expert-skills-hub
Helps with backend & apis tasks.
About
apiyi-gpt-image-2-all-gen is a Claude Code skill for backend & apis. It helps solo builders move faster with AI-assisted development.
- apiyi-gpt-image-2-all-gen
- Backend & APIs
- AI-coding skill
Apiyi Gpt Image 2 All Gen by the numbers
- 44 all-time installs (skills.sh)
- Ranked #3,265 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Jul 27, 2026 (Skillselion catalog sync)
npx skills add https://github.com/wuchubuzai2018/expert-skills-hub --skill apiyi-gpt-image-2-all-genAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 44 |
|---|---|
| repo stars | ★ 33 |
| Last updated | July 5, 2026 |
| Repository | wuchubuzai2018/expert-skills-hub ↗ |
What it does
Helps with backend & apis tasks.
Files
图片生成与编辑(GPT Image 2 All)Skills
基于API易平台的ChatGPT最新生图gpt-image-2-all模型实现图片生成技能,可以通过自然语言帮助用户生成图片,通过API易国内代理服务访问,支持Node.js和Python两种运行环境。gpt-image-2-all是API易平台上线的一款GPT图像生成官逆模型,以 $0.03/张 的极具竞争力的按次计费定价,约60秒到300秒出图,支持文生图/单图编辑/多图融合/自然语言改图,文字还原度高、内容限制少、原生支持中文提示词。
使用指引
遵循以下步骤:
第1步:分析需求与参数提取
1. 明确意图:区分用户是需要【文生图】(生成新图片)还是【图生图】(编辑/修改现有图片)或【多图融合】。 2. 提示词(Prompt)分析:
- 使用用户原始完整输入:把用户输入的原始完整问题需求描述(原文)直接作为
-p提示词的主体,避免自行改写、总结或二次创作,防止细节丢失。 - 需要补充时先确认:如果信息不足(例如缺少风格、主体数量、镜头语言、场景细节、文字内容、禁止元素等),先向用户提问确认;用户确认后,再把补充内容以"追加"的方式拼接到原始提示词后。
- 样例:
- 用户输入:"帮我生成一张猫的图片,风格要可爱一点。"
- 正例说明:直接使用用户输入作为提示词:
-p "帮我生成一张猫的图片,风格要可爱一点。" - 反例说明:擅自改写为"生成一张可爱风格的猫的图片"会丢失用户原始输入的细节和语气。
- 如果需要补充细节(例如颜色、背景等),先提问确认:"你希望猫是什么颜色的?背景有什么要求吗?"用户回答后,再追加到提示词中:
-p "帮我生成一张猫的图片,风格要可爱一点。猫是橘色的,背景是草地。"
3. 关键参数整理:
- Prompt(必需):提示词分析后的最终提示词(默认=用户原始完整且一致的输入;仅在用户确认后才追加补充信息)。
- Filename(可选):输出图片文件名/路径(需包含文件随机标识,避免重复)。不传则脚本会自动生成带时间戳的文件名。建议根据内容生成合理文件名(例如
cat_in_garden.png),避免使用通用名。 - Size/Aspect(可选):由于该模型无显式size参数,尺寸通过prompt描述控制。建议在prompt开头描述尺寸。
- "手机壁纸" -> 在prompt开头写
竖版 9:16或手机海报 9:16 - "电脑壁纸/视频封面" -> 在prompt开头写
横版 16:9或电影画幅 16:9 - "头像" -> 在prompt开头写
1:1 方形构图或1024×1024 方图 - 默认若用户未明确指定图片比例,保持图片比例为空(由模型自适应)。
- Response Format(可选):响应格式,默认
url(R2 CDN加速链接),可选b64_json(base64图片数据)。 - 注意:该模型不支持
size、n、quality、aspect_ratio参数,传入可能触发参数校验错误。
第2步:环境检查与命令执行
1. 检查环境:确认 APIYI_API_KEY 环境变量是否已设置(通常假定已设置,若运行失败再提示用户)。 2. 构建并运行命令:
- 优先尝试 Node.js 版本:如果环境有 Node(
node命令可用),优先使用scripts/generate_image.js(零依赖,参数与 Python 保持一致)。 - Node 不可用再用 Python 版本:使用
scripts/generate_image.py。
文生图命令模板(优先 Node.js):
node scripts/generate_image.js -p "{prompt}" -f "{filename}" [-r {response_format}]图生图命令模板(优先 Node.js):
node scripts/generate_image.js -p "{edit_instruction}" -i "{input_path}" -f "{output_filename}" [-r {response_format}]多图融合命令模板(优先 Node.js):
node scripts/generate_image.js -p "融合图1和图2的风格" -i ref1.png ref2.png -f "merged.png" [-r {response_format}](可选)Python 版本命令模板(Node 不可用时):
python scripts/generate_image.py -p "{prompt}" -f "{filename}" [-r {response_format}]
python scripts/generate_image.py -p "{edit_instruction}" -i "{input_path}" -f "{output_filename}" [-r {response_format}]⏱️ 长时间任务处理策略
1. 任务前提示
执行前必须告知用户:
- "图片生成已启动,预计需要60秒到300秒"
2. 🎨 最佳实践示例
"图片生成中,预计60秒到300秒完成...\n⏳ 正在生成..."
第3步:结果反馈
1. 执行反馈:等待终端命令执行完毕。 2. 成功:告知用户图片已生成,并指出保存路径。 3. 失败:
- 若提示 API Key 缺失,请指导用户设置环境变量。
- 若提示网络错误,建议用户检查网络或稍后重试。
命令行使用样例
生成新图片
python scripts/generate_image.py -p "图片描述文本" -f "output.png" [-r url|b64_json]示例:
# 基础生成
python scripts/generate_image.py -p "一只可爱的橘猫在草地上玩耍" -f "cat.png"
# 指定尺寸(在prompt开头描述)
python scripts/generate_image.py -p "横版 16:9 电影画幅,日落山脉风景" -f "sunset.png"
# 竖版高清图片(适合手机壁纸)
python scripts/generate_image.py -p "竖版 9:16 手机海报,城市夜景" -f "city.png"(可选)Node.js 版本示例:
# 基础生成
node scripts/generate_image.js -p "一只可爱的橘猫在草地上玩耍" -f "cat.png"
# 指定尺寸
node scripts/generate_image.js -p "横版 16:9 电影画幅,日落山脉风景" -f "sunset.png"编辑已有图片
python scripts/generate_image.py -p "编辑指令" -f "output.png" -i "path/to/input.png"示例:
# 修改风格
python scripts/generate_image.py -p "将图片转换成水彩画风格" -f "watercolor.png" -i "original.png"
# 添加元素
python scripts/generate_image.py -p "在天空添加彩虹" -f "rainbow.png" -i "landscape.png"
# 替换背景
python scripts/generate_image.py -p "将背景换成海滩" -f "beach-bg.png" -i "portrait.png"(可选)Node.js 版本示例:
# 修改风格
node scripts/generate_image.js -p "将图片转换成水彩画风格" -f "watercolor.png" -i "original.png"
# 多张参考图融合(最多5张)
node scripts/generate_image.js -p "融合图1和图2的风格" -i ref1.png ref2.png -f "merged.png"命令行参数说明
Python 与 Node.js 版本参数保持一致(短参数与长参数等价)。
| 参数 | 必填 | 说明 |
|---|---|---|
-p / --prompt | 是 | 图片描述(文生图)或编辑指令(图生图)。保留用户原始完整输入。 |
-f / --filename | 否 | 输出图片路径/文件名;不传则自动生成带时间戳的 PNG 文件名,并写入当前目录。 |
-r / --response-format | 否 | 响应格式:url(默认,R2 CDN链接)或 b64_json(base64图片数据)。 |
-i / --input-image | 否 | 图生图输入图片路径;可传多张(最多5张)。传入该参数即进入编辑模式。 |
文件资源说明
| 资源 | 说明 |
|---|---|
| `scripts/generate_image.js` | Node.js 版本(零依赖,优先使用) |
| `scripts/generate_image.py` | Python 版本(备选) |
| `references/size-guide.md` | 尺寸与比例控制文档,需要时使用,按需加载 |
| `references/batch-template.md` | 批量生成配置模板,需要批量生成时使用,按需加载 |
批量生成
当用户需要一次性生成多张图片(批量生成)时:
1. 加载配置模板:references/batch-template.md — 包含 JSON 配置格式说明和使用示例 2. 获取/生成 JSON 文件:用户可自行提供 JSON 文件,或描述需求后 AI 根据需求生成 3. 预处理 prompt:确保每个 prompt 开头包含尺寸描述(如"横版 16:9"),必要时补充 global.size_hint 4. 逐个执行:读取 prompts 数组,逐个执行生成命令,每张完成后反馈结果 5. 汇总反馈:完成后告知成功数量、图片路径列表
注意:批量任务总时间 = 单张时间(60-300秒) × 图片数量,请提前告知用户预估时长。
图片比例说明
由于gpt-image-2-all模型没有size参数,尺寸通过prompt描述控制。经验证较稳定的写法:
| 需求 | 推荐写法 |
|---|---|
| 方形 | 1024×1024 方图 / 1:1 方形构图 |
| 横版 | 横版 16:9 / 宽屏 16:9 电影画幅 |
| 竖版 | 竖版 9:16 / 手机海报 9:16 |
| 超宽横幅 | 横幅 21:9 超宽银幕 |
| 经典印刷 | 4:3 标准画幅 / 3:2 经典画幅 |
技巧:在prompt开头描述尺寸/构图,模型遵循度更高。可搭配画幅风格词(如 电影画幅、手机海报、方形构图)进一步提升一致性。
响应格式说明
url(默认)
默认返回 R2 CDN 加速链接,有效期约24小时。适用于Web应用直接渲染。对于需要长期保存的图片,请生成后立即转存到自己的对象存储。
b64_json
返回 base64 编码的图片数据(已含 data:image/png;base64, 前缀)。适用于:
- 服务端需要直接处理图片数据
- 需要写入本地文件
- 前端直接渲染
注意事项
- API密钥必须设置,可通过环境变量或命令行参数提供
- 图片生成时间:约60秒到300秒
- 编辑图片时,输入图片会自动转换为base64编码
- 确保输出目录有写入权限
- 该模型不支持
size、n、quality、aspect_ratio参数 - 默认响应的 url 字段是 R2 CDN 加速链接,有效期约24小时
API Key设置与获取
如何获取API Key
如果你还没有API密钥,请前往 https://api.apiyi.com 注册账号并申请API Key。
获取步骤: 1. 访问 https://api.apiyi.com 2. 注册/登录你的账号 3. 在控制台中创建API密钥 4. 复制密钥并设置环境变量或在命令行中使用
设置API Key
脚本从环境变量 APIYI_API_KEY 获取API密钥。
设置环境变量:
# Linux/Mac
export APIYI_API_KEY="your-api-key-here"
# Windows CMD
set APIYI_API_KEY=your-api-key-here
# Windows PowerShell
$env:APIYI_API_KEY="your-api-key-here"API端点说明
主推端点:POST /v1/chat/completions
对话式端点——相比 /v1/images/generations 和 /v1/images/edits,对话式端点对提示词遵循更好,并且同一端点同时支持文生图与带参考图改图,可以天然做多轮迭代。
- 仅输入文本 messages → 文生图
- messages 中加入 image_url(URL 或 base64 data URL)→ 带参考图改图
- 保留 assistant 历史消息继续追问 → 多轮迭代改图
模型信息
- 模型名:gpt-image-2-all
- 出图速度:约 60-300 秒
- 输出分辨率:无显式 size 参数,由模型自适应(建议在 prompt 中描述)
- 默认响应格式:url(R2 CDN 加速链接,默认 1 天有效期)
- 可选响应格式:b64_json
- 中文提示词:✅ 原生支持
- 支持能力:文生图、单图编辑、多图融合、自然语言改图
- 价格:$0.03/张
作者介绍
- 爱海贼的无处不在
- 我的微信公众号:无处不在的技术
批量生成提示词模板
当用户需要一次性生成多张图片时,可使用本文档定义的 JSON 格式配置批量生成任务。
JSON 文件格式
{
"description": "批量任务描述(可选)",
"global": {
"response_format": "url",
"size_hint": "横版 16:9"
},
"prompts": [
{
"prompt": "提示词1描述(包含尺寸)",
"filename": "output1.png",
"response_format": "url"
},
{
"prompt": "提示词2描述(包含尺寸)",
"filename": "output2.png",
"response_format": "b64_json"
}
]
}字段说明
根级字段
| 字段 | 必填 | 说明 |
|---|---|---|
description | 否 | 批量任务描述,用于记录本次生成的目的 |
global | 否 | 全局默认参数,所有 prompts 共享此配置 |
prompts | 是 | 提示词数组,每个元素生成一张图片 |
global 字段
| 字段 | 必填 | 说明 |
|---|---|---|
response_format | 否 | 全局响应格式:url(默认)/ b64_json |
size_hint | 否 | 全局尺寸提示(会在每个 prompt 前自动追加) |
prompts 数组元素
| 字段 | 必填 | 说明 |
|---|---|---|
prompt | 是 | 图片描述(文生图)或编辑指令(图生图),建议在开头包含尺寸描述 |
filename | 否 | 输出文件名;不传则自动生成 |
response_format | 否 | 响应格式,覆盖全局配置:url / b64_json |
input_images | 否 | 图生图/多图融合时参考图路径(数组,最多5张) |
使用方法
1. 创建/获取 JSON 文件
用户可以通过以下方式提供 JSON 文件:
- 用户自行创建并提供文件路径
- 用户描述需求,AI 根据需求生成 JSON 文件
2. 读取并解析
AI 读取 JSON 文件后,逐个解析 prompts 数组中的每个对象。
3. 预处理 prompt
重要:gpt-image-2-all 模型需要在 prompt 开头描述尺寸,AI 需要确保每个 prompt 包含尺寸描述。
如果 prompt 中未包含尺寸,且 global.size_hint 未设置,应询问用户或自动补充常用尺寸(如"横版 16:9")。
4. 逐个生成
对每个提示词对象执行生成命令:
node scripts/generate_image.js -p "{prompt}" -f "{filename}" [-r {response_format}]执行策略:
- 逐个执行,每次生成完成后反馈结果
- 每张图片生成前提示用户预计等待时间
- 批量任务总时间 = 单张时间(60-300秒) × 图片数量
5. 结果反馈
生成完成后汇总反馈:
- 成功数量
- 各图片保存路径
- 失败项目及原因(如有)
示例场景
场景1:生成系列风格图
{
"description": "生成同一场景的不同风格版本",
"global": {
"response_format": "url",
"size_hint": "横版 16:9"
},
"prompts": [
{
"prompt": "横版 16:9 电影画幅,山间日出风景,写实摄影风格",
"filename": "sunrise_realistic.png"
},
{
"prompt": "横版 16:9 电影画幅,山间日出风景,水彩画风格",
"filename": "sunrise_watercolor.png"
},
{
"prompt": "横版 16:9 电影画幅,山间日出风景,动漫插画风格",
"filename": "sunrise_anime.png"
}
]
}场景2:生成产品多视角图
{
"description": "产品展示图多角度生成",
"global": {
"response_format": "url"
},
"prompts": [
{
"prompt": "1:1 方形构图,白色陶瓷咖啡杯,正面45度角拍摄,简洁白色背景",
"filename": "cup_front.png"
},
{
"prompt": "1:1 方形构图,白色陶瓷咖啡杯,侧面角度拍摄,简洁白色背景",
"filename": "cup_side.png"
},
{
"prompt": "1:1 方形构图,白色陶瓷咖啡杯,俯视角度拍摄,简洁白色背景",
"filename": "cup_top.png"
}
]
}场景3:生成社交媒体配图套装
{
"description": "小红书配图套装",
"prompts": [
{
"prompt": "1:1 方形构图,美食摄影,精致甜点摆盘,暖色调",
"filename": "content_01.png",
"response_format": "url"
},
{
"prompt": "竖版 9:16 手机海报,生活场景,明亮窗边阅读角",
"filename": "content_02.png",
"response_format": "url"
},
{
"prompt": "横版 16:9 电影画幅,旅行风景,海边日落剪影",
"filename": "content_03.png",
"response_format": "url"
}
]
}注意事项
1. 尺寸优先级:单个 prompt 中的尺寸描述优先于 global.size_hint 2. prompt 预处理:AI 必须在执行前确保每个 prompt 包含尺寸描述(开头) 3. 文件名建议:使用有意义的文件名,避免重复 4. 预估时间:每张图约 60-300 秒,批量任务需预留充足时间 5. 图生图支持:prompts 数组元素中可包含 input_images 字段进行多图融合
尺寸与比例控制指南
本文档说明如何在 GPT Image 2 All(gpt-image-2-all)中控制输出图片的尺寸和比例。
控制方式
gpt-image-2-all 模型没有显式的 size 参数,尺寸通过在 prompt 开头描述来控制。
推荐写法
| 需求 | 推荐写法 |
|---|---|
| 方形 | 1024×1024 方图 / 1:1 方形构图 / 方形构图 |
| 横版 16:9 | 横版 16:9 / 宽屏 16:9 / 电影画幅 16:9 |
| 竖版 9:16 | 竖版 9:16 / 手机海报 9:16 / 手机壁纸 9:16 |
| 超宽横幅 21:9 | 横幅 21:9 / 超宽银幕 / 21:9 电影画幅 |
| 经典印刷 4:3 | 4:3 标准画幅 / 经典画幅 |
| 经典印刷 3:2 | 3:2 经典画幅 / 单反比例 |
| 超长竖版 | 超长竖版构图 / 1:4 竖版 |
| 超宽横版 | 超宽横版 / 4:1 横幅 |
使用示例
# 方形图片
node scripts/generate_image.js -p "1:1 方形构图,一只可爱的橘猫在草地上玩耍" -f "cat.png"
# 横版电影画幅
node scripts/generate_image.js -p "横版 16:9 电影画幅,日落山脉风景" -f "sunset.png"
# 竖版手机壁纸
node scripts/generate_image.js -p "竖版 9:16 手机壁纸,城市夜景" -f "city.png"
# 超宽银幕
node scripts/generate_image.js -p "横幅 21:9 超宽银幕,海岸线全景" -f "panorama.png"技巧
1. 位置很重要:在 prompt 开头描述尺寸,模型遵循度更高 2. 搭配风格词:可配合构图风格词提升一致性
- 电影画幅 → 更有电影感
- 手机海报 → 适合手机展示
- 方形构图 → 简洁大方
3. 组合描述:可以将尺寸和风格组合使用
- "横版 16:9 电影画幅"
- "竖版 9:16 手机壁纸风格"
注意事项
1. gpt-image-2-all 没有显式的 size 参数,尺寸完全依赖 prompt 描述 2. 建议使用经验证较稳定的写法(见上方推荐写法表格) 3. 描述越具体越清晰,模型遵循度越高 4. 默认若用户未明确指定图片比例,保持图片比例为空(由模型自适应)
#!/usr/bin/env node
/*
基于GPT Image 2 All的图片生成与编辑脚本(Node.js版)
使用API易国内代理服务
支持功能:
- 文生图:根据提示词生成图片
- 图生图:根据编辑指令修改已有图片
- 多图融合:参考多张图片融合
参数说明:
- -p, --prompt 图片描述或编辑指令文本(必需)
- -f, --filename 输出图片路径(可选,默认自动生成时间戳文件名)
- -r, --response-format 响应格式(可选:url/b64_json,默认url)
- -i, --input-image 输入图片路径(可选,可多张,最多5张)
使用示例:
【生成新图片】
node generate_image.js -p "一只可爱的橘猫"
node generate_image.js -p "横版 16:9 电影画幅,日落山脉" -f sunset.png
node generate_image.js -p "竖版 9:16 手机海报,城市夜景" -f city.png
【编辑已有图片】
node generate_image.js -p "转换成油画风格" -i original.png
node generate_image.js -p "添加彩虹到天空" -i photo.jpg -f edited.png
node generate_image.js -p "将背景换成海滩" -i portrait.png -f beach-bg.png
【多图融合】
node generate_image.js -p "融合图1和图2的风格" -i ref1.png ref2.png -f merged.png
【环境变量】
export APIYI_API_KEY="your-api-key"
*/
const fs = require('fs');
const path = require('path');
const https = require('https');
const SUPPORTED_RESPONSE_FORMATS = ['url', 'b64_json'];
function printHelpAndExit(exitCode = 0) {
const help = `usage: generate_image.js [-h] --prompt PROMPT [--filename FILENAME]
[--response-format url|b64_json]
[--input-image INPUT_IMAGE [INPUT_IMAGE ...]]
基于GPT Image 2 All的图片生成与编辑工具(Node.js版)
options:
-h, --help show this help message and exit
-p, --prompt PROMPT 图片描述或编辑指令文本(必需)
-f, --filename FILE 输出图片路径 (默认: 自动生成时间戳文件名)
-r, --response-format 响应格式 (可选: url, b64_json,默认url)
-i, --input-image 输入图片路径(编辑模式,可传多张,最多5张)
尺寸说明(通过prompt描述,无显式size参数):
- 方形: 1024×1024 方图 / 1:1 方形构图
- 横版: 横版 16:9 / 宽屏 16:9 电影画幅
- 竖版: 竖版 9:16 / 手机海报 9:16
- 超宽: 横幅 21:9 超宽银幕
- 印刷: 4:3 标准画幅 / 3:2 经典画幅
运行示例:
node scripts/generate_image.js -p "一只可爱的橘猫"
node scripts/generate_image.js -p "横版 16:9 电影画幅,日落山脉" -f sunset.png
node scripts/generate_image.js -p "转换成油画风格" -i original.png
node scripts/generate_image.js -p "融合图1和图2的风格" -i ref1.png ref2.png -f merged.png
`;
process.stdout.write(help);
process.exit(exitCode);
}
function exitWithError(message) {
process.stderr.write(`${message}\n`);
process.exit(1);
}
function pad2(n) {
return String(n).padStart(2, '0');
}
function formatTimestamp(dateObj) {
const d = dateObj || new Date();
return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}-${pad2(d.getHours())}-${pad2(d.getMinutes())}-${pad2(d.getSeconds())}`;
}
function addTimestampToFilename(filePath, timestamp) {
const ts = timestamp || formatTimestamp(new Date());
const parsed = path.parse(filePath);
const base = parsed.name ? `${parsed.name}-${ts}` : ts;
return path.join(parsed.dir || '.', `${base}${parsed.ext || ''}`);
}
function generateFilename(prompt) {
const now = new Date();
const timestamp = formatTimestamp(now);
const keywords = String(prompt).split(/\s+/).filter(Boolean).slice(0, 3);
const keywordStrRaw = keywords.join('-') || 'image';
const keywordStr = keywordStrRaw
.split('')
.map((c) => (/^[a-zA-Z0-9\-_.]$/.test(c) ? c : '-'))
.join('')
.toLowerCase()
.slice(0, 30);
return `${timestamp}-${keywordStr}.png`;
}
function getApiKey() {
const apiKey = process.env.APIYI_API_KEY;
if (!apiKey) {
exitWithError(
'错误: 未设置 APIYI_API_KEY 环境变量\n' +
'请前往 https://api.apiyi.com 注册申请API Key'
);
}
return apiKey;
}
function encodeImageToBase64(imagePath) {
try {
const bytes = fs.readFileSync(imagePath);
return bytes.toString('base64');
} catch (e) {
exitWithError(`错误: 无法读取图片文件 ${imagePath} - ${e.message || String(e)}`);
}
}
function postJson(urlString, headers, payload, timeoutMs) {
return new Promise((resolve, reject) => {
const url = new URL(urlString);
const body = Buffer.from(JSON.stringify(payload), 'utf8');
const req = https.request(
{
protocol: url.protocol,
hostname: url.hostname,
port: url.port || 443,
path: url.pathname + url.search,
method: 'POST',
headers: {
...headers,
'Content-Length': body.length,
},
},
(res) => {
const chunks = [];
res.on('data', (d) => chunks.push(d));
res.on('end', () => {
const text = Buffer.concat(chunks).toString('utf8');
const statusCode = res.statusCode || 0;
if (statusCode < 200 || statusCode >= 300) {
const err = new Error(`HTTP ${statusCode}`);
err.statusCode = statusCode;
err.responseText = text;
return reject(err);
}
try {
resolve(JSON.parse(text));
} catch (e) {
const err = new Error('响应不是有效的JSON');
err.responseText = text;
return reject(err);
}
});
}
);
req.on('error', reject);
req.setTimeout(timeoutMs, () => {
req.destroy(new Error('timeout'));
});
req.write(body);
req.end();
});
}
function parseArgs(argv) {
const args = {
prompt: null,
filename: null,
responseFormat: null,
inputImages: null,
};
const knownFlags = new Set([
'-h',
'--help',
'-p',
'--prompt',
'-f',
'--filename',
'-r',
'--response-format',
'-i',
'--input-image',
]);
function requireValue(i, flag) {
const v = argv[i + 1];
if (!v || (v.startsWith('-') && knownFlags.has(v))) {
exitWithError(`错误: 参数 ${flag} 需要一个值`);
}
return v;
}
for (let i = 0; i < argv.length; i++) {
const a = argv[i];
if (a === '-h' || a === '--help') {
printHelpAndExit(0);
}
if (a === '-p' || a === '--prompt') {
args.prompt = requireValue(i, a);
i++;
continue;
}
if (a === '-f' || a === '--filename') {
args.filename = requireValue(i, a);
i++;
continue;
}
if (a === '-r' || a === '--response-format') {
args.responseFormat = requireValue(i, a);
i++;
continue;
}
if (a === '-i' || a === '--input-image') {
const images = [];
let j = i + 1;
while (j < argv.length) {
const v = argv[j];
if (v.startsWith('-') && knownFlags.has(v)) break;
images.push(v);
j++;
}
if (images.length === 0) {
exitWithError(`错误: 参数 ${a} 需要至少一个图片路径`);
}
args.inputImages = images;
i = j - 1;
continue;
}
if (a.startsWith('-')) {
exitWithError(`错误: 未知参数 ${a},请使用 --help 查看帮助`);
}
}
if (!args.prompt) {
exitWithError('错误: 缺少必需参数 -p/--prompt');
}
return args;
}
function extractImageUrl(content) {
if (!content) return null;
const urlMatch = content.match(/(https?:\/\/[^\s)]+\.(png|jpg|jpeg|webp))/i);
if (urlMatch) return urlMatch[1];
const b64Match = content.match(/(data:image\/[^\s;]+;base64,[A-Za-z0-9+/=]+)/);
if (b64Match) return b64Match[1];
return null;
}
async function downloadImage(urlString) {
return new Promise((resolve, reject) => {
const url = new URL(urlString);
const req = https.get(
{
protocol: url.protocol,
hostname: url.hostname,
port: url.port || 443,
path: url.pathname + url.search,
},
(res) => {
if (res.statusCode < 200 || res.statusCode >= 300) {
const err = new Error(`HTTP ${res.statusCode}`);
err.statusCode = res.statusCode;
return reject(err);
}
const chunks = [];
res.on('data', (d) => chunks.push(d));
res.on('end', () => resolve(Buffer.concat(chunks)));
}
);
req.on('error', reject);
req.setTimeout(30000, () => {
req.destroy(new Error('timeout'));
});
});
}
async function downloadBase64Image(urlString) {
const imageBuffer = await downloadImage(urlString);
return imageBuffer.toString('base64');
}
async function main() {
const argv = process.argv.slice(2);
const args = parseArgs(argv);
const runTimestamp = formatTimestamp(new Date());
let checkProgress = null;
const clearProgressTimer = () => {
if (checkProgress) {
clearInterval(checkProgress);
checkProgress = null;
}
};
if (args.responseFormat != null && !SUPPORTED_RESPONSE_FORMATS.includes(args.responseFormat)) {
exitWithError(
`错误: 不支持的响应格式 '${args.responseFormat}'\n支持的格式: ${SUPPORTED_RESPONSE_FORMATS.join(', ')}`
);
}
if (!args.filename) {
args.filename = generateFilename(args.prompt);
} else {
const resolved = path.resolve(args.filename);
if (fs.existsSync(resolved)) {
const adjusted = addTimestampToFilename(args.filename, runTimestamp);
process.stdout.write(`⚠️ 输出文件已存在,将避免覆盖并改为: ${adjusted}\n`);
args.filename = adjusted;
}
}
const apiKey = getApiKey();
const url = 'https://api.apiyi.com/v1/chat/completions';
const headers = {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
};
let content = [];
let modeStr = '生成图片';
if (args.inputImages && args.inputImages.length > 0) {
if (args.inputImages.length > 5) {
exitWithError(`错误: 输入图片最多支持5张,当前为 ${args.inputImages.length} 张`);
}
for (let idx = 0; idx < args.inputImages.length; idx++) {
const imgPath = args.inputImages[idx];
if (!fs.existsSync(imgPath)) {
exitWithError(`错误: 输入图片不存在: ${imgPath}`);
}
const imageBase64 = encodeImageToBase64(imgPath);
const dataUrl = `data:image/png;base64,${imageBase64}`;
content.push({
type: 'image_url',
image_url: { url: dataUrl },
});
}
modeStr = args.inputImages.length === 1 ? '编辑图片' : '多图融合';
content = [
{
type: 'text',
text: args.prompt,
},
...content,
];
} else {
content = args.prompt;
}
const payload = {
model: 'gpt-image-2-all',
messages: [
{
role: 'user',
content: content,
},
],
};
if (args.responseFormat === 'b64_json') {
payload.response_format = { type: 'b64_json' };
}
process.stdout.write('🎨 图片生成已启动!\n');
process.stdout.write(`⏱️ 预计时间: 约60秒到300秒\n`);
process.stdout.write(`正在${modeStr}...\n`);
process.stdout.write(`提示词: ${args.prompt}\n`);
process.stdout.write('image generation in progress...\n');
const startTime = Date.now();
checkProgress = setInterval(() => {
const elapsed = Math.floor((Date.now() - startTime) / 1000);
process.stdout.write(`🔄 已进行 ${elapsed}秒...\n`);
}, 5000);
let data;
try {
data = await postJson(url, headers, payload, 300_000);
} catch (e) {
clearProgressTimer();
if (e && e.message === 'timeout') {
exitWithError('错误: 请求超时,请稍后重试');
}
if (e && e.statusCode) {
process.stderr.write(`错误: 请求失败 - HTTP ${e.statusCode}\n`);
if (e.responseText) {
try {
const detail = JSON.parse(e.responseText);
process.stderr.write(`错误详情: ${JSON.stringify(detail, null, 2)}\n`);
} catch {
process.stderr.write(`响应内容: ${e.responseText}\n`);
}
}
process.exit(1);
}
exitWithError(`错误: 请求失败 - ${e.message || String(e)}`);
}
clearProgressTimer();
const responseContent =
data &&
data.choices &&
Array.isArray(data.choices) &&
data.choices[0] &&
data.choices[0].message &&
data.choices[0].message.content;
if (!responseContent) {
process.stderr.write('错误: 响应中未找到内容\n');
process.stderr.write(`完整响应: ${JSON.stringify(data, null, 2)}\n`);
process.exit(1);
}
let imageData = null;
if (args.responseFormat === 'b64_json') {
const b64Match = responseContent.match(/data:image\/png;base64,([A-Za-z0-9+/=]+)/);
if (b64Match) {
imageData = b64Match[1];
}
}
if (!imageData) {
const imageUrl = extractImageUrl(responseContent);
if (imageUrl) {
if (imageUrl.startsWith('data:')) {
imageData = imageUrl.replace(/^data:image\/png;base64,/, '');
} else {
process.stdout.write(`📥 正在下载图片...\n`);
imageData = await downloadBase64Image(imageUrl);
}
}
}
if (!imageData) {
process.stderr.write('错误: 未能从响应中提取图片数据\n');
process.stderr.write(`响应内容: ${responseContent}\n`);
process.exit(1);
}
const imageBytes = Buffer.from(imageData, 'base64');
const outputFile = path.resolve(args.filename);
const outputDir = path.dirname(outputFile);
fs.mkdirSync(outputDir, { recursive: true });
fs.writeFileSync(outputFile, imageBytes);
process.stdout.write(`✓ 图片已成功${modeStr}并保存到: ${args.filename}\n`);
process.stdout.write('✅ 生成完成!\n');
}
main().catch((e) => {
exitWithError(`错误: ${e && e.message ? e.message : String(e)}`);
});#!/usr/bin/env python3
"""
基于GPT Image 2 All的图片生成与编辑脚本(Python版)
使用API易国内代理服务
支持功能:
- 文生图:根据提示词生成图片
- 图生图:根据编辑指令修改已有图片
- 多图融合:参考多张图片融合
参数说明:
- -p, --prompt 图片描述或编辑指令文本(必需)
- -f, --filename 输出图片路径(可选,默认自动生成时间戳文件名)
- -r, --response-format 响应格式(可选:url/b64_json,默认url)
- -i, --input-image 输入图片路径(可选,可多张,最多5张)
- -k, --api-key API密钥(可选,覆盖环境变量 APIYI_API_KEY)
使用示例:
【生成新图片】
python generate_image.py -p "一只可爱的橘猫"
python generate_image.py -p "横版 16:9 电影画幅,日落山脉" -f sunset.png
python generate_image.py -p "竖版 9:16 手机海报,城市夜景" -f city.png
【编辑已有图片】
python generate_image.py -p "转换成油画风格" -i original.png
python generate_image.py -p "添加彩虹到天空" -i photo.jpg -f edited.png
python generate_image.py -p "将背景换成海滩" -i portrait.png -f beach-bg.png
【多图融合】
python generate_image.py -p "融合图1和图2的风格" -i ref1.png ref2.png -f merged.png
【环境变量】
export APIYI_API_KEY="your-api-key"
"""
import os
import sys
import re
import json
import base64
import argparse
import datetime
from pathlib import Path
from typing import Optional, List, Dict, Any, Union
try:
import requests
except ImportError:
print("错误: 需要安装 requests 库,请运行: pip install requests")
sys.exit(1)
SUPPORTED_RESPONSE_FORMATS = ['url', 'b64_json']
DEFAULT_RESPONSE_FORMAT = 'url'
DEFAULT_TIMEOUT = 300
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description='基于GPT Image 2 All的图片生成与编辑工具(Python版)',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='''
尺寸说明(通过prompt描述,无显式size参数):
- 方形: 1024×1024 方图 / 1:1 方形构图
- 横版: 横版 16:9 / 宽屏 16:9 电影画幅
- 竖版: 竖版 9:16 / 手机海报 9:16
- 超宽: 横幅 21:9 超宽银幕
- 印刷: 4:3 标准画幅 / 3:2 经典画幅
运行示例:
python scripts/generate_image.py -p "一只可爱的橘猫"
python scripts/generate_image.py -p "横版 16:9 电影画幅,日落山脉" -f sunset.png
python scripts/generate_image.py -p "转换成油画风格" -i original.png
python scripts/generate_image.py -p "融合图1和图2的风格" -i ref1.png ref2.png -f merged.png
'''
)
parser.add_argument('-p', '--prompt', required=True, help='图片描述或编辑指令文本(必需)')
parser.add_argument('-f', '--filename', default=None, help='输出图片路径 (默认: 自动生成时间戳文件名)')
parser.add_argument('-r', '--response-format', default=DEFAULT_RESPONSE_FORMAT,
choices=SUPPORTED_RESPONSE_FORMATS,
help='响应格式 (默认: url)')
parser.add_argument('-i', '--input-image', nargs='+', default=None,
help='输入图片路径(编辑模式,可传多张,最多5张)')
parser.add_argument('-k', '--api-key', default=None, help='API密钥(覆盖环境变量)')
return parser.parse_args()
def get_api_key(args_key: Optional[str]) -> str:
if args_key:
return args_key
api_key = os.environ.get('APIYI_API_KEY')
if not api_key:
print('错误: 未设置 APIYI_API_KEY 环境变量', file=sys.stderr)
print('请前往 https://api.apiyi.com 注册申请API Key', file=sys.stderr)
print('或使用 -k/--api-key 参数临时指定', file=sys.stderr)
sys.exit(1)
return api_key
def encode_image_to_base64(image_path: str) -> str:
try:
with open(image_path, 'rb') as f:
return base64.b64encode(f.read()).decode()
except Exception as e:
print(f'错误: 无法读取图片文件 {image_path} - {e}', file=sys.stderr)
sys.exit(1)
def generate_filename(prompt: str) -> str:
now = datetime.datetime.now()
timestamp = now.strftime('%Y-%m-%d-%H-%M-%S')
keywords = str(prompt).split()[:3]
keyword_str = '-'.join(keywords) if keywords else 'image'
keyword_str = ''.join(c if c.isalnum() or c in '-_.' else '-' for c in keyword_str)
keyword_str = keyword_str.lower()[:30]
return f'{timestamp}-{keyword_str}.png'
def add_timestamp_to_filename(file_path: str, timestamp: str) -> str:
path = Path(file_path)
name = path.stem
ext = path.suffix
new_name = f'{name}-{timestamp}{ext}'
return str(path.parent / new_name)
def extract_image_url(content: str) -> Optional[str]:
if not content:
return None
url_match = re.search(r'(https?://[^\s)]+\.(png|jpg|jpeg|webp))', content, re.IGNORECASE)
if url_match:
return url_match.group(1)
b64_match = re.search(r'(data:image/[^;]+;base64,[A-Za-z0-9+/=]+)', content)
if b64_match:
return b64_match.group(1)
return None
def download_image(url_string: str) -> bytes:
try:
response = requests.get(url_string, timeout=30)
if response.status_code < 200 or response.status_code >= 300:
print(f'错误: 下载图片失败 - HTTP {response.status_code}', file=sys.stderr)
sys.exit(1)
return response.content
except requests.exceptions.Timeout:
print('错误: 下载图片超时', file=sys.stderr)
sys.exit(1)
except requests.exceptions.RequestException as e:
print(f'错误: 下载图片失败 - {e}', file=sys.stderr)
sys.exit(1)
def download_base64_image(url_string: str) -> str:
image_buffer = download_image(url_string)
return base64.b64encode(image_buffer).decode()
def main():
args = parse_args()
timestamp = datetime.datetime.now().strftime('%Y-%m-%d-%H-%M-%S')
if args.response_format not in SUPPORTED_RESPONSE_FORMATS:
print(f"错误: 不支持的响应格式 '{args.response_format}'", file=sys.stderr)
print(f"支持的格式: {', '.join(SUPPORTED_RESPONSE_FORMATS)}", file=sys.stderr)
sys.exit(1)
if not args.filename:
args.filename = generate_filename(args.prompt)
else:
resolved = Path(args.filename).resolve()
if resolved.exists():
adjusted = add_timestamp_to_filename(args.filename, timestamp)
print(f'⚠️ 输出文件已存在,将避免覆盖并改为: {adjusted}')
args.filename = adjusted
api_key = get_api_key(args.api_key)
url = 'https://api.apiyi.com/v1/chat/completions'
headers = {
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json',
}
content = []
mode_str = '生成图片'
if args.input_image and len(args.input_image) > 0:
if len(args.input_image) > 5:
print(f'错误: 输入图片最多支持5张,当前为 {len(args.input_image)} 张', file=sys.stderr)
sys.exit(1)
for img_path in args.input_image:
if not Path(img_path).exists():
print(f'错误: 输入图片不存在: {img_path}', file=sys.stderr)
sys.exit(1)
image_base64 = encode_image_to_base64(img_path)
data_url = f'data:image/png;base64,{image_base64}'
content.append({
'type': 'image_url',
'image_url': {'url': data_url}
})
mode_str = '编辑图片' if len(args.input_image) == 1 else '多图融合'
content = [
{
'type': 'text',
'text': args.prompt,
},
*content,
]
else:
content = args.prompt
payload = {
'model': 'gpt-image-2-all',
'messages': [
{
'role': 'user',
'content': content,
},
],
}
if args.response_format == 'b64_json':
payload['response_format'] = {'type': 'b64_json'}
print('🎨 图片生成已启动!')
print(f'⏱️ 预计时间: 约60秒到300秒')
print(f'正在{mode_str}...')
print(f'提示词: {args.prompt}')
print('image generation in progress...')
start_time = datetime.datetime.now()
try:
response = requests.post(url, headers=headers, json=payload, timeout=DEFAULT_TIMEOUT)
response.raise_for_status()
data = response.json()
except requests.exceptions.Timeout:
print('错误: 请求超时,请稍后重试', file=sys.stderr)
sys.exit(1)
except requests.exceptions.HTTPError as e:
print(f'错误: 请求失败 - {e}', file=sys.stderr)
try:
error_detail = e.response.json()
print(f'错误详情: {json.dumps(error_detail, indent=2, ensure_ascii=False)}', file=sys.stderr)
except:
print(f'响应内容: {e.response.text}', file=sys.stderr)
sys.exit(1)
except requests.exceptions.RequestException as e:
print(f'错误: 请求失败 - {e}', file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError:
print('错误: 响应不是有效的JSON', file=sys.stderr)
sys.exit(1)
elapsed = (datetime.datetime.now() - start_time).total_seconds()
print(f'⏱️ 生成完成,耗时 {elapsed:.1f}秒')
response_content = None
if data and data.get('choices') and len(data['choices']) > 0:
choice = data['choices'][0]
if choice.get('message'):
response_content = choice['message'].get('content')
if not response_content:
print('错误: 响应中未找到内容', file=sys.stderr)
print(f'完整响应: {json.dumps(data, indent=2, ensure_ascii=False)}', file=sys.stderr)
sys.exit(1)
image_data = None
if args.response_format == 'b64_json':
b64_match = re.search(r'data:image/png;base64,([A-Za-z0-9+/=]+)', response_content)
if b64_match:
image_data = b64_match.group(1)
if not image_data:
image_url = extract_image_url(response_content)
if image_url:
if image_url.startswith('data:'):
image_data = image_url.replace('data:image/png;base64,', '')
else:
print('📥 正在下载图片...')
image_data = download_base64_image(image_url)
if not image_data:
print('错误: 未能从响应中提取图片数据', file=sys.stderr)
print(f'响应内容: {response_content}', file=sys.stderr)
sys.exit(1)
try:
image_bytes = base64.b64decode(image_data)
except Exception as e:
print(f'错误: 图片数据解码失败 - {e}', file=sys.stderr)
sys.exit(1)
output_file = Path(args.filename).resolve()
output_dir = output_file.parent
output_dir.mkdir(parents=True, exist_ok=True)
output_file.write_bytes(image_bytes)
print(f'✓ 图片已成功{mode_str}并保存到: {args.filename}')
print('✅ 生成完成!')
if __name__ == '__main__':
main()