add doubao-seedance node

This commit is contained in:
jueming
2025-07-26 11:12:00 +08:00
parent 86dc22674c
commit c672dd3734
3 changed files with 466 additions and 10 deletions
+114 -8
View File
@@ -20,7 +20,7 @@
- 视频时长固定5秒
- 自动下载和保存视频文件
### 3. Volcengine Img Edit V3.0 - 图生图指令编辑节点 (新增)
### 3. Volcengine Img Edit V3.0 - 图生图指令编辑节点
- 火山引擎图生图3.0指令编辑模型
- 根据文字指令智能编辑图片内容
- 支持精确的编辑强度控制(0.1-1.0)
@@ -28,6 +28,16 @@
- 保持原图主体结构的同时进行局部编辑
- 自动保存编辑后的图片
### 4. Volcengine Doubao Seedance - 豆包Seedance视频生成节点 (新增)
- 火山引擎豆包Seedance视频生成模型
- 支持文生视频和图生视频两种模式
- 支持所有模型文本命令参数:分辨率、宽高比、时长、帧率等
- 支持多种视频分辨率:480p、720p、1080p
- 支持9种宽高比和2种特殊比例模式
- 支持5秒和10秒视频时长选择
- 异步任务处理,自动轮询查询结果
- 自动下载和保存视频文件
## 安装
1. 克隆此仓库到 ComfyUI 的 custom_nodes 目录:
@@ -71,7 +81,7 @@ pip install -r requirements.txt
- **seed**: 随机种子 (可选,-1表示随机)
- **filename_prefix**: 保存文件名前缀 (可选)
### Volcengine Img Edit V3.0 使用 (新增功能)
### Volcengine Img Edit V3.0 使用
1. 在节点列表中找到 `JM-Volcengine-API/ImgEdit` 分类
2. 添加 `Volcengine Img Edit V3.0` 节点
3. 配置参数:
@@ -84,6 +94,24 @@ pip install -r requirements.txt
- **filename_prefix**: 保存文件名前缀 (可选)
- **return_url**: 是否返回图片URL链接 (可选,默认True,24小时有效)
### Volcengine Doubao Seedance 使用 (新增功能)
1. 在节点列表中找到 `JM-Volcengine-API/Video` 分类
2. 添加 `Volcengine Doubao Seedance` 节点
3. 配置参数:
- **ark_api_key**: 火山方舟API密钥
- **model**: 模型ID (默认doubao-seedance-1-0-pro-250528)
- **prompt**: 视频生成提示词,支持中英文
- **image**: 输入图片 (可选,图生视频模式)
- **image_role**: 图片角色 (可选,first_frame或last_frame)
- **resolution**: 视频分辨率 (可选,480p/720p/1080p)
- **ratio**: 视频宽高比 (可选,9种比例+特殊模式)
- **duration**: 视频时长 (可选,5秒或10秒)
- **framepersecond**: 帧率 (可选,16或24)
- **watermark**: 是否包含水印 (可选)
- **seed**: 随机种子 (可选,-1表示随机)
- **camerafixed**: 是否固定摄像头 (可选)
- **filename_prefix**: 保存文件名前缀 (可选)
## 参数说明
### SeeDream V3 参数
@@ -114,7 +142,7 @@ pip install -r requirements.txt
- 支持中英文,建议使用简洁明确的描述
- 可以包含镜头切换、人物动作、情绪演绎等描述
### Img Edit V3.0 参数 (新增)
### Img Edit V3.0 参数
- **prompt**: 编辑指令,支持中英文,建议长度<=120字符,描述具体的编辑需求,例如:
- "把天空改成夜晚"
- "给人物添加墨镜"
@@ -136,6 +164,41 @@ pip install -r requirements.txt
- 局部编辑时指令描述尽量精准
- 发现编辑效果不明显时,可以调整scale数值
### Doubao Seedance 参数 (新增)
- **支持的视频分辨率**:
- 480p → 低分辨率
- 720p → 标准高清(默认)
- 1080p → 全高清
- **支持的宽高比**:
- 21:9 → 超宽屏比例
- 16:9 → 标准横屏比例
- 4:3 → 传统电视比例
- 1:1 → 正方形比例
- 3:4 → 竖屏比例
- 9:16 → 手机竖屏比例
- 9:21 → 超高竖屏比例
- keep_ratio → 保持输入图片的宽高比
- adaptive → 根据输入图片自动选择最合适的宽高比(默认)
- **视频时长**:
- 5秒(默认)
- 10秒
- **帧率选项**:
- 16fps → 较低帧率
- 24fps → 标准帧率(默认)
- **图片角色**(图生视频模式):
- first_frame → 首帧图片(默认)
- last_frame → 尾帧图片
- **使用建议**:
- 提示词描述要清晰具体,避免过于抽象
- 图生视频模式下,输入图片质量会影响生成效果
- 建议根据实际需求选择合适的分辨率和时长
- 使用keep_ratio或adaptive可以更好地适配输入图片
## 输出说明
### SeeDream V3 输出
@@ -147,11 +210,14 @@ pip install -r requirements.txt
- **video_url**: 生成的视频URL链接 (有效期1小时)
- **local_video_path**: 本地保存的视频文件路径
### Img Edit V3.0 输出 (新增)
### Img Edit V3.0 输出
- **image**: 编辑后的图片张量,可连接到其他节点
- **image_url**: 图片的URL链接(当return_url=True时)或Base64数据信息(当return_url=False时)
- **local_image_path**: 本地保存的图片文件路径
### Doubao Seedance 输出 (新增)
- **video_path**: 本地保存的视频文件路径
## 注意事项
### 通用注意事项
@@ -172,7 +238,7 @@ pip install -r requirements.txt
- 输入图片和提示词需要通过内容审核
- 建议根据输入图片的实际比例选择合适的aspect_ratio
### Img Edit V3.0 特定注意事项 (新增)
### Img Edit V3.0 特定注意事项
- 使用SeedEdit 3.0指令编辑模型,服务标识固定为"seededit_v3.0"
- 编辑效果取决于输入图片质量和编辑指令的准确性
- 建议使用清晰、具体的编辑指令以获得更好的效果
@@ -182,6 +248,17 @@ pip install -r requirements.txt
- 编辑后的图片保持原图的基本构图和主体结构
- 输入图片格式支持JPEG、PNG,最大5MB,分辨率最大4096×4096
### Doubao Seedance 特定注意事项 (新增)
- 视频生成为异步处理,通常需要几分钟时间
- 程序会自动轮询查询结果,请耐心等待
- 支持文生视频和图生视频两种模式
- 图生视频模式下,输入图片质量会影响生成效果
- 生成的视频URL有效期为24小时,请及时下载保存
- 输入图片和提示词需要通过内容审核
- 建议根据实际用途选择合适的分辨率和时长参数
- 所有模型文本命令参数都可以通过UI直接设置
- 使用keep_ratio或adaptive可以更好地适配输入图片比例
## 错误处理
### 常见错误及解决方案
@@ -198,7 +275,7 @@ pip install -r requirements.txt
- **任务超时**: 可能服务器繁忙,可稍后重试
- **宽高比不匹配**: 建议选择与输入图片比例接近的aspect_ratio
#### Img Edit V3.0 (新增)
#### Img Edit V3.0
- **认证失败**: 检查AccessKey和SecretKey是否正确并有相应权限
- **编辑指令错误**: 检查编辑指令是否清晰明确,避免过于复杂的描述
- **提示词过长**: 确保提示词长度不超过120字符
@@ -207,6 +284,16 @@ pip install -r requirements.txt
- **任务超时**: 可能服务器繁忙,任务处理时间过长,可稍后重试
- **任务未找到或过期**: 任务可能已过期(12小时),请重新提交
#### Doubao Seedance (新增)
- **认证失败**: 检查ARK API密钥是否正确并有相应权限
- **模型不可用**: 确认所选模型是否已开通并可正常使用
- **提示词审核未通过**: 检查提示词内容是否符合平台规范
- **图片审核未通过**: 检查输入图片内容是否符合平台规范
- **参数错误**: 确认所有参数值是否在支持范围内
- **任务超时**: 可能服务器繁忙,可稍后重试
- **视频生成失败**: 检查提示词和参数设置,重新尝试
- **下载失败**: 网络连接问题,检查网络状态
## 技术实现
### SeeDream V3
@@ -221,7 +308,7 @@ pip install -r requirements.txt
- 完整的错误处理和重试机制
- 视频文件自动下载和命名管理
### Img Edit V3.0 (新增)
### Img Edit V3.0
- 实现完整的火山引擎SeedEdit 3.0异步接口调用
- 支持任务提交和轮询查询的异步处理机制
- 智能图片格式转换和处理
@@ -229,6 +316,16 @@ pip install -r requirements.txt
- 完善的错误处理和异常捕获
- 自动文件命名和保存管理
### Doubao Seedance (新增)
- 实现火山方舟标准API调用流程
- 支持异步任务提交和轮询查询机制
- 完整的模型文本命令参数支持
- 智能图片格式转换和Base64编码
- 支持文生视频和图生视频两种模式
- 完善的错误处理和重试机制
- 视频文件自动下载和命名管理
- 支持所有官方模型参数配置
## 系统要求
- ComfyUI 环境
- Python 3.8+
@@ -237,7 +334,16 @@ pip install -r requirements.txt
## 更新日志
### v3.0.0 (最新)
### v4.0.0 (最新)
- 新增 Volcengine Doubao Seedance 视频生成节点
- 支持火山引擎豆包Seedance模型的文生视频和图生视频功能
- 完整支持所有模型文本命令参数:分辨率、宽高比、时长、帧率、水印、种子、摄像头固定等
- 支持3种分辨率、9种宽高比和2种特殊比例模式
- 实现异步任务处理和自动轮询查询
- 自动视频下载和文件命名管理
- 优化用户界面参数配置
### v3.0.0
- 新增 Volcengine Img Edit V3.0 图生图指令编辑节点(SeedEdit 3.0)
- 支持根据文字指令智能编辑图片内容
- 实现异步任务处理机制,支持任务提交和轮询查询
+5 -2
View File
@@ -1,17 +1,20 @@
from .nodes.volcengine_seedream_v3 import VolcengineSeeDreamV3Node
from .nodes.volcengine_i2v_s2pro import VolcengineI2VS2Pro
from .nodes.volcengine_img_edit_v3 import VolcengineImgEditV3
from .nodes.volcengine_doubao_seedance import VolcengineDoubaoSeedance
NODE_CLASS_MAPPINGS = {
"volcengine-seedream-v3": VolcengineSeeDreamV3Node,
"volcengine-i2v-s2pro": VolcengineI2VS2Pro,
"volcengine-img-edit-v3": VolcengineImgEditV3
"volcengine-img-edit-v3": VolcengineImgEditV3,
"volcengine-doubao-seedance": VolcengineDoubaoSeedance
}
NODE_DISPLAY_NAME_MAPPINGS = {
"volcengine-seedream-v3": "Volcengine SeeDream V3",
"volcengine-i2v-s2pro": "Volcengine I2V S2.0Pro",
"volcengine-img-edit-v3": "Volcengine Img Edit V3.0"
"volcengine-img-edit-v3": "Volcengine Img Edit V3.0",
"volcengine-doubao-seedance": "Volcengine Doubao Seedance"
}
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
+347
View File
@@ -0,0 +1,347 @@
import json
import time
import base64
import hashlib
import hmac
import datetime
from urllib.parse import urlencode
import requests
import torch
import numpy as np
from PIL import Image
import io
import os
import folder_paths
class VolcengineDoubaoSeedance:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"ark_api_key": ("STRING", {
"default": "",
"multiline": False,
"tooltip": "火山方舟API密钥"
}),
"model": (["doubao-seedance-1-0-pro-250528"], {
"default": "doubao-seedance-1-0-pro-250528",
"tooltip": "模型ID"
}),
"prompt": ("STRING", {
"default": "",
"multiline": True,
"tooltip": "视频生成提示词,支持中英文"
}),
},
"optional": {
"image": ("IMAGE", {
"tooltip": "输入图片(图生视频模式)"
}),
"image_role": (["first_frame", "last_frame"], {
"default": "first_frame",
"tooltip": "图片角色:首帧或尾帧"
}),
"resolution": (["480p", "720p", "1080p"], {
"default": "720p",
"tooltip": "视频分辨率"
}),
"ratio": (["21:9", "16:9", "4:3", "1:1", "3:4", "9:16", "9:21", "keep_ratio", "adaptive"], {
"default": "adaptive",
"tooltip": "视频宽高比"
}),
"duration": ([5, 10], {
"default": 5,
"tooltip": "视频时长(秒)"
}),
"framepersecond": ([16, 24], {
"default": 24,
"tooltip": "帧率"
}),
"watermark": ("BOOLEAN", {
"default": False,
"tooltip": "是否包含水印"
}),
"seed": ("INT", {
"default": -1,
"min": -1,
"max": 2**32 - 1,
"tooltip": "随机种子,-1表示随机"
}),
"camerafixed": ("BOOLEAN", {
"default": False,
"tooltip": "是否固定摄像头"
}),
"filename_prefix": ("STRING", {
"default": "doubao_seedance",
"tooltip": "保存文件名前缀"
}),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("video_path",)
FUNCTION = "generate_video"
CATEGORY = "JM-Volcengine-API/Video"
DESCRIPTION = "火山引擎豆包Seedance视频生成模型 - 支持文生视频和图生视频"
def __init__(self):
self.base_url = "https://ark.cn-beijing.volces.com/api/v3/contents/generations/tasks"
def image_to_base64(self, image_tensor):
"""将ComfyUI图片张量转换为Base64"""
# image_tensor shape: [1, H, W, 3]
if image_tensor.dim() == 4:
image_tensor = image_tensor.squeeze(0) # 移除batch维度
# 转换为numpy数组并确保数据类型正确
image_np = image_tensor.cpu().numpy()
if image_np.dtype != np.uint8:
image_np = (image_np * 255).astype(np.uint8)
# 转换为PIL图像
pil_image = Image.fromarray(image_np)
# 转换为Base64
buffer = io.BytesIO()
pil_image.save(buffer, format='PNG')
image_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8')
return f"data:image/png;base64,{image_base64}"
def build_text_command(self, prompt, resolution="720p", ratio="adaptive", duration=5,
framepersecond=24, watermark=False, seed=-1, camerafixed=False):
"""构建文本命令字符串"""
commands = []
# 添加基础提示词
text_content = prompt.strip()
# 添加参数命令
if resolution != "720p":
commands.append(f"--resolution {resolution}")
if ratio != "adaptive":
commands.append(f"--ratio {ratio}")
if duration != 5:
commands.append(f"--duration {duration}")
if framepersecond != 24:
commands.append(f"--framepersecond {framepersecond}")
if watermark:
commands.append("--watermark true")
if seed != -1:
commands.append(f"--seed {seed}")
if camerafixed:
commands.append("--camerafixed true")
# 组合完整的文本内容
if commands:
text_content += " " + " ".join(commands)
return text_content
def create_task(self, ark_api_key, model, content_list):
"""创建视频生成任务"""
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {ark_api_key}"
}
payload = {
"model": model,
"content": content_list
}
try:
response = requests.post(self.base_url, headers=headers, json=payload, timeout=30)
response.raise_for_status()
result = response.json()
if "id" in result:
return result["id"]
else:
print(f"创建任务失败: {result}")
return None
except Exception as e:
print(f"创建任务时发生错误: {str(e)}")
return None
def query_task(self, ark_api_key, task_id, max_retries=60, retry_interval=10):
"""查询任务结果"""
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {ark_api_key}"
}
query_url = f"{self.base_url}/{task_id}"
for attempt in range(max_retries):
try:
response = requests.get(query_url, headers=headers, timeout=30)
response.raise_for_status()
result = response.json()
status = result.get("status")
print(f"查询任务 {task_id} 状态: {status} (尝试 {attempt + 1}/{max_retries})")
if status == "succeeded":
content = result.get("content", {})
video_url = content.get("video_url")
if video_url:
print(f"任务完成,视频URL: {video_url}")
return {"status": "success", "video_url": video_url, "result": result}
else:
print("任务成功但未找到视频URL")
return {"status": "error", "message": "未找到视频URL"}
elif status == "failed":
error_info = result.get("error", {})
error_message = error_info.get("message", "任务失败")
print(f"任务失败: {error_message}")
return {"status": "error", "message": error_message}
elif status == "cancelled":
print("任务被取消")
return {"status": "error", "message": "任务被取消"}
elif status in ["queued", "running"]:
print(f"任务进行中,状态: {status},等待 {retry_interval} 秒后重试...")
time.sleep(retry_interval)
continue
else:
print(f"未知状态: {status}")
time.sleep(retry_interval)
continue
except Exception as e:
print(f"查询任务时发生错误: {str(e)}")
if attempt < max_retries - 1:
time.sleep(retry_interval)
continue
else:
return {"status": "error", "message": f"查询任务失败: {str(e)}"}
return {"status": "error", "message": "任务超时"}
def download_video(self, video_url, filename_prefix):
"""下载视频到ComfyUI output目录"""
try:
# 确保输出目录存在
output_dir = folder_paths.get_output_directory()
os.makedirs(output_dir, exist_ok=True)
# 生成唯一文件名
counter = 1
while True:
filename = f"{filename_prefix}_{counter:04d}.mp4"
file_path = os.path.join(output_dir, filename)
if not os.path.exists(file_path):
break
counter += 1
print(f"开始下载视频到: {file_path}")
# 下载视频
response = requests.get(video_url, stream=True, timeout=300)
response.raise_for_status()
with open(file_path, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
if chunk:
f.write(chunk)
print(f"视频下载成功: {file_path}")
return file_path
except Exception as e:
print(f"下载视频时发生错误: {str(e)}")
return None
def generate_video(self, ark_api_key, model, prompt, image=None, image_role="first_frame",
resolution="720p", ratio="adaptive", duration=5, framepersecond=24,
watermark=False, seed=-1, camerafixed=False, filename_prefix="doubao_seedance"):
"""主要的视频生成函数"""
# 验证必需参数
if not ark_api_key:
return ("错误:请提供有效的ARK API密钥",)
if not prompt.strip():
return ("错误:请提供视频生成提示词",)
try:
# 构建内容数组
content_list = []
# 构建文本命令
text_with_commands = self.build_text_command(
prompt, resolution, ratio, duration, framepersecond,
watermark, seed, camerafixed
)
# 添加文本内容
content_list.append({
"type": "text",
"text": text_with_commands
})
# 如果有图片,添加图片内容(图生视频模式)
if image is not None:
print("检测到输入图片,使用图生视频模式")
image_base64 = self.image_to_base64(image)
image_content = {
"type": "image_url",
"image_url": {
"url": image_base64
}
}
# 如果指定了图片角色,添加role字段
if image_role in ["first_frame", "last_frame"]:
image_content["role"] = image_role
content_list.append(image_content)
else:
print("使用文生视频模式")
print(f"创建视频生成任务...")
print(f"模型: {model}")
print(f"提示词: {text_with_commands}")
# 创建任务
task_id = self.create_task(ark_api_key, model, content_list)
if not task_id:
return ("错误:任务创建失败",)
print(f"任务创建成功,task_id: {task_id}")
print("开始查询任务状态...")
# 查询任务结果
result = self.query_task(ark_api_key, task_id)
if result["status"] != "success":
return (f"错误:{result['message']}",)
video_url = result["video_url"]
print(f"获取到视频URL: {video_url}")
# 下载视频
video_path = self.download_video(video_url, filename_prefix)
if not video_path:
return ("错误:视频下载失败",)
return (video_path,)
except Exception as e:
print(f"生成视频时发生错误: {str(e)}")
return (f"错误:{str(e)}",)