feat: 发布豆包Seedream 5.0/4.5生图节点
- 新增宽高比(8种)× 分辨率档位(2K/3K/4K)选择,后端自动换算像素 - 5.0支持2K/3K,4.5支持2K/4K,搭配错误时明确报错 - 新增生图数量(1-10),2张以上自动并发请求,加快出图速度 - 移除旧版尺寸预设、宽度、高度输入 Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Sonnet 4.5
parent
0b9d7583c7
commit
659f94656c
+257
-127
@@ -1,56 +1,110 @@
|
|||||||
"""
|
"""
|
||||||
豆包生图节点
|
豆包生图节点
|
||||||
1:1 复刻字节跳动 Seedream 4 节点的前端外观(输入/输出/参数/样式)
|
|
||||||
后端通过 new-api 兼容层调用豆包官方 API
|
后端通过 new-api 兼容层调用豆包官方 API
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
import time
|
import time
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import torch
|
import torch
|
||||||
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from typing import List
|
from typing import List, Optional
|
||||||
|
|
||||||
from ..clients.doubao_image_client import DoubaoImageClient
|
from ..clients.doubao_image_client import DoubaoImageClient
|
||||||
from ..utils.image_utils import tensor_to_pil
|
from ..utils.image_utils import tensor_to_pil
|
||||||
|
|
||||||
|
|
||||||
# ── 尺寸预设 ──────────────────────────────────────────────────────────────────
|
|
||||||
# (显示名, 宽, 高) —— 宽高用于构造 "WxH" size 字符串
|
|
||||||
RECOMMENDED_PRESETS_SEEDREAM_4 = [
|
|
||||||
("2048×2048 (1:1)", 2048, 2048),
|
|
||||||
("2304×1728 (4:3)", 2304, 1728),
|
|
||||||
("1728×2304 (3:4)", 1728, 2304),
|
|
||||||
("2560×1440 (16:9)", 2560, 1440),
|
|
||||||
("1440×2560 (9:16)", 1440, 2560),
|
|
||||||
("2496×1664 (3:2)", 2496, 1664),
|
|
||||||
("1664×2496 (2:3)", 1664, 2496),
|
|
||||||
("3024×1296 (21:9)", 3024, 1296),
|
|
||||||
("3072×3072 (1:1)", 3072, 3072),
|
|
||||||
("4096×4096 (1:1)", 4096, 4096),
|
|
||||||
("自定义", None, None),
|
|
||||||
]
|
|
||||||
|
|
||||||
_PRESET_LABELS = [label for label, _, _ in RECOMMENDED_PRESETS_SEEDREAM_4]
|
|
||||||
|
|
||||||
# ── 模型列表 ──────────────────────────────────────────────────────────────────
|
# ── 模型列表 ──────────────────────────────────────────────────────────────────
|
||||||
# 节点下拉选项 = new-api 后台配置的模型 ID(直接透传给 API)
|
|
||||||
_MODELS = [
|
_MODELS = [
|
||||||
"doubao-seedream-5-0-260128",
|
"doubao-seedream-5-0-260128",
|
||||||
"doubao-seedream-4-5-251128",
|
"doubao-seedream-4-5-251128",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# ── 宽高比列表 ─────────────────────────────────────────────────────────────────
|
||||||
|
_ASPECT_RATIOS = ["1:1", "4:3", "3:4", "16:9", "9:16", "3:2", "2:3", "21:9"]
|
||||||
|
|
||||||
|
# ── 分辨率档位(每个模型支持的档位不同)──────────────────────────────────────
|
||||||
|
# 5.0:2K / 3K
|
||||||
|
# 4.5:2K / 4K
|
||||||
|
_RESOLUTIONS = ["2K", "3K", "4K"]
|
||||||
|
|
||||||
|
# ── 像素对照表 ─────────────────────────────────────────────────────────────────
|
||||||
|
# 结构:{ 模型版本key: { 分辨率: { 宽高比: (宽, 高) } } }
|
||||||
|
_SIZE_TABLE = {
|
||||||
|
"5-0": {
|
||||||
|
"2K": {
|
||||||
|
"1:1": (2048, 2048),
|
||||||
|
"4:3": (2304, 1728),
|
||||||
|
"3:4": (1728, 2304),
|
||||||
|
"16:9": (2848, 1600),
|
||||||
|
"9:16": (1600, 2848),
|
||||||
|
"3:2": (2496, 1664),
|
||||||
|
"2:3": (1664, 2496),
|
||||||
|
"21:9": (3136, 1344),
|
||||||
|
},
|
||||||
|
"3K": {
|
||||||
|
"1:1": (3072, 3072),
|
||||||
|
"4:3": (3456, 2592),
|
||||||
|
"3:4": (2592, 3456),
|
||||||
|
"16:9": (4096, 2304),
|
||||||
|
"9:16": (2304, 4096),
|
||||||
|
"3:2": (3744, 2496),
|
||||||
|
"2:3": (2496, 3744),
|
||||||
|
"21:9": (4704, 2016),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"4-5": {
|
||||||
|
"2K": {
|
||||||
|
"1:1": (2048, 2048),
|
||||||
|
"4:3": (2304, 1728),
|
||||||
|
"3:4": (1728, 2304),
|
||||||
|
"16:9": (2848, 1600),
|
||||||
|
"9:16": (1600, 2848),
|
||||||
|
"3:2": (2496, 1664),
|
||||||
|
"2:3": (1664, 2496),
|
||||||
|
"21:9": (3136, 1344),
|
||||||
|
},
|
||||||
|
"4K": {
|
||||||
|
"1:1": (4096, 4096),
|
||||||
|
"4:3": (4704, 3520),
|
||||||
|
"3:4": (3520, 4704),
|
||||||
|
"16:9": (5504, 3040),
|
||||||
|
"9:16": (3040, 5504),
|
||||||
|
"3:2": (4992, 3328),
|
||||||
|
"2:3": (3328, 4992),
|
||||||
|
"21:9": (6240, 2656),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# 每个模型版本支持的分辨率档位
|
||||||
|
_MODEL_RESOLUTIONS = {
|
||||||
|
"5-0": ["2K", "3K"],
|
||||||
|
"4-5": ["2K", "4K"],
|
||||||
|
}
|
||||||
|
|
||||||
|
# 并发请求超时(秒)
|
||||||
|
_CONCURRENT_TIMEOUT = 330
|
||||||
|
|
||||||
|
|
||||||
|
def _model_key(model: str) -> str:
|
||||||
|
"""从模型 ID 中提取版本 key('5-0' 或 '4-5')。"""
|
||||||
|
for key in _SIZE_TABLE:
|
||||||
|
if key in model:
|
||||||
|
return key
|
||||||
|
raise ValueError(f"无法识别模型版本:{model},支持的模型:{_MODELS}")
|
||||||
|
|
||||||
|
|
||||||
def _pil_list_to_tensor(images: List[Image.Image]) -> torch.Tensor:
|
def _pil_list_to_tensor(images: List[Image.Image]) -> torch.Tensor:
|
||||||
"""
|
"""
|
||||||
PIL Image 列表 → ComfyUI IMAGE tensor [B, H, W, C],值域 [0, 1]。
|
PIL Image 列表 → ComfyUI IMAGE tensor [B, H, W, C],值域 [0, 1]。
|
||||||
|
多张尺寸不同时,以最大尺寸为准,较小图像丢弃。
|
||||||
多张尺寸不同时,以最大尺寸为准,较小图像丢弃(与项目其他节点策略一致)。
|
|
||||||
"""
|
"""
|
||||||
if not images:
|
if not images:
|
||||||
placeholder = Image.new("RGB", (512, 512), color=(128, 128, 128))
|
placeholder = Image.new("RGB", (512, 512), color=(128, 128, 128))
|
||||||
images = [placeholder]
|
images = [placeholder]
|
||||||
|
|
||||||
# 找最大尺寸
|
|
||||||
base_size = max(images, key=lambda img: img.size[0] * img.size[1]).size
|
base_size = max(images, key=lambda img: img.size[0] * img.size[1]).size
|
||||||
matched = [img for img in images if img.size == base_size]
|
matched = [img for img in images if img.size == base_size]
|
||||||
skipped = len(images) - len(matched)
|
skipped = len(images) - len(matched)
|
||||||
@@ -62,11 +116,11 @@ def _pil_list_to_tensor(images: List[Image.Image]) -> torch.Tensor:
|
|||||||
arr = np.array(img.convert("RGB")).astype(np.float32) / 255.0
|
arr = np.array(img.convert("RGB")).astype(np.float32) / 255.0
|
||||||
tensors.append(torch.from_numpy(arr))
|
tensors.append(torch.from_numpy(arr))
|
||||||
|
|
||||||
return torch.stack(tensors, dim=0) # [B, H, W, C]
|
return torch.stack(tensors, dim=0) # [B, H, W, C]
|
||||||
|
|
||||||
|
|
||||||
class DoubaoImage:
|
class DoubaoImage:
|
||||||
"""豆包生图 —— 1:1 复刻字节跳动 Seedream 4 节点前端,后端对接豆包官方 API"""
|
"""豆包生图 —— 通过宽高比 + 分辨率档位选择尺寸,后端自动换算真实像素"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(cls):
|
def INPUT_TYPES(cls):
|
||||||
@@ -84,55 +138,33 @@ class DoubaoImage:
|
|||||||
"tooltip": "用于创建或编辑图像的文本提示",
|
"tooltip": "用于创建或编辑图像的文本提示",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
"尺寸预设": (
|
"宽高比": (
|
||||||
_PRESET_LABELS,
|
_ASPECT_RATIOS,
|
||||||
{
|
{
|
||||||
"default": _PRESET_LABELS[0],
|
"default": "1:1",
|
||||||
"tooltip": '选择推荐尺寸。选择"自定义"可使用下方的宽度和高度',
|
"tooltip": "图像宽高比。所有分辨率档位均支持这些比例",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
"宽度": (
|
"分辨率": (
|
||||||
"INT",
|
_RESOLUTIONS,
|
||||||
{
|
{
|
||||||
"default": 2048,
|
"default": "2K",
|
||||||
"min": 1024,
|
|
||||||
"max": 6240,
|
|
||||||
"step": 64,
|
|
||||||
"tooltip": '图像的自定义宽度。仅当尺寸预设设置为"自定义"时生效',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
"高度": (
|
|
||||||
"INT",
|
|
||||||
{
|
|
||||||
"default": 2048,
|
|
||||||
"min": 1024,
|
|
||||||
"max": 4992,
|
|
||||||
"step": 64,
|
|
||||||
"tooltip": '图像的自定义高度。仅当尺寸预设设置为"自定义"时生效',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
"顺序图像生成": (
|
|
||||||
["disabled", "auto"],
|
|
||||||
{
|
|
||||||
"default": "disabled",
|
|
||||||
"tooltip": (
|
"tooltip": (
|
||||||
'分组图像生成模式。'
|
"图像分辨率档位。\n"
|
||||||
'"disabled"生成单张图像;'
|
"• Seedream 5.0:支持 2K / 3K\n"
|
||||||
'"auto"让模型决定是否生成多张相关图像(如故事场景、角色变体)'
|
"• Seedream 4.5:支持 2K / 4K\n"
|
||||||
|
"(3K 与 4.5 或 4K 与 5.0 搭配时将报错)"
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
"最大图片数": (
|
"生图数量": (
|
||||||
"INT",
|
"INT",
|
||||||
{
|
{
|
||||||
"default": 1,
|
"default": 1,
|
||||||
"min": 1,
|
"min": 1,
|
||||||
"max": 15,
|
"max": 10,
|
||||||
"step": 1,
|
"step": 1,
|
||||||
"tooltip": (
|
"tooltip": "生成图像的数量。2-10 张时自动并发请求,加快出图速度",
|
||||||
"当顺序图像生成='auto'时生成的最大图像数量。"
|
|
||||||
"总图像数(输入+生成)不能超过15张"
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
"种子": (
|
"种子": (
|
||||||
@@ -150,7 +182,10 @@ class DoubaoImage:
|
|||||||
"BOOLEAN",
|
"BOOLEAN",
|
||||||
{
|
{
|
||||||
"default": True,
|
"default": True,
|
||||||
"tooltip": "如果启用,当任何请求的图像缺失或返回错误时将中止执行",
|
"tooltip": (
|
||||||
|
"启用时:任意一张失败即抛出错误并中止。\n"
|
||||||
|
"禁用时:返回已成功生成的图像,忽略失败项"
|
||||||
|
),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -172,106 +207,201 @@ class DoubaoImage:
|
|||||||
FUNCTION = "generate"
|
FUNCTION = "generate"
|
||||||
CATEGORY = "comfyui_o1key/豆包"
|
CATEGORY = "comfyui_o1key/豆包"
|
||||||
|
|
||||||
# ── 核心生成方法 ──────────────────────────────────────────────────────────
|
# ── 并发核心:在新 event loop 里 gather N 个 _generate_async ─────────────
|
||||||
|
|
||||||
|
async def _run_concurrent(
|
||||||
|
self,
|
||||||
|
client: DoubaoImageClient,
|
||||||
|
生图数量: int,
|
||||||
|
model: str,
|
||||||
|
prompt: str,
|
||||||
|
size: str,
|
||||||
|
seed: int,
|
||||||
|
image_tensor,
|
||||||
|
pbar,
|
||||||
|
) -> List[dict]:
|
||||||
|
"""
|
||||||
|
并发发起 生图数量 个独立请求,每完成一个推进一格进度条。
|
||||||
|
返回结果列表:[{"index": int, "images": [...], "error": str|None}]
|
||||||
|
"""
|
||||||
|
# 固定参数(顺序生成功能暂时隐藏)
|
||||||
|
seq = "disabled"
|
||||||
|
max_img = 1
|
||||||
|
|
||||||
|
async def _one(idx: int) -> dict:
|
||||||
|
try:
|
||||||
|
imgs = await client._generate_async(
|
||||||
|
model=model,
|
||||||
|
prompt=prompt,
|
||||||
|
size=size,
|
||||||
|
seed=seed,
|
||||||
|
sequential_image_generation=seq,
|
||||||
|
max_images=max_img,
|
||||||
|
image_tensor=image_tensor,
|
||||||
|
)
|
||||||
|
return {"index": idx, "images": imgs, "error": None}
|
||||||
|
except Exception as e:
|
||||||
|
return {"index": idx, "images": [], "error": str(e)}
|
||||||
|
|
||||||
|
# 用 as_completed 方式逐个推进进度条
|
||||||
|
tasks = [asyncio.create_task(_one(i)) for i in range(生图数量)]
|
||||||
|
results = [None] * 生图数量
|
||||||
|
completed = 0
|
||||||
|
|
||||||
|
for coro in asyncio.as_completed(tasks):
|
||||||
|
res = await coro
|
||||||
|
results[res["index"]] = res
|
||||||
|
completed += 1
|
||||||
|
status = "✓" if res["error"] is None else f"✗ {res['error']}"
|
||||||
|
print(f"[豆包生图] [{completed}/{生图数量}] 第 {res['index'] + 1} 张 → {status}")
|
||||||
|
if pbar is not None:
|
||||||
|
pbar.update(1)
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
# ── 节点主入口 ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def generate(
|
def generate(
|
||||||
self,
|
self,
|
||||||
模型: str,
|
模型: str,
|
||||||
提示词: str,
|
提示词: str,
|
||||||
尺寸预设: str,
|
宽高比: str,
|
||||||
宽度: int,
|
分辨率: str,
|
||||||
高度: int,
|
生图数量: int,
|
||||||
顺序图像生成: str,
|
|
||||||
最大图片数: int,
|
|
||||||
种子: int,
|
种子: int,
|
||||||
部分失败时停止: bool,
|
部分失败时停止: bool,
|
||||||
图像=None,
|
图像=None,
|
||||||
):
|
):
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
|
|
||||||
|
# 顺序图像生成功能暂时隐藏,固定使用默认值
|
||||||
|
顺序图像生成 = "disabled"
|
||||||
|
最大图片数 = 1
|
||||||
|
|
||||||
# ── 1. 校验提示词 ─────────────────────────────────────────────────────
|
# ── 1. 校验提示词 ─────────────────────────────────────────────────────
|
||||||
if not 提示词.strip():
|
if not 提示词.strip():
|
||||||
raise ValueError("提示词不能为空,请输入图像描述后重试。")
|
raise ValueError("提示词不能为空,请输入图像描述后重试。")
|
||||||
|
|
||||||
# ── 2. 解析尺寸 ───────────────────────────────────────────────────────
|
# ── 2. 解析模型版本并校验分辨率兼容性 ────────────────────────────────
|
||||||
w, h = None, None
|
try:
|
||||||
for label, tw, th in RECOMMENDED_PRESETS_SEEDREAM_4:
|
mkey = _model_key(模型)
|
||||||
if label == 尺寸预设:
|
except ValueError as e:
|
||||||
w, h = tw, th
|
raise ValueError(str(e)) from None
|
||||||
break
|
|
||||||
|
|
||||||
if w is None or h is None:
|
supported = _MODEL_RESOLUTIONS[mkey]
|
||||||
# 自定义尺寸
|
if 分辨率 not in supported:
|
||||||
w, h = 宽度, 高度
|
raise ValueError(
|
||||||
print(f"[豆包生图] 自定义尺寸:{w}×{h}")
|
f"模型 {模型} 不支持 {分辨率} 分辨率。\n"
|
||||||
|
f"该模型支持:{' / '.join(supported)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── 3. 查表换算真实像素 ───────────────────────────────────────────────
|
||||||
|
w, h = _SIZE_TABLE[mkey][分辨率][宽高比]
|
||||||
size_str = f"{w}x{h}"
|
size_str = f"{w}x{h}"
|
||||||
|
|
||||||
# ── 3. 打印概要 ───────────────────────────────────────────────────────
|
# ── 4. 打印概要 ───────────────────────────────────────────────────────
|
||||||
mode_str = "图生图" if 图像 is not None else "文生图"
|
mode_str = "图生图" if 图像 is not None else "文生图"
|
||||||
seq_str = f" | 顺序生成=auto(最多{最大图片数}张)" if 顺序图像生成 == "auto" else ""
|
|
||||||
print(
|
print(
|
||||||
f"[豆包生图] {mode_str} | 模型={模型} | 尺寸={size_str}"
|
f"[豆包生图] {mode_str} | 模型={模型} | {分辨率} {宽高比} → {size_str}"
|
||||||
f" | 种子={种子}{seq_str}"
|
f" | 数量={生图数量} | 种子={种子}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# ── 4. 进度条 ─────────────────────────────────────────────────────────
|
# ── 5. 初始化客户端 ───────────────────────────────────────────────────
|
||||||
try:
|
|
||||||
from comfy.utils import ProgressBar
|
|
||||||
pbar = ProgressBar(100)
|
|
||||||
except Exception:
|
|
||||||
pbar = None
|
|
||||||
|
|
||||||
def _pb(pct: int):
|
|
||||||
if pbar:
|
|
||||||
pbar.update_absolute(pct, 100)
|
|
||||||
|
|
||||||
_pb(0)
|
|
||||||
|
|
||||||
# ── 5. 调用客户端 ─────────────────────────────────────────────────────
|
|
||||||
try:
|
try:
|
||||||
client = DoubaoImageClient()
|
client = DoubaoImageClient()
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise ValueError(str(e)) from None
|
raise ValueError(str(e)) from None
|
||||||
|
|
||||||
_pb(5)
|
# ── 6. 进度条(按张数计)──────────────────────────────────────────────
|
||||||
|
|
||||||
try:
|
try:
|
||||||
pil_images: List[Image.Image] = client.generate_sync(
|
from comfy.utils import ProgressBar
|
||||||
model=模型,
|
pbar = ProgressBar(生图数量)
|
||||||
prompt=提示词,
|
except Exception:
|
||||||
size=size_str,
|
pbar = None
|
||||||
seed=种子,
|
|
||||||
sequential_image_generation=顺序图像生成,
|
|
||||||
max_images=最大图片数,
|
|
||||||
image_tensor=图像,
|
|
||||||
)
|
|
||||||
except RuntimeError as e:
|
|
||||||
raise RuntimeError(str(e)) from None
|
|
||||||
except Exception as e:
|
|
||||||
raise RuntimeError(f"豆包生图请求失败: {e}") from None
|
|
||||||
|
|
||||||
_pb(90)
|
# ── 7. 单张 / 多张分支 ────────────────────────────────────────────────
|
||||||
|
if 生图数量 == 1:
|
||||||
# ── 6. 部分失败判断 ───────────────────────────────────────────────────
|
# 单张:走原有同步路径
|
||||||
if 顺序图像生成 == "auto" and 部分失败时停止:
|
try:
|
||||||
if len(pil_images) < 最大图片数:
|
pil_images: List[Image.Image] = client.generate_sync(
|
||||||
raise RuntimeError(
|
model=模型,
|
||||||
f"部分图像生成失败:期望 {最大图片数} 张,"
|
prompt=提示词,
|
||||||
f"实际返回 {len(pil_images)} 张。"
|
size=size_str,
|
||||||
"(可将【部分失败时停止】设为 False 以接受不完整结果)"
|
seed=种子,
|
||||||
|
sequential_image_generation=顺序图像生成,
|
||||||
|
max_images=最大图片数,
|
||||||
|
image_tensor=图像,
|
||||||
)
|
)
|
||||||
|
except RuntimeError as e:
|
||||||
|
raise RuntimeError(str(e)) from None
|
||||||
|
except Exception as e:
|
||||||
|
raise RuntimeError(f"豆包生图请求失败: {e}") from None
|
||||||
|
|
||||||
# ── 7. PIL → tensor ───────────────────────────────────────────────────
|
if pbar is not None:
|
||||||
|
pbar.update(1)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# 多张:并发请求
|
||||||
|
def _run_in_thread():
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
asyncio.set_event_loop(loop)
|
||||||
|
try:
|
||||||
|
return loop.run_until_complete(
|
||||||
|
self._run_concurrent(
|
||||||
|
client=client,
|
||||||
|
生图数量=生图数量,
|
||||||
|
model=模型,
|
||||||
|
prompt=提示词,
|
||||||
|
size=size_str,
|
||||||
|
seed=种子,
|
||||||
|
image_tensor=图像,
|
||||||
|
pbar=pbar,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
loop.close()
|
||||||
|
|
||||||
|
with ThreadPoolExecutor(max_workers=1) as executor:
|
||||||
|
future = executor.submit(_run_in_thread)
|
||||||
|
try:
|
||||||
|
results = future.result(timeout=_CONCURRENT_TIMEOUT)
|
||||||
|
except TimeoutError:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"并发生图超时(>{_CONCURRENT_TIMEOUT}s),请检查网络或减少生图数量"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 统计成功 / 失败
|
||||||
|
success_results = [r for r in results if r and r["error"] is None]
|
||||||
|
failed_results = [r for r in results if r and r["error"] is not None]
|
||||||
|
|
||||||
|
if failed_results:
|
||||||
|
fail_info = ";".join(
|
||||||
|
f"第{r['index']+1}张: {r['error']}" for r in failed_results
|
||||||
|
)
|
||||||
|
if 部分失败时停止:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"{len(failed_results)}/{生图数量} 张生成失败:{fail_info}\n"
|
||||||
|
"(可将【部分失败时停止】设为 False 以返回已成功的图像)"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print(f"[豆包生图] 警告:{len(failed_results)}/{生图数量} 张失败,已忽略:{fail_info}")
|
||||||
|
|
||||||
|
if not success_results:
|
||||||
|
raise RuntimeError("所有图像均生成失败,请检查网络或 API 配置。")
|
||||||
|
|
||||||
|
# 按原始 index 排序,展平为 PIL 列表
|
||||||
|
success_results.sort(key=lambda r: r["index"])
|
||||||
|
pil_images = []
|
||||||
|
for r in success_results:
|
||||||
|
pil_images.extend(r["images"])
|
||||||
|
|
||||||
|
# ── 8. PIL → tensor ───────────────────────────────────────────────────
|
||||||
output_tensor = _pil_list_to_tensor(pil_images)
|
output_tensor = _pil_list_to_tensor(pil_images)
|
||||||
|
|
||||||
_pb(100)
|
# ── 9. 完成日志 ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
# ── 8. 完成日志 ───────────────────────────────────────────────────────
|
|
||||||
elapsed = time.time() - start_time
|
elapsed = time.time() - start_time
|
||||||
time_str = f"{elapsed:.1f}s"
|
|
||||||
print(
|
print(
|
||||||
f"[豆包生图] 完成!耗时 {time_str},"
|
f"[豆包生图] 完成!耗时 {elapsed:.1f}s,"
|
||||||
f"输出 {output_tensor.shape[0]} 张 "
|
f"输出 {output_tensor.shape[0]} 张 "
|
||||||
f"{output_tensor.shape[2]}×{output_tensor.shape[1]}"
|
f"{output_tensor.shape[2]}×{output_tensor.shape[1]}"
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user