Files
tripocli/tripo/features/image_gen.py
T

164 lines
6.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""文生图 — text to image(游戏资产流程的第一步)。
tripocli img -p "a cute cartoon wooden barrel" -m nano-banana-2 --amount 1 --resolution 2K --wait -o ./refs
实测流程(页面上真操作抓到的请求):
POST /v2/studio/image/gen_image_v2
{"if_upload":true,"sketch_to_render":false,"t_pose":false,"amount":4,
"model_version":"gemini_3.1_flash_image_preview","prompt":"...","resolution":"4K","scale":"1:1"}
→ {task_id, asset_id, remaining_free_count}
POST /v2/studio/image/get_image_asset {"asset_id": "..."}
→ {status, type:"generate_image", output:{data:[{bucket,key,url}]}}
异步:先 gen_image_v2 拿 asset_id,再轮询 get_image_asset 直到 status=success,从 output.data 取图。
"""
import os
import time
from ..errors import TripoAPIError, TripoTimeout
NAME = "img"
HELP = "文生图:用文本生成参考图(游戏资产流程的第一步)"
# 界面上的模型名 → API 的 model_version(逐个在页面上选中并抓包确认)
IMAGE_MODELS = {
"nano-banana-2": "gemini_3.1_flash_image_preview",
"nanobanana": "gemini_3.1_flash_image_preview",
"nano-banana": "gemini_3.1_flash_image_preview",
"banana": "gemini_3.1_flash_image_preview",
"nano-banana-pro": "gemini_3_pro_image_preview",
"nanobanana-pro": "gemini_3_pro_image_preview",
"gpt-image-2": "gpt_image_2",
"gpt-image-2.5": "gpt_image_2.5_sunburst",
"midjourney": "midjourney",
}
DEFAULT_MODEL = "nano-banana-2" # 免费/非会员默认可用模型 (Nano Banana 2 / Gemini 3.1 Flash)
SCALES = ["1:1", "16:9", "9:16", "4:3", "3:4"]
RESOLUTIONS = ["1K", "2K", "4K"]
def resolve_model(name):
"""Accept either a friendly alias or a raw model_version string."""
if not name:
return IMAGE_MODELS[DEFAULT_MODEL]
key = name.strip().lower()
return IMAGE_MODELS.get(key, name.strip())
def add_arguments(parser):
parser.add_argument("-p", "--prompt", help="图片描述提示词(用 --asset 取已有结果时可省略)")
parser.add_argument("--asset", help="直接取已有 asset_id 的结果,不发起新生成")
parser.add_argument("--templates", action="store_true", help="列出官方模板(角色拆分/T姿势/3D增强…)后退出")
parser.add_argument("--amount", type=int, default=1, choices=[1, 2, 3, 4], help="生成数量 1-4(默认 1,省额度)")
parser.add_argument("--scale", default="1:1", choices=SCALES, help="图片比例(默认 1:1)")
parser.add_argument("--resolution", default="1K", choices=RESOLUTIONS, help="分辨率 1K/2K/4K(默认 1K)")
parser.add_argument(
"-m", "--model", default=DEFAULT_MODEL,
help="图像模型: " + " / ".join(IMAGE_MODELS) + f"(默认 {DEFAULT_MODEL}),也可直接传 model_version",
)
parser.add_argument("--t-pose", action="store_true", help="人形角色输出 T 姿势(便于后续绑骨)")
parser.add_argument("-w", "--wait", action="store_true", help="等待生成完成")
parser.add_argument("-o", "--output", help="图片保存目录(配合 --wait)")
parser.add_argument("--timeout", type=int, default=300, help="等待超时秒数(默认 300)")
def _poll_asset(client, asset_id, timeout):
deadline = time.time() + timeout
while time.time() < deadline:
data = client.get_data("POST", "/v2/studio/image/get_image_asset", body={"asset_id": asset_id}) or {}
status = data.get("status")
if status == "success":
return data
if status in ("failed", "banned"):
raise TripoAPIError(f"图片生成失败: status={status}")
time.sleep(3)
raise TripoTimeout(f"图片生成在 {timeout}s 内未完成(asset_id={asset_id})")
def _report(ctx, asset_id, asset, output_dir):
images = ((asset.get("output") or {}).get("data")) or []
saved = []
if output_dir and images:
os.makedirs(output_dir, exist_ok=True)
for index, item in enumerate(images):
target = os.path.abspath(os.path.join(output_dir, f"image_{index}.png"))
ctx.client.download(item["url"], target)
saved.append(target)
payload = {"asset_id": asset_id, "status": asset.get("status"), "images": images, "saved": saved}
ctx.out.data(
payload,
human=lambda: (
ctx.out.line(f"🎉 生成完成(status={asset.get('status')}),共 {len(images)} 张"),
*[ctx.out.line(f" {path}") for path in saved],
*([] if saved else [ctx.out.line(f" {item['url'][:90]}") for item in images]),
),
)
return payload
def run(ctx):
args = ctx.args
if args.templates:
# 模板是客户端的「提示词+参数」预设,接口只给元数据(实测:请求体里没有 template_id)。
data = ctx.client.get_data("GET", "/v2/studio/image/available_templates") or {}
templates = data.get("templates") or []
def show():
for item in templates:
tags = ", ".join(item.get("secondary_tag") or [])
ctx.out.line(f" {item.get('title', ''):<22} [{item.get('category', '')}] {tags}")
if item.get("description"):
ctx.out.line(f" {item['description'].splitlines()[0]}")
ctx.out.data({"templates": templates}, human=show)
return data
if args.asset:
if args.wait:
asset = _poll_asset(ctx.client, args.asset, args.timeout)
else:
asset = ctx.client.get_data(
"POST", "/v2/studio/image/get_image_asset", body={"asset_id": args.asset}
) or {}
return _report(ctx, args.asset, asset, args.output)
if not args.prompt:
raise ValueError("需要 -p/--prompt 发起生成,或用 --asset 取已有结果")
model_version = resolve_model(args.model)
body = {
"if_upload": True,
"sketch_to_render": False,
"t_pose": args.t_pose,
"amount": args.amount,
"model_version": model_version,
"prompt": args.prompt,
"resolution": args.resolution,
"scale": args.scale,
}
ctx.out.line(f"🎨 生成图片: \"{args.prompt}\" (x{args.amount}, {args.scale}, {args.resolution}, {model_version})")
data = ctx.client.get_data("POST", "/v2/studio/image/gen_image_v2", body=body) or {}
asset_id = data.get("asset_id") or data.get("task_id")
if not asset_id:
raise TripoAPIError(f"gen_image_v2 未返回 asset_id: {data}")
if not args.wait:
ctx.out.data(
data,
human=lambda: (
ctx.out.line(f"✅ 已提交 asset_id={asset_id}(免费剩余 {data.get('remaining_free_count')})"),
ctx.out.line(f"取图: tripocli img --asset {asset_id} --wait -o ./refs"),
),
)
return data
asset = _poll_asset(ctx.client, asset_id, args.timeout)
return _report(ctx, asset_id, asset, args.output)