- Add new ModelversePreviewVideo node for downloading and previewing generated videos - Change all Vidu video generation nodes (extend, i2v, ref2v, startend2v, t2v) to return video URL and task ID instead of downloaded video objects - Change all Wan AI video generation nodes (i2v, t2v) to return video URL and task ID instead of downloaded video objects - Remove duplicate VideoFromFile import fallback logic from all video generation modules - Simplify video handling by delegating download responsibility to preview node - Improve separation of concerns between generation and preview workflows
121 lines
4.2 KiB
Python
121 lines
4.2 KiB
Python
"""
|
|
Vidu Extend - 视频延长模型
|
|
Models: viduq2-pro, viduq2-turbo
|
|
"""
|
|
import time
|
|
from .modelverse_api.client import ModelverseClient
|
|
from .modelverse_api.utils import image_to_base64
|
|
from comfy.comfy_types.node_typing import IO
|
|
|
|
|
|
MODELS = ["viduq2-pro", "viduq2-turbo"]
|
|
RESOLUTIONS = ["540p", "720p", "1080p"]
|
|
|
|
|
|
class ViduExtendNode:
|
|
"""
|
|
Vidu Extend - 视频延长
|
|
Models: viduq2-pro, viduq2-turbo
|
|
输入视频时长需4秒-1分钟,延长1-7秒
|
|
"""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"client": ("MODELVERSE_API_CLIENT",),
|
|
"model": (MODELS, {"default": "viduq2-turbo", "tooltip": "viduq2-pro: 效果好, viduq2-turbo: 生成快"}),
|
|
"video_url": (IO.STRING, {"default": "", "tooltip": "需要延长的视频URL (4秒-1分钟)"}),
|
|
"duration": (IO.INT, {"default": 5, "min": 1, "max": 7, "step": 1, "tooltip": "延长时长(秒), 1-7"}),
|
|
"resolution": (RESOLUTIONS, {"default": "720p", "tooltip": "分辨率"}),
|
|
},
|
|
"optional": {
|
|
"last_frame_image": (IO.IMAGE, {"tooltip": "延长到尾帧的参考图像"}),
|
|
"last_frame_url": (IO.STRING, {"default": "", "tooltip": "延长到尾帧的参考图像URL"}),
|
|
"prompt": (IO.STRING, {"multiline": True, "default": "", "tooltip": "延长提示词,控制延长内容"}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = (IO.STRING, IO.STRING)
|
|
RETURN_NAMES = ("url", "task_id")
|
|
FUNCTION = "generate"
|
|
CATEGORY = "UCLOUD_MODELVERSE/Vidu"
|
|
|
|
def generate(self, client, model, video_url, duration, resolution,
|
|
last_frame_image=None, last_frame_url="", prompt=""):
|
|
api_key = client.get("api_key")
|
|
if not api_key:
|
|
raise ValueError("API key is not set")
|
|
|
|
if not video_url or not video_url.strip():
|
|
raise ValueError("必须提供 video_url")
|
|
|
|
mv_client = ModelverseClient(api_key)
|
|
|
|
task_input = {
|
|
"video_url": video_url.strip(),
|
|
}
|
|
|
|
# Optional last frame
|
|
has_last_url = last_frame_url and last_frame_url.strip()
|
|
has_last_image = last_frame_image is not None
|
|
if has_last_url and has_last_image:
|
|
raise ValueError("尾帧:请提供 image 或 url,不能同时提供")
|
|
|
|
if has_last_url:
|
|
task_input["last_frame_url"] = last_frame_url.strip()
|
|
elif has_last_image:
|
|
task_input["last_frame_url"] = image_to_base64(last_frame_image)
|
|
|
|
if prompt and prompt.strip():
|
|
task_input["prompt"] = prompt.strip()
|
|
|
|
parameters = {
|
|
"vidu_type": "extend",
|
|
"duration": duration,
|
|
"resolution": resolution,
|
|
}
|
|
|
|
# Submit task
|
|
submit_res = mv_client.submit_task(model, task_input, parameters)
|
|
task_id = submit_res.get("output", {}).get("task_id")
|
|
if not task_id:
|
|
raise Exception(f"Failed to submit task: {submit_res}")
|
|
|
|
print(f"Vidu Extend task submitted: {task_id}")
|
|
|
|
# Poll for result
|
|
video_url_result = self._poll_task(mv_client, task_id)
|
|
|
|
return (video_url_result, task_id)
|
|
|
|
def _poll_task(self, mv_client, task_id, max_retries=180):
|
|
for i in range(max_retries):
|
|
status_res = mv_client.get_task_status(task_id)
|
|
task_status = status_res.get("output", {}).get("task_status")
|
|
|
|
if task_status == "Success":
|
|
urls = status_res.get("output", {}).get("urls", [])
|
|
if urls:
|
|
return urls[0]
|
|
raise Exception("Task succeeded but no video URL returned")
|
|
elif task_status == "Failure":
|
|
error = status_res.get("output", {}).get("error_message", "Unknown error")
|
|
raise Exception(f"Task failed: {error}")
|
|
elif task_status in ["Pending", "Running"]:
|
|
print(f"Task {task_id}: {task_status} ({i+1}/{max_retries})")
|
|
time.sleep(5)
|
|
else:
|
|
raise Exception(f"Unknown status: {task_status}")
|
|
|
|
raise Exception("Task timed out")
|
|
|
|
|
|
NODE_CLASS_MAPPINGS = {
|
|
"Vidu_Extend": ViduExtendNode,
|
|
}
|
|
|
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
|
"Vidu_Extend": "Vidu Extend",
|
|
}
|