Files

168 lines
6.1 KiB
Python
Raw Permalink 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.
"""
Kling V3 - Unified text/image-to-video and motion control model
"""
import time
from .modelverse_api.client import ModelverseClient
from .modelverse_api.requests.kling_common import (
ASPECT_RATIOS,
CHARACTER_ORIENTATIONS,
KLING_V3_TYPES,
MODEL_KLING_V3,
MODES,
SOUNDS,
YES_NO,
)
from .modelverse_api.requests.kling_v3 import KlingV3
from comfy.comfy_types.node_typing import IO
class KlingV3Node:
"""
Kling V3 unified video generation.
Auto-routes to text-to-video, image-to-video, or motion control.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"client": ("MODELVERSE_API_CLIENT",),
"prompt": (IO.STRING, {
"multiline": True,
"default": "",
"tooltip": "Text prompt describing the desired video",
}),
},
"optional": {
"negative_prompt": (IO.STRING, {
"multiline": True,
"default": "",
"tooltip": "Negative prompt to avoid unwanted content",
}),
"kling_v3_type": (KLING_V3_TYPES, {
"default": "auto",
"tooltip": "Task type; auto infers from inputs (image→i2v, video→motion_control)",
}),
"first_frame_image": (IO.IMAGE, {"tooltip": "First frame image (image-to-video / motion control)"}),
"first_frame_url": (IO.STRING, {
"default": "",
"tooltip": "First frame URL (use either this OR first_frame_image, not both)",
}),
"last_frame_image": (IO.IMAGE, {"tooltip": "Last frame image (image-to-video)"}),
"last_frame_url": (IO.STRING, {
"default": "",
"tooltip": "Last frame URL (use either this OR last_frame_image, not both)",
}),
"reference_video_url": (IO.STRING, {
"default": "",
"tooltip": "Reference video URL (motion control; auto-selects motion_control when set)",
}),
"aspect_ratio": (ASPECT_RATIOS, {"default": "16:9", "tooltip": "Output aspect ratio"}),
"duration": (IO.INT, {
"default": 5, "min": 3, "max": 15, "step": 1,
"tooltip": "Video duration in seconds (3–15)",
}),
"mode": (MODES, {"default": "std", "tooltip": "std: 720p, pro: 1080p"}),
"sound": (SOUNDS, {"default": "off", "tooltip": "Generate synchronized audio"}),
"shot_type": (IO.STRING, {
"default": "",
"tooltip": "Optional shot type, e.g. 'multi' for multi-shot",
}),
"character_orientation": (CHARACTER_ORIENTATIONS, {
"default": "image",
"tooltip": "Motion control: follow image or video orientation",
}),
"keep_original_sound": (YES_NO, {
"default": "no",
"tooltip": "Motion control: keep original reference video sound",
}),
},
}
RETURN_TYPES = (IO.STRING, IO.STRING)
RETURN_NAMES = ("url", "task_id")
FUNCTION = "generate"
CATEGORY = "UCLOUD_MODELVERSE/Kling"
def generate(
self,
client,
prompt,
negative_prompt="",
kling_v3_type="auto",
first_frame_image=None,
first_frame_url="",
last_frame_image=None,
last_frame_url="",
reference_video_url="",
aspect_ratio="16:9",
duration=5,
mode="std",
sound="off",
shot_type="",
character_orientation="image",
keep_original_sound="no",
):
api_key = client.get("api_key")
if not api_key:
raise ValueError("API key is not set")
request = KlingV3(
prompt=prompt,
negative_prompt=negative_prompt,
kling_v3_type=kling_v3_type,
first_frame=first_frame_image,
first_frame_url=first_frame_url,
last_frame=last_frame_image,
last_frame_url=last_frame_url,
reference_video_url=reference_video_url,
aspect_ratio=aspect_ratio,
duration=duration,
mode=mode,
sound=sound,
shot_type=shot_type,
character_orientation=character_orientation,
keep_original_sound=keep_original_sound,
)
mv_client = ModelverseClient(api_key)
print(f"Submitting Kling V3 task: model={MODEL_KLING_V3}, type={kling_v3_type}, prompt={prompt!r}")
submit_res = mv_client.submit_task_request(request)
task_id = submit_res.get("output", {}).get("task_id")
if not task_id:
raise Exception(f"Failed to submit task: {submit_res}")
print(f"Kling V3 task submitted: {task_id}")
video_url = self._poll_task(mv_client, task_id)
return (video_url, 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")
if task_status == "Failure":
error = status_res.get("output", {}).get("error_message", "Unknown error")
raise Exception(f"Task failed: {error}")
if task_status in ["Pending", "Running"]:
print(f"Task {task_id}: {task_status} ({i + 1}/{max_retries})")
time.sleep(5)
continue
raise Exception(f"Unknown status: {task_status}")
raise Exception("Task timed out")
NODE_CLASS_MAPPINGS = {
"Kling_V3": KlingV3Node,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"Kling_V3": "Modelverse Kling V3",
}