
Apiyi Gpt Image 2 Gen
- 64 installs
- 33 repo stars
- Updated July 5, 2026
- wuchubuzai2018/expert-skills-hub
Helps with backend & apis tasks.
About
apiyi-gpt-image-2-gen is a Claude Code skill for backend & apis. It helps solo builders move faster with AI-assisted development.
- apiyi-gpt-image-2-gen
- Backend & APIs
- AI-coding skill
Apiyi Gpt Image 2 Gen by the numbers
- 64 all-time installs (skills.sh)
- +1 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #3,123 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-genAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 64 |
|---|---|
| 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 官方正式版)
基于API易平台的GPT Image 2模型(gpt-image-2)官方正式版实现图片生成技能,可以通过自然语言帮助用户生成图片,通过API易国内代理服务访问,支持Node.js和Python两种运行环境。gpt-image-2是API易平台的官方正式版GPT图像生成模型,支持精确的尺寸/画质控制(含4K),按token计费。
使用指引
遵循以下步骤:
第1步:分析需求与参数提取
1. 明确意图:区分用户是需要【文生图】(生成新图片)还是【图生图】(编辑/修改现有图片)或【多图融合】。 2. 提示词(Prompt)分析:
- 使用用户原始完整输入:把用户输入的原始完整问题需求描述(原文)直接作为
-p提示词的主体,避免自行改写、总结或二次创作,防止细节丢失。 - 需要补充时先确认:如果信息不足(例如缺少风格、主体数量、镜头语言、场景细节、文字内容、禁止元素等),先向用户提问确认;用户确认后,再把补充内容以"追加"的方式拼接到原始提示词后。
- 样例:
- 用户输入:"帮我生成一张猫的图片,风格要可爱一点。"
- 正例说明:直接使用用户输入作为提示词:
-p "帮我生成一张猫的图片,风格要可爱一点。" - 反例说明:擅自改写为"生成一张可爱风格的猫的图片"会丢失用户原始输入的细节和语气。
- 如果需要补充细节(例如颜色、背景等),先提问确认:"你希望猫是什么颜色的?背景有什么要求吗?"用户回答后,再追加到提示词中:
-p "帮我生成一张猫的图片,风格要可爱一点。猫是橘色的,背景是草地。"
3. 关键参数整理:
- Prompt(必需):提示词分析后的最终提示词(默认=用户原始完整且一致的输入;仅在用户确认后才追加补充信息)。
- Filename(可选):输出图片文件名/路径(需包含文件随机标识,避免重复)。不传则脚本会自动生成带时间戳的文件名。建议根据内容生成合理文件名(例如
cat_in_garden.png),避免使用通用名。 - Size(可选):输出尺寸。
- 预设值:
1024x1024、1536x1024、1024x1536、2048x2048、2048x1152、3840x2160、2160x3840 - 也可使用自定义尺寸(满足:最大边≤3840、两边16倍数、比例≤3:1、总像素0.65–8.3MP)
- 默认由模型自适应(auto)
- Quality(可选):画质档位。
low(草图/批量)、medium(日常)、high(终稿/精细文字)、auto(默认) - Output Format(可选):
png(默认)、jpeg、webp - Output Compression(可选):输出压缩率(0-100),仅jpeg/webp生效
- 注意:该模型使用官方正式版端点,与官逆版gpt-image-2-all不同。
第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}" [-s {size}] [-q {quality}] [-o {output_format}]图生图命令模板(优先 Node.js):
node scripts/generate_image.js -p "{edit_instruction}" -i "{input_path}" -f "{output_filename}" [-s {size}] [-q {quality}]多图融合命令模板(优先 Node.js):
node scripts/generate_image.js -p "融合图1和图2的风格" -i ref1.png ref2.png -f "merged.png" [-s {size}] [-q {quality}](可选)Python 版本命令模板(Node 不可用时):
python scripts/generate_image.py -p "{prompt}" -f "{filename}" [-s {size}] [-q {quality}] [-o {output_format}]
python scripts/generate_image.py -p "{edit_instruction}" -i "{input_path}" -f "{output_filename}" [-s {size}] [-q {quality}]⏱️ 长时间任务处理策略
1. 任务前提示
执行前必须告知用户:
- "图片生成已启动,预计需要120-150秒,请耐心等待"
2. 🎨 最佳实践示例
"图片生成中,预计120-150秒完成...\n⏳ 正在生成...\n(high + 2K/4K 复杂场景可能需要更长时间,请耐心等待)"
第3步:结果反馈
1. 执行反馈:等待终端命令执行完毕。 2. 成功:告知用户图片已生成,并指出保存路径。 3. 失败:
- 若提示 API Key 缺失,请指导用户设置环境变量。
- 若提示网络错误,建议用户检查网络或稍后重试。
命令行使用样例
生成新图片
python scripts/generate_image.py -p "图片描述文本" -f "output.png" [-s {size}] [-q {quality}] [-o {output_format}]示例:
# 基础生成
python scripts/generate_image.py -p "一只可爱的橘猫在草地上玩耍" -f "cat.png"
# 指定尺寸和画质
python scripts/generate_image.py -p "日落山脉风景" -f "sunset.png" -s "2048x1152" -q "high"
# 竖版高清图片(适合手机壁纸)
python scripts/generate_image.py -p "城市夜景" -f "city.png" -s "2160x3840" -q "high"
# 输出为JPEG
python scripts/generate_image.py -p "风景照片" -f "landscape.jpg" -s "3840x2160" -q "high" -o "jpeg"(可选)Node.js 版本示例:
# 基础生成
node scripts/generate_image.js -p "一只可爱的橘猫在草地上玩耍" -f "cat.png"
# 指定尺寸和画质
node scripts/generate_image.js -p "日落山脉风景" -f "sunset.png" -s "2048x1152" -q "high"编辑已有图片
python scripts/generate_image.py -p "编辑指令" -f "output.png" -i "path/to/input.png" [-s {size}] [-q {quality}]示例:
# 修改风格
python scripts/generate_image.py -p "将图片转换成水彩画风格" -f "watercolor.png" -i "original.png"
# 添加元素
python scripts/generate_image.py -p "在天空添加彩虹" -f "rainbow.png" -i "landscape.png" -q "high"
# 替换背景
python scripts/generate_image.py -p "将背景换成海滩" -f "beach-bg.png" -i "portrait.png" -s "2048x2048"(可选)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 | 否 | 输出图片路径/文件名;不传则自动生成带时间戳的文件名。 |
-s / --size | 否 | 输出尺寸:1024x1024 / 1536x1024 / 1024x1536 / 2048x2048 / 2048x1152 / 3840x2160 / 2160x3840 或自定义尺寸。 |
-q / --quality | 否 | 画质档位:low / medium / high / auto(默认auto)。 |
-o / --output-format | 否 | 输出格式:png(默认)/ jpeg / webp。 |
-c / --output-compression | 否 | 输出压缩率(0-100),仅jpeg/webp生效。 |
-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. 逐个执行:读取 prompts 数组,逐个执行生成命令,每张完成后反馈结果 4. 汇总反馈:完成后告知成功数量、图片路径列表
注意:批量任务总时间 = 单张时间(120-150秒) × 图片数量,请提前告知用户预估时长。
画质说明
| 画质 | 说明 | 适用场景 |
|---|---|---|
| low | 草图/批量生成 | 快速预览、多次迭代 |
| medium | 日常 | 普通使用 |
| high | 终稿/精细文字 | 最终输出、包含文字的图像 |
| auto | 默认 | 由模型决定 |
输出格式说明
| 格式 | 说明 | 适用场景 |
|---|---|---|
| png | 无压缩,透明背景 | 需要透明背景、保留最佳画质 |
| jpeg | 有压缩 | 照片、存储空间敏感 |
| webp | 现代格式 | Web使用、平衡画质与大小 |
注意:b64_json字段是纯base64,不含 data:image/...;base64, 前缀。客户端需要:
- 写文件:
base64.b64decode(b64_str)→ 写入磁盘 - 浏览器渲染:自行拼前缀
data:image/png;base64,+ b64
注意事项
- API密钥必须设置,可通过环境变量或命令行参数提供
- 图片生成时间:约120-150秒,high + 2K/4K 复杂场景可能需要更长时间
- 编辑图片时,使用multipart/form-data上传参考图
- 确保输出目录有写入权限
- 按token计费(非按张)
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"
我的电脑高级设置中设置环境变量或者执行set 命令:
# Windows CMD
set APIYI_API_KEY=your-api-key-here
# Windows PowerShell
$env:APIYI_API_KEY="your-api-key-here"API端点说明
文生图端点:POST /v1/images/generations
文生图端点,使用JSON格式请求。
图生图端点:POST /v1/images/edits
图生图端点,使用multipart/form-data格式请求。上传参考图(最多5张)+ 指令进行单图改图、多图融合。
参考图顺序有意义,prompt中可用"图1/图2/图3"指代。
模型信息
- 模型名:gpt-image-2
- 出图速度:约 120-150秒(4K复杂场景可能需要更长时间)
- 输出分辨率:1024x1024 / 1536x1024 / 1024x1536 / 2048x2048 / 2048x1152 / 3840x2160 / 2160x3840 或自定义
- 默认响应格式:b64_json(纯base64,无前缀)
- 画质档位:low / medium / high / auto
- 输出格式:png / jpeg / webp
- 支持能力:文生图、单图编辑、多图融合
- 计费方式:按token计费
gpt-image-2(官转)vs gpt-image-2-all(官逆)对比
| 特性 | gpt-image-2 | gpt-image-2-all |
|---|---|---|
| 性质 | 官方正式版 | 官方逆向版 |
| 计费 | 按token | 统一$0.03/张 |
| 端点 | /v1/images/generations, /v1/images/edits | /v1/chat/completions |
| 上传参考图 | multipart form-data | base64 data URL |
| 下载图片 | b64_json(纯base64) | url或b64_json(带前缀) |
| 多图融合 | image[]数组最多5张 | chat多个image_url |
| 尺寸控制 | 显式size参数 | prompt描述 |
| 速度 | 约120-150秒 | 约60-300秒 |
作者介绍
- 爱海贼的无处不在
- 我的微信公众号:无处不在的技术
批量生成提示词模板
当用户需要一次性生成多张图片时,可使用本文档定义的 JSON 格式配置批量生成任务。
JSON 文件格式
{
"description": "批量任务描述(可选)",
"global": {
"quality": "high",
"size": "1024x1024",
"output_format": "png"
},
"prompts": [
{
"prompt": "提示词1描述",
"filename": "output1.png",
"size": "1024x1024",
"quality": "high",
"output_format": "png"
},
{
"prompt": "提示词2描述",
"filename": "output2.png",
"size": "2048x2048",
"quality": "high"
}
]
}字段说明
根级字段
| ���段 | 必填 | 说明 |
|---|---|---|
description | 否 | 批量任务描述,用于记录本次生成的目的 |
global | 否 | 全局默认参数,所有 prompts 共享此配置 |
prompts | 是 | 提示词数组,每个元素生成一张图片 |
global 字段
| 字段 | 必填 | 说明 |
|---|---|---|
quality | 否 | 全局画质档位:low / medium / high / auto |
size | 否 | 全局尺寸 |
output_format | 否 | 全局输出格式:png / jpeg / webp |
prompts 数组元素
| 字段 | 必填 | 说明 |
|---|---|---|
prompt | 是 | 图片描述(文生图)或编辑指令(图生图) |
filename | 否 | 输出文件名;不传则自动生成 |
size | 否 | 输出尺寸,覆盖全局配置 |
quality | 否 | 画质档位,覆盖全局配置 |
output_format | 否 | 输出格式,覆盖全局配置 |
input_images | 否 | 图生图/多图融合时参考图路径(数组,最多5张) |
使用方法
1. 创建/获取 JSON 文件
用户可以通过以下方式提供 JSON 文件:
- 用户自行创建并提供文件路径
- 用户描述需求,AI 根据需求生成 JSON 文件
2. 读取并解析
AI 读取 JSON 文件后,逐个解析 prompts 数组中的每个对象。
3. 逐个生成
对每个提示词对象执行生成命令:
# 单张生成示例
node scripts/generate_image.js -p "{prompt}" -f "{filename}" [-s {size}] [-q {quality}] [-o {output_format}]执行策略:
- 逐个执行,每次生成完成后反馈结果
- 每张图片生成前提示用户预计等待时间
- 批量任务总时间 = 单张时间 × 图片数量
4. 结果反馈
生成完成后汇总反馈:
- 成功数量
- 各图片保存路径
- 失败项目及原因(如有)
示例场景
场景1:生成系列风格图
{
"description": "生成同一场景的不同风格版本",
"global": {
"quality": "high",
"size": "2048x1152",
"output_format": "png"
},
"prompts": [
{
"prompt": "山间日出风景,写实摄影风格",
"filename": "sunrise_realistic.png"
},
{
"prompt": "山间日出风景,水彩画风格",
"filename": "sunrise_watercolor.png"
},
{
"prompt": "山间日出风景,动漫插画风格",
"filename": "sunrise_anime.png"
}
]
}场景2:生成产品多视角图
{
"description": "产品展示图多角度生成",
"global": {
"quality": "high",
"size": "1024x1024"
},
"prompts": [
{
"prompt": "白色陶瓷咖啡杯,正面45度角拍摄,简洁白色背景",
"filename": "cup_front.png"
},
{
"prompt": "白色陶瓷咖啡杯,侧面角度拍摄,简洁白色背景",
"filename": "cup_side.png"
},
{
"prompt": "白色陶瓷咖啡杯,俯视角度拍摄,简洁白色背景",
"filename": "cup_top.png"
}
]
}场景3:生成社交媒体配图套装
{
"description": "小红书配图套装",
"global": {
"quality": "high"
},
"prompts": [
{
"prompt": "美食摄影,精致甜点摆盘,暖色调",
"filename": "content_01.png",
"size": "1024x1024"
},
{
"prompt": "生活场景,明亮窗边阅读角",
"filename": "content_02.png",
"size": "1024x1536"
},
{
"prompt": "旅行风景,海边日落剪影",
"filename": "content_03.png",
"size": "2048x1152"
}
]
}注意事项
1. 优先级:prompts 数组中单个元素的字段会覆盖 global 中的全局配置 2. 文件名建议:使用有意义的文件名,避免重复 3. 预估时间:每张图约 120-150 秒,批量任务需预留充足时间 4. 图生图支持:prompts 数组元素中可包含 input_images 字段进行多图融合
尺寸与比例控制指南
本文档说明如何在 GPT Image 2 官方正式版(gpt-image-2)中控制输出图片的尺寸和比例。
预设尺寸
| 尺寸 | 比例 | 适用场景 |
|---|---|---|
| 1024x1024 | 1:1 | 头像、Instagram帖子、方形构图 |
| 1536x1024 | 3:2 | 标准横版、摄影构图 |
| 1024x1536 | 2:3 | 标准竖版、人物肖像 |
| 2048x2048 | 1:1 | 高清方图 |
| 2048x1152 | 16:9 | 横版视频封面、桌面壁纸 |
| 3840x2160 | 16:9 | 4K超高清横版 |
| 2160x3840 | 9:16 | 竖版4K、手机壁纸 |
自定义尺寸
可使用任意合法自定义尺寸,需满足以下条件:
- 最大边 ≤ 3840
- 两边都能被 16 整除
- 比例 ≤ 3:1
- 总像素 0.65–8.3MP
常见自定义尺寸示例
| 尺寸 | 比例 | 适用场景 |
|---|---|---|
| 1920x1080 | 16:9 | 全高清 |
| 1280x720 | 16:9 | 高清 |
| 1080x1920 | 9:16 | 手机竖版 |
| 2560x1440 | 16:9 | 2K |
| 1440x2560 | 9:16 | 手机竖版2K |
| 1920x1280 | 3:2 | 经典摄影比例 |
| 1280x1920 | 2:3 | 经典竖版摄影 |
| 3840x1600 | 12:5 | 超宽银幕 |
| 1600x3840 | 5:12 | 超长竖版 |
使用方法
通过 -s 或 --size 参数指定尺寸:
# 使用预设尺寸
node scripts/generate_image.js -p "风景照片" -f "landscape.png" -s "3840x2160"
# 使用自定义尺寸
node scripts/generate_image.js -p "超宽风景" -f "ultrawide.png" -s "3840x1600"注意事项
1. 最大边限制:无论宽高,最大边不能超过 3840 像素 2. 16倍数规则:宽和高都必须是 16 的倍数 3. 比例限制:宽高比不能超过 3:1(防止极端比例) 4. 像素总量:总像素数应在 0.65MP 到 8.3MP 之间 5. 高精度场景:如需 4K 输出,使用 3840x2160(横版)或 2160x3840(竖版)
#!/usr/bin/env node
/*
基于GPT Image 2官方正式版的图片生成与编辑脚本(Node.js版)
使用API易国内代理服务
支持功能:
- 文生图:根据提示词生成图片
- 图生图:根据编辑指令修改已有图片
- 多图融合:参考多张图片融合
参数说明:
- -p, --prompt 图片描述或编辑指令文本(必需)
- -f, --filename 输出图片路径(可选,默认自动生成时间戳文件名)
- -s, --size 输出尺寸(可选)
- -q, --quality 画质档位(可选:low/medium/high/auto,默认auto)
- -o, --output-format 输出格式(可选:png/jpeg/webp,默认png)
- -c, --output-compression 输出压缩率(可选:0-100,默认85)
- -i, --input-image 输入图片路径(可选,可多张,最多5张)
使用示例:
【生成新图片】
node generate_image.js -p "一只可爱的橘猫"
node generate_image.js -p "日落山脉" -s "2048x1152" -q "high" -f sunset.png
node generate_image.js -p "城市夜景" -s "2160x3840" -q "high" -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_SIZES = [
'1024x1024',
'1536x1024',
'1024x1536',
'2048x2048',
'2048x1152',
'3840x2160',
'2160x3840',
];
const SUPPORTED_QUALITIES = ['auto', 'low', 'medium', 'high'];
const SUPPORTED_OUTPUT_FORMATS = ['png', 'jpeg', 'webp'];
function printHelpAndExit(exitCode = 0) {
const help = `usage: generate_image.js [-h] --prompt PROMPT [--filename FILENAME]
[--size SIZE]
[--quality auto|low|medium|high]
[--output-format png|jpeg|webp]
[--output-compression 0-100]
[--input-image INPUT_IMAGE [INPUT_IMAGE ...]]
基于GPT Image 2官方正式版的图片生成与编辑工具(Node.js版)
options:
-h, --help show this help message and exit
-p, --prompt PROMPT 图片描述或编辑指令文本(必需)
-f, --filename FILE 输出图片路径 (默认: 自动生成时间戳文件名)
-s, --size 输出尺寸 (可选: 1024x1024, 1536x1024, 1024x1536, 2048x2048, 2048x1152, 3840x2160, 2160x3840)
-q, --quality 画质档位 (可选: auto, low, medium, high)
-o, --output-format 输出格式 (可选: png, jpeg, webp)
-c, --output-compression 输出压缩率 (0-100,仅jpeg/webp生效)
-i, --input-image 输入图片路径(编辑模式,可传多张,最多5张)
尺寸说明:
- 预设值: 1024x1024, 1536x1024, 1024x1536, 2048x2048, 2048x1152, 3840x2160, 2160x3840
- 也支持自定义尺寸(最大边≤3840,两边16倍数,比例≤3:1)
运行示例:
node scripts/generate_image.js -p "一只可爱的橘猫"
node scripts/generate_image.js -p "日落山脉" -s "2048x1152" -q "high" -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 parseArgs(argv) {
const args = {
prompt: null,
filename: null,
size: null,
quality: null,
outputFormat: null,
outputCompression: null,
inputImages: null,
};
const knownFlags = new Set([
'-h', '--help',
'-p', '--prompt',
'-f', '--filename',
'-s', '--size',
'-q', '--quality',
'-o', '--output-format',
'-c', '--output-compression',
'-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 === '-s' || a === '--size') {
args.size = requireValue(i, a);
i++;
continue;
}
if (a === '-q' || a === '--quality') {
args.quality = requireValue(i, a);
i++;
continue;
}
if (a === '-o' || a === '--output-format') {
args.outputFormat = requireValue(i, a);
i++;
continue;
}
if (a === '-c' || a === '--output-compression') {
args.outputCompression = 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 buildBoundary() {
let s = '';
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < 24; i++) {
s += chars[Math.floor(Math.random() * chars.length)];
}
return s;
}
function postMultipart(urlString, headers, boundary, parts) {
return new Promise((resolve, reject) => {
const url = new URL(urlString);
const bodyParts = [];
for (const part of parts) {
bodyParts.push(Buffer.from(`--${boundary}\r\n`, 'utf8'));
bodyParts.push(Buffer.from(`${part.header}\r\n`, 'utf8'));
bodyParts.push(Buffer.from('\r\n', 'utf8'));
if (Buffer.isBuffer(part.body)) {
bodyParts.push(part.body);
} else {
bodyParts.push(Buffer.from(part.body, 'utf8'));
}
bodyParts.push(Buffer.from('\r\n', 'utf8'));
}
bodyParts.push(Buffer.from(`--${boundary}--\r\n`, 'utf8'));
const body = Buffer.concat(bodyParts);
const req = https.request(
{
protocol: url.protocol,
hostname: url.hostname,
port: url.port || 443,
path: url.pathname + url.search,
method: 'POST',
headers: {
...headers,
'Content-Type': `multipart/form-data; boundary=${boundary}`,
'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(500_000, () => {
req.destroy(new Error('timeout'));
});
req.write(body);
req.end();
});
}
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-Type': 'application/json',
'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();
});
}
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.size != null && !SUPPORTED_SIZES.includes(args.size)) {
const sizePattern = /^\d+x\d+$/;
if (!sizePattern.test(args.size)) {
exitWithError(
`错误: 不支持的尺寸 '${args.size}'\n支持的预设尺寸: ${SUPPORTED_SIZES.join(', ')}\n或自定义尺寸(如 1920x1080)`
);
}
const [w, h] = args.size.split('x').map(Number);
if (w > 3840 || h > 3840) {
exitWithError(`错误: 尺寸最大边不能超过3840`);
}
if (w % 16 !== 0 || h % 16 !== 0) {
exitWithError(`错误: 尺寸两边必须能被16整除`);
}
if (Math.max(w / h, h / w) > 3) {
exitWithError(`错误: 尺寸比例不能超过3:1`);
}
const mp = (w * h) / 1000000;
if (mp < 0.65 || mp > 8.3) {
exitWithError(`错误: 总像素必须在0.65-8.3MP之间,当前${mp.toFixed(2)}MP`);
}
}
if (args.quality != null && !SUPPORTED_QUALITIES.includes(args.quality)) {
exitWithError(
`错误: 不支持的画质 '${args.quality}'\n支持的画质: ${SUPPORTED_QUALITIES.join(', ')}`
);
}
if (args.outputFormat != null && !SUPPORTED_OUTPUT_FORMATS.includes(args.outputFormat)) {
exitWithError(
`错误: 不支持的输出格式 '${args.outputFormat}'\n支持的格式: ${SUPPORTED_OUTPUT_FORMATS.join(', ')}`
);
}
if (args.outputCompression != null) {
const comp = parseInt(args.outputCompression);
if (isNaN(comp) || comp < 0 || comp > 100) {
exitWithError(`错误: 输出压缩率必须在0-100之间`);
}
}
if (!args.filename) {
const ext = args.outputFormat === 'jpeg' ? 'jpg' : args.outputFormat === 'webp' ? 'webp' : 'png';
args.filename = generateFilename(args.prompt).replace(/\.png$/, `.${ext}`);
} 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 headers = {
Authorization: `Bearer ${apiKey}`,
};
let modeStr = '生成图片';
let data;
if (args.inputImages && args.inputImages.length > 0) {
if (args.inputImages.length > 5) {
exitWithError(`错误: 输入图片最多支持5张,当前为 ${args.inputImages.length} 张`);
}
for (const imgPath of args.inputImages) {
if (!fs.existsSync(imgPath)) {
exitWithError(`错误: 输入图片不存在: ${imgPath}`);
}
}
modeStr = args.inputImages.length === 1 ? '编辑图片' : '多图融合';
const boundary = buildBoundary();
const parts = [];
parts.push({
header: 'Content-Disposition: form-data; name="model"',
body: 'gpt-image-2',
});
parts.push({
header: `Content-Disposition: form-data; name="prompt"`,
body: args.prompt,
});
if (args.size != null) {
parts.push({
header: 'Content-Disposition: form-data; name="size"',
body: args.size,
});
}
if (args.quality != null) {
parts.push({
header: 'Content-Disposition: form-data; name="quality"',
body: args.quality,
});
}
if (args.outputFormat != null) {
parts.push({
header: 'Content-Disposition: form-data; name="output_format"',
body: args.outputFormat,
});
}
if (args.outputCompression != null) {
parts.push({
header: 'Content-Disposition: form-data; name="output_compression"',
body: args.outputCompression,
});
}
for (let i = 0; i < args.inputImages.length; i++) {
const imgPath = args.inputImages[i];
const imageData = fs.readFileSync(imgPath);
const fileName = path.basename(imgPath);
const mimeType = `image/${path.extname(imgPath).slice(1)}`;
parts.push({
header: `Content-Disposition: form-data; name="image[]"; filename="${fileName}"\r\nContent-Type: ${mimeType}`,
body: imageData,
});
}
const url = 'https://api.apiyi.com/v1/images/edits';
process.stdout.write('🎨 图片生成已启动!\n');
process.stdout.write(`⏱️ 预计时间: 约120-150秒,请耐心等待\n`);
process.stdout.write(`正在${modeStr}...\n`);
process.stdout.write(`提示词: ${args.prompt}\n`);
if (args.size) {
process.stdout.write(`尺寸: ${args.size}\n`);
}
if (args.quality) {
process.stdout.write(`画质: ${args.quality}\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);
try {
data = await postMultipart(url, headers, boundary, parts);
} 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)}`);
}
} else {
const payload = {
model: 'gpt-image-2',
prompt: args.prompt,
};
if (args.size != null) payload.size = args.size;
if (args.quality != null) payload.quality = args.quality;
if (args.outputFormat != null) payload.output_format = args.outputFormat;
if (args.outputCompression != null) payload.output_compression = parseInt(args.outputCompression);
const url = 'https://api.apiyi.com/v1/images/generations';
process.stdout.write('🎨 图片生成已启动!\n');
process.stdout.write(`⏱️ 预计时间: 约120-150秒,请耐心等待\n`);
process.stdout.write(`正在${modeStr}...\n`);
process.stdout.write(`提示词: ${args.prompt}\n`);
if (args.size) {
process.stdout.write(`尺寸: ${args.size}\n`);
}
if (args.quality) {
process.stdout.write(`画质: ${args.quality}\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);
try {
data = await postJson(url, headers, payload, 500_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 b64Json =
data &&
data.data &&
Array.isArray(data.data) &&
data.data[0] &&
data.data[0].b64_json;
if (!b64Json) {
process.stderr.write('错误: 响应中未找到图片数据\n');
process.stderr.write(`完整响应: ${JSON.stringify(data, null, 2)}\n`);
process.exit(1);
}
const imageBytes = Buffer.from(b64Json, '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官方正式版的图片生成与编辑脚本(Python版)
使用API易国内代理服务
支持功能:
- 文生图:根据提示词生成图片
- 图生图:根据编辑指令修改已有图片
- 多图融合:参考多张图片融合
参数说明:
- -p, --prompt 图片描述或编辑指令文本(必需)
- -f, --filename 输出图片路径(可选,默认自动生成时间戳文件名)
- -s, --size 输出尺寸(可选)
- -q, --quality 画质档位(可选:low/medium/high/auto,默认auto)
- -o, --output-format 输出格式(可选:png/jpeg/webp,默认png)
- -c, --output-compression 输出压缩率(可选:0-100,默认85)
- -i, --input-image 输入图片路径(可选,可多张,最多5张)
- -k, --api-key API密钥(可选,覆盖环境变量 APIYI_API_KEY)
使用示例:
【生成新图片】
python generate_image.py -p "一只可爱的橘猫"
python generate_image.py -p "日落山脉" -s "2048x1152" -q "high" -f sunset.png
python generate_image.py -p "城市夜景" -s "2160x3840" -q "high" -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
try:
import requests
except ImportError:
print("错误: 需要安装 requests 库,请运行: pip install requests")
sys.exit(1)
SUPPORTED_SIZES = ['1024x1024', '1536x1024', '1024x1536', '2048x2048', '2048x1152', '3840x2160', '2160x3840']
SUPPORTED_QUALITIES = ['auto', 'low', 'medium', 'high']
SUPPORTED_OUTPUT_FORMATS = ['png', 'jpeg', 'webp']
DEFAULT_TIMEOUT = 500
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description='基于GPT Image 2官方正式版的图片生成与编辑工具(Python版)',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='''
尺寸说明:
- 预设值: 1024x1024, 1536x1024, 1024x1536, 2048x2048, 2048x1152, 3840x2160, 2160x3840
- 也支持自定义尺寸(最大边≤3840,两边16倍数,比例≤3:1)
运行示例:
python scripts/generate_image.py -p "一只可爱的橘猫"
python scripts/generate_image.py -p "日落山脉" -s "2048x1152" -q "high" -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('-s', '--size', default=None, help='输出尺寸 (可选)')
parser.add_argument('-q', '--quality', default='auto', choices=SUPPORTED_QUALITIES, help='画质档位 (默认: auto)')
parser.add_argument('-o', '--output-format', default='png', choices=SUPPORTED_OUTPUT_FORMATS, help='输出格式 (默认: png)')
parser.add_argument('-c', '--output-compression', type=int, default=None, help='输出压缩率 (0-100,仅jpeg/webp生效)')
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) -> bytes:
try:
with open(image_path, 'rb') as f:
return f.read()
except Exception as e:
print(f'错误: 无法读取图片文件 {image_path} - {e}', file=sys.stderr)
sys.exit(1)
def generate_filename(prompt: str, output_format: str = 'png') -> 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]
ext = output_format if output_format != 'jpeg' else 'jpg'
return f'{timestamp}-{keyword_str}.{ext}'
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 main():
args = parse_args()
timestamp = datetime.datetime.now().strftime('%Y-%m-%d-%H-%M-%S')
if args.size and args.size not in SUPPORTED_SIZES:
size_pattern = re.match(r'^(\d+)x(\d+)$', args.size)
if not size_pattern:
print(f"错误: 无效的尺寸格式 '{args.size}'", file=sys.stderr)
print(f"支持的预设尺寸: {', '.join(SUPPORTED_SIZES)} 或自定义尺寸 (如 1920x1080)", file=sys.stderr)
sys.exit(1)
w = int(size_pattern.group(1))
h = int(size_pattern.group(2))
if w > 3840 or h > 3840:
print('错误: 尺寸最大边不能超过3840', file=sys.stderr)
sys.exit(1)
if w % 16 != 0 or h % 16 != 0:
print('错误: 尺寸两边必须能被16整除', file=sys.stderr)
sys.exit(1)
if max(w / h, h / w) > 3:
print('错误: 尺寸比例不能超过3:1', file=sys.stderr)
sys.exit(1)
mp = (w * h) / 1000000
if mp < 0.65 or mp > 8.3:
print(f'错误: 总像素必须在0.65-8.3MP之间 (当前{mp:.2f}MP)', file=sys.stderr)
sys.exit(1)
if args.quality not in SUPPORTED_QUALITIES:
print(f"错误: 不支持的画质 '{args.quality}'", file=sys.stderr)
print(f"支持的画质: {', '.join(SUPPORTED_QUALITIES)}", file=sys.stderr)
sys.exit(1)
if args.output_format not in SUPPORTED_OUTPUT_FORMATS:
print(f"错误: 不支持的输出格式 '{args.output_format}'", file=sys.stderr)
print(f"支持的格式: {', '.join(SUPPORTED_OUTPUT_FORMATS)}", file=sys.stderr)
sys.exit(1)
if args.output_compression is not None:
if args.output_compression < 0 or args.output_compression > 100:
print('错误: 输出压缩率必须在0-100之间', file=sys.stderr)
sys.exit(1)
if not args.filename:
args.filename = generate_filename(args.prompt, args.output_format)
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)
headers = {
'Authorization': f'Bearer {api_key}',
}
mode_str = '生成图片'
start_time = datetime.datetime.now()
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)
mode_str = '编辑图片' if len(args.input_image) == 1 else '多图融合'
url = 'https://api.apiyi.com/v1/images/edits'
files_list = [
('model', (None, 'gpt-image-2')),
('prompt', (None, args.prompt)),
]
if args.size:
files_list.append(('size', (None, args.size)))
if args.quality:
files_list.append(('quality', (None, args.quality)))
if args.output_format:
files_list.append(('output_format', (None, args.output_format)))
if args.output_compression is not None:
files_list.append(('output_compression', (None, str(args.output_compression))))
for img_path in args.input_image:
img_data = encode_image_to_base64(img_path)
file_name = Path(img_path).name
suffix = Path(img_path).suffix[1:].lower()
mime_type = f'image/{suffix}' if suffix in ['png', 'jpg', 'jpeg', 'webp'] else 'image/png'
files_list.append(('image[]', (file_name, img_data, mime_type)))
print('🎨 图片生成已启动!')
print(f'⏱️ 预计时间: 约120-150秒,请耐心等待')
print(f'正在{mode_str}...')
print(f'提示词: {args.prompt}')
if args.size:
print(f'尺寸: {args.size}')
if args.quality:
print(f'画质: {args.quality}')
print('image generation in progress...')
try:
response = requests.post(url, headers=headers, files=files_list, 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)
else:
url = 'https://api.apiyi.com/v1/images/generations'
payload = {
'model': 'gpt-image-2',
'prompt': args.prompt,
}
if args.size:
payload['size'] = args.size
if args.quality:
payload['quality'] = args.quality
if args.output_format:
payload['output_format'] = args.output_format
if args.output_compression is not None:
payload['output_compression'] = args.output_compression
print('🎨 图片生成已启动!')
print(f'⏱️ 预计时间: 约120-150秒,请耐心等待')
print(f'正在{mode_str}...')
print(f'提示词: {args.prompt}')
if args.size:
print(f'尺��: {args.size}')
if args.quality:
print(f'画质: {args.quality}')
print('image generation in progress...')
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)
elapsed = (datetime.datetime.now() - start_time).total_seconds()
print(f'⏱️ 生成完成,耗时 {elapsed:.1f}秒')
b64_json = None
if data and data.get('data') and len(data['data']) > 0:
b64_json = data['data'][0].get('b64_json')
if not b64_json:
print('错误: 响应中未找到图片数据', file=sys.stderr)
print(f'完整响应: {json.dumps(data, indent=2, ensure_ascii=False)}', file=sys.stderr)
sys.exit(1)
try:
image_bytes = base64.b64decode(b64_json)
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()