feat: add kling 3 experimental nodes, preflight, and docs sync

This commit is contained in:
Dreaming Computers
2026-02-11 09:29:54 +07:00
parent 39493f8bcc
commit e1749f3cfa
14 changed files with 847 additions and 45 deletions
+12
View File
@@ -60,6 +60,11 @@ This node pack currently includes the following nodes:
- **Kling 2.6 Motion-Control Image-to-Video**
- Image-to-video generation with additional motion control parameters.
- Designed for more directed camera and motion behavior.
- **Kling 3.0 (Video)**
- Supports single-shot or multi-shot generation.
- Supports optional first/last frame control and named element references.
- In multi-shot mode, total duration is computed from shot durations (max 15s).
- **Experimental / development status:** not production-ready yet.
- **Seedance V1 Pro (Fast) Image-to-Video**
Fast image-to-video generation optimized for quick iteration.
- **Seedance 1.5 Pro Image-to-Video, Text-to-Video**
@@ -93,6 +98,11 @@ This node pack currently includes the following nodes:
- **System Prompt Selector**
- Combines a user prompt with a system prompt template from `prompts/`.
- See [prompts/README.md](prompts/README.md) for creating new templates.
- **Kling Elements + Kling Elements Batch**
- Build named Kling elements (image/video) and batch them for Kling 3.0 prompts using `@element_name`.
- **Kling 3.0 Preflight**
- Validates/uploads inputs and returns exact createTask payload JSON without running generation.
- Recommended before using Kling 3.0 generation while the node is in development.
Each node has its own documentation page under `web/docs/` with detailed inputs, outputs, and usage examples.
@@ -150,6 +160,8 @@ Feel free to adapt, simplify, or remix them to fit your own pipelines.
- 2026-01-30: Gemini 3 Pro LLM updated with role dropdown and media inputs (phase 1.5).
- 2026-01-30: Gemini 3 Pro LLM updated with audio input support.
- 2026-02-03: Suno Music nodes now return two songs and two cover images.
- 2026-02-11: Added Kling 3.0 video node and helper nodes for Kling elements + element batching.
- 2026-02-11: Added Kling 3.0 preflight node to validate and preview request payloads before generation.
## About Kie.ai
Kie.ai is a unified API and model marketplace for image, video, and audio generation. This project is community-maintained and not affiliated with Kie.ai. Learn more at [https://kie.ai](https://kie.ai?ref=e7565cf24a7fad4586341a87eaf21e42).
+2 -13
View File
@@ -5,8 +5,6 @@ Provides tensor-only helpers so nodes remain thin wrappers.
import torch
from .nanobanana import _log
VALID_GRIDS = {
"2x2": (2, 2),
@@ -33,7 +31,7 @@ def slice_grid_tensor(
gutter_px: Pixels removed between tiles inside the grid.
order: "row-major" or "column-major".
process_batch: "first" or "all".
log: Enable verbose logging via shared logger.
log: Reserved for compatibility with existing node signature.
Returns:
Tensor of tiles stacked on batch dimension.
@@ -96,20 +94,11 @@ def slice_grid_tensor(
for r in range(rows):
add_tile(r, c)
if log:
_log(
log,
f"Sliced image into {rows}x{cols} tiles of size {tile_h}x{tile_w} "
f"(outer_crop_px={outer_crop_px}, gutter_px={gutter_px}, order={order})",
)
return tiles
all_tiles: list[torch.Tensor] = []
for idx, img in enumerate(targets):
for img in targets:
tiles = slice_single(img)
all_tiles.extend(tiles)
if log and process_batch == "all":
_log(log, f"Processed image {idx + 1}/{len(targets)}.")
return torch.stack(all_tiles, dim=0)
+381
View File
@@ -0,0 +1,381 @@
"""Kling 3.0 video helpers and element preparation."""
import re
import time
from typing import Any
import torch
from .auth import _load_api_key
from .credits import _log_remaining_credits
from .jobs import _create_task, _poll_task_until_complete
from .log import _log
from .results import _extract_result_urls
from .upload import _image_tensor_to_png_bytes, _truncate_url, _upload_image, _upload_video
from .validation import _validate_prompt
from .video import _coerce_video_to_mp4_bytes, _download_video, _video_bytes_to_comfy_video
MODEL_NAME = "kling-3.0/video"
MODE_OPTIONS = ["std", "pro"]
ASPECT_RATIO_OPTIONS = ["1:1", "9:16", "16:9"]
DURATION_OPTIONS = [str(i) for i in range(3, 16)]
MULTI_SHOT_MIN = 1
MULTI_SHOT_MAX = 12
PROMPT_MAX_LENGTH = 2500
ELEMENT_IMAGE_MIN = 2
ELEMENT_IMAGE_MAX = 4
ELEMENT_BATCH_MAX = 9
def _validate_batch_image(images: torch.Tensor | None, label: str) -> torch.Tensor:
if images is None:
raise RuntimeError(f"{label} input is required.")
if not isinstance(images, torch.Tensor):
raise RuntimeError(f"{label} input must be a tensor batch.")
if images.dim() != 4 or images.shape[-1] != 3:
raise RuntimeError(f"{label} input must have shape [B, H, W, 3].")
if images.shape[0] < 1:
raise RuntimeError(f"{label} input batch is empty.")
return images
def _extract_referenced_elements(text: str) -> set[str]:
if not text:
return set()
return set(re.findall(r"@([a-zA-Z0-9_\\-]+)", text))
def _parse_multi_prompt_text(shots_text: str) -> list[dict[str, Any]]:
lines = [line.strip() for line in (shots_text or "").splitlines() if line.strip()]
if not lines:
raise RuntimeError("multi_shots is enabled but shots_text is empty.")
shots: list[dict[str, Any]] = []
for idx, line in enumerate(lines, start=1):
parts = [part.strip() for part in line.split("|")]
if len(parts) == 2:
duration_str, prompt = parts
elif len(parts) >= 3:
duration_str, prompt = parts[1], "|".join(parts[2:]).strip()
else:
raise RuntimeError(
f"Invalid shots_text format on line {idx}. "
"Use 'duration | prompt' or 'label | duration | prompt'."
)
try:
duration = int(duration_str)
except ValueError as exc:
raise RuntimeError(f"Invalid shot duration '{duration_str}' on line {idx}.") from exc
if duration < MULTI_SHOT_MIN or duration > MULTI_SHOT_MAX:
raise RuntimeError(
f"Shot duration on line {idx} must be between {MULTI_SHOT_MIN} and {MULTI_SHOT_MAX} seconds."
)
_validate_prompt(prompt, max_length=PROMPT_MAX_LENGTH)
shots.append({"prompt": prompt, "duration": duration})
return shots
def build_kling3_element(
*,
name: str,
description: str,
images: torch.Tensor | None,
video: Any | None,
log: bool,
) -> dict[str, Any]:
"""Build one Kling element with uploaded URLs for image or video."""
element_name = (name or "").strip()
if not element_name:
raise RuntimeError("Element name is required.")
if not re.match(r"^[a-zA-Z0-9_\\-]+$", element_name):
raise RuntimeError("Element name may only contain letters, numbers, underscore, or hyphen.")
has_images = images is not None
has_video = video is not None
if has_images and has_video:
raise RuntimeError("Element cannot contain both images and video. Use one media type per element.")
if not has_images and not has_video:
raise RuntimeError("Element requires either images or video.")
api_key = _load_api_key()
description_text = (description or "").strip()
if has_images:
image_batch = _validate_batch_image(images, "images")
image_count = image_batch.shape[0]
if image_count < ELEMENT_IMAGE_MIN or image_count > ELEMENT_IMAGE_MAX:
raise RuntimeError(
f"Element images must contain {ELEMENT_IMAGE_MIN}-{ELEMENT_IMAGE_MAX} images; got {image_count}."
)
image_urls: list[str] = []
_log(log, f"Uploading {image_count} element image(s) for '{element_name}'...")
for idx in range(image_count):
png_bytes = _image_tensor_to_png_bytes(image_batch[idx])
image_url = _upload_image(api_key, png_bytes)
image_urls.append(image_url)
_log(log, f"Element image {idx + 1} upload success: {_truncate_url(image_url)}")
payload: dict[str, Any] = {
"name": element_name,
"description": description_text,
"element_input_urls": image_urls,
}
return payload
video_bytes, source = _coerce_video_to_mp4_bytes(video)
_log(log, f"Uploading element video for '{element_name}' ({source})...")
video_url = _upload_video(api_key, video_bytes, filename=f"{element_name}.mp4")
_log(log, f"Element video upload success: {_truncate_url(video_url)}")
return {
"name": element_name,
"description": description_text,
"element_input_video_urls": [video_url],
}
def merge_kling3_elements(*elements: Any) -> list[dict[str, Any]]:
"""Merge up to the supported max element payloads."""
merged: list[dict[str, Any]] = []
seen_names: set[str] = set()
for item in elements:
if item is None:
continue
if not isinstance(item, dict):
raise RuntimeError("Element batch inputs must be KIE_ELEMENT payload objects.")
name = str(item.get("name") or "").strip()
if not name:
raise RuntimeError("Element payload missing name.")
if name in seen_names:
raise RuntimeError(f"Duplicate element name '{name}' in elements batch.")
seen_names.add(name)
merged.append(item)
if len(merged) > ELEMENT_BATCH_MAX:
raise RuntimeError(f"Element batch supports up to {ELEMENT_BATCH_MAX} elements.")
return merged
def _build_kling3_payload(
*,
mode: str,
aspect_ratio: str,
duration: str,
multi_shots: bool,
sound: bool,
prompt: str,
shots_text: str,
first_frame: torch.Tensor | None,
last_frame: torch.Tensor | None,
elements: list[dict[str, Any]] | None,
log: bool,
) -> tuple[dict[str, Any], str]:
"""Build Kling 3.0 createTask payload after validation/uploads.
Returns:
(payload, resolved_duration)
"""
if mode not in MODE_OPTIONS:
raise RuntimeError("Invalid mode. Use the pinned enum options.")
if aspect_ratio not in ASPECT_RATIO_OPTIONS:
raise RuntimeError("Invalid aspect_ratio. Use the pinned enum options.")
if duration not in DURATION_OPTIONS:
raise RuntimeError("Invalid duration. Use 3-15 seconds.")
if multi_shots and sound:
raise RuntimeError("sound is only supported in single-shot mode.")
api_key = _load_api_key()
frame_urls: list[str] = []
if first_frame is not None:
first_batch = _validate_batch_image(first_frame, "first_frame")
if first_batch.shape[0] > 1:
_log(log, f"More than 1 first_frame image provided ({first_batch.shape[0]}); using the first.")
_log(log, "Uploading first frame...")
first_url = _upload_image(api_key, _image_tensor_to_png_bytes(first_batch[0]))
frame_urls.append(first_url)
_log(log, f"First frame upload success: {_truncate_url(first_url)}")
if last_frame is not None:
if multi_shots:
raise RuntimeError("last_frame is not allowed when multi_shots is enabled.")
last_batch = _validate_batch_image(last_frame, "last_frame")
if last_batch.shape[0] > 1:
_log(log, f"More than 1 last_frame image provided ({last_batch.shape[0]}); using the first.")
_log(log, "Uploading last frame...")
last_url = _upload_image(api_key, _image_tensor_to_png_bytes(last_batch[0]))
frame_urls.append(last_url)
_log(log, f"Last frame upload success: {_truncate_url(last_url)}")
payload_input: dict[str, Any] = {
"mode": mode,
"multi_shots": multi_shots,
}
resolved_duration = duration
if not multi_shots:
_validate_prompt(prompt, max_length=PROMPT_MAX_LENGTH)
payload_input["prompt"] = prompt
payload_input["sound"] = sound
else:
multi_prompt = _parse_multi_prompt_text(shots_text)
total_duration = sum(int(item["duration"]) for item in multi_prompt)
if total_duration < 3 or total_duration > 15:
raise RuntimeError("Total multi-shot duration must be between 3 and 15 seconds.")
if int(duration) != total_duration:
_log(
log,
f"multi_shots enabled: overriding duration={duration} with computed total={total_duration}.",
)
resolved_duration = str(total_duration)
payload_input["multi_prompt"] = multi_prompt
payload_input["duration"] = resolved_duration
if frame_urls:
payload_input["image_urls"] = frame_urls
# aspect_ratio must be omitted when both start/end frames are used.
if len(frame_urls) < 2:
payload_input["aspect_ratio"] = aspect_ratio
if elements:
if len(elements) > ELEMENT_BATCH_MAX:
raise RuntimeError(f"At most {ELEMENT_BATCH_MAX} elements are supported in this node.")
payload_input["kling_elements"] = elements
available = {str(item.get("name") or "").strip() for item in elements}
referenced = _extract_referenced_elements(prompt)
if multi_shots:
for shot in payload_input.get("multi_prompt", []):
referenced |= _extract_referenced_elements(str(shot.get("prompt") or ""))
missing = sorted(ref for ref in referenced if ref not in available)
if missing:
raise RuntimeError(
"Prompt references unknown @elements: " + ", ".join(f"@{name}" for name in missing)
)
payload = {"model": MODEL_NAME, "input": payload_input}
return payload, resolved_duration
def preflight_kling3_payload(
*,
mode: str,
aspect_ratio: str,
duration: str,
multi_shots: bool,
sound: bool,
prompt: str,
shots_text: str,
first_frame: torch.Tensor | None,
last_frame: torch.Tensor | None,
elements: list[dict[str, Any]] | None,
log: bool,
) -> dict[str, Any]:
"""Build and return createTask payload only (no task submission)."""
payload, _resolved_duration = run_kling3_video_payload(
mode=mode,
aspect_ratio=aspect_ratio,
duration=duration,
multi_shots=multi_shots,
sound=sound,
prompt=prompt,
shots_text=shots_text,
first_frame=first_frame,
last_frame=last_frame,
elements=elements,
log=log,
)
return payload
def run_kling3_video_payload(
*,
mode: str,
aspect_ratio: str,
duration: str,
multi_shots: bool,
sound: bool,
prompt: str,
shots_text: str,
first_frame: torch.Tensor | None,
last_frame: torch.Tensor | None,
elements: list[dict[str, Any]] | None,
log: bool,
) -> tuple[dict[str, Any], str]:
"""Public payload builder used by preflight and generation."""
return _build_kling3_payload(
mode=mode,
aspect_ratio=aspect_ratio,
duration=duration,
multi_shots=multi_shots,
sound=sound,
prompt=prompt,
shots_text=shots_text,
first_frame=first_frame,
last_frame=last_frame,
elements=elements,
log=log,
)
def run_kling3_video(
*,
mode: str,
aspect_ratio: str,
duration: str,
multi_shots: bool,
sound: bool,
prompt: str,
shots_text: str,
first_frame: torch.Tensor | None,
last_frame: torch.Tensor | None,
elements: list[dict[str, Any]] | None,
poll_interval_s: float,
timeout_s: int,
log: bool,
) -> Any:
"""Run Kling 3.0 video generation."""
payload, _resolved_duration = run_kling3_video_payload(
mode=mode,
aspect_ratio=aspect_ratio,
duration=duration,
multi_shots=multi_shots,
sound=sound,
prompt=prompt,
shots_text=shots_text,
first_frame=first_frame,
last_frame=last_frame,
elements=elements,
log=log,
)
_log(log, "Creating Kling 3.0 video task...")
start_time = time.time()
task_id, create_response_text = _create_task(api_key, payload)
_log(log, f"createTask response (elapsed={time.time() - start_time:.1f}s): {create_response_text}")
_log(log, f"Task created with ID {task_id}. Polling for completion...")
record_data = _poll_task_until_complete(
api_key,
task_id,
poll_interval_s,
timeout_s,
log,
start_time,
)
result_urls = _extract_result_urls(record_data)
video_url = result_urls[0]
_log(log, f"Final video URL: {video_url}")
video_bytes = _download_video(video_url)
video_output = _video_bytes_to_comfy_video(video_bytes)
_log_remaining_credits(log, record_data, api_key, _log)
return video_output
+9 -2
View File
@@ -313,10 +313,17 @@ def run_suno_generate(
_log(log, f"Suno audio URL 2: {audio_url_2}")
try:
audio_bytes_1 = requests.get(audio_url_1, timeout=180).content
audio_bytes_2 = requests.get(audio_url_2, timeout=180).content
response_1 = requests.get(audio_url_1, timeout=180)
response_2 = requests.get(audio_url_2, timeout=180)
except requests.RequestException as exc:
raise RuntimeError(f"Failed to download audio: {exc}") from exc
if response_1.status_code != 200:
raise RuntimeError(f"Failed to download audio 1 (status code {response_1.status_code}).")
if response_2.status_code != 200:
raise RuntimeError(f"Failed to download audio 2 (status code {response_2.status_code}).")
audio_bytes_1 = response_1.content
audio_bytes_2 = response_2.content
audio_output_1 = _audio_bytes_to_comfy_audio(audio_bytes_1, "audio_1.mp3")
audio_output_2 = _audio_bytes_to_comfy_audio(audio_bytes_2, "audio_2.mp3")
+340
View File
@@ -1,3 +1,4 @@
import json
import os
import time
@@ -42,6 +43,15 @@ from .kie_api.kling26_t2v import (
DURATION_OPTIONS as KLING26_T2V_DURATION_OPTIONS,
run_kling26_t2v_video,
)
from .kie_api.kling3_video import (
ASPECT_RATIO_OPTIONS as KLING3_ASPECT_RATIO_OPTIONS,
DURATION_OPTIONS as KLING3_DURATION_OPTIONS,
MODE_OPTIONS as KLING3_MODE_OPTIONS,
build_kling3_element,
merge_kling3_elements,
preflight_kling3_payload,
run_kling3_video,
)
from .kie_api.suno_music import MODEL_OPTIONS as SUNO_MODEL_OPTIONS, run_suno_generate
from .kie_api.gemini3_pro_llm import (
MODEL_OPTIONS as GEMINI3_MODEL_OPTIONS,
@@ -629,6 +639,328 @@ Outputs:
time.sleep(backoff)
class KIE_KlingElements:
HELP = """
KIE Kling Elements
Build one named Kling element from either images (2-4) or one video.
Inputs:
- name: Element name used by @name in prompts
- description: Optional element description
- images: Optional image batch input (2-4 images)
- video: Optional video input (single clip)
- log: Console logging on/off
Rules:
- Provide exactly one media type: images or video
- Do not connect both images and video to the same element node
Outputs:
- KIE_ELEMENT: Element payload for Kling 3.0
- STRING: JSON preview of the element payload
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"name": ("STRING", {"default": "element_name"}),
},
"optional": {
"description": ("STRING", {"default": ""}),
"images": ("IMAGE",),
"video": ("VIDEO",),
"log": ("BOOLEAN", {"default": True}),
},
}
RETURN_TYPES = ("KIE_ELEMENT", "STRING")
RETURN_NAMES = ("element", "data")
FUNCTION = "build"
CATEGORY = "kie/helpers"
def build(
self,
name: str,
description: str = "",
images: torch.Tensor | None = None,
video: object | None = None,
log: bool = True,
):
element_payload = build_kling3_element(
name=name,
description=description,
images=images,
video=video,
log=log,
)
return (element_payload, json.dumps(element_payload, indent=2, ensure_ascii=False))
class KIE_KlingElementsBatch:
HELP = """
KIE Kling Elements Batch
Collect up to 9 KIE_ELEMENT payloads into a single KIE_ELEMENTS batch.
Inputs:
- element_1..element_9: Optional KIE_ELEMENT inputs
Outputs:
- KIE_ELEMENTS: Batched elements payload
- STRING: JSON preview of batched elements
"""
@classmethod
def INPUT_TYPES(cls):
return {
"optional": {
"element_1": ("KIE_ELEMENT",),
"element_2": ("KIE_ELEMENT",),
"element_3": ("KIE_ELEMENT",),
"element_4": ("KIE_ELEMENT",),
"element_5": ("KIE_ELEMENT",),
"element_6": ("KIE_ELEMENT",),
"element_7": ("KIE_ELEMENT",),
"element_8": ("KIE_ELEMENT",),
"element_9": ("KIE_ELEMENT",),
}
}
RETURN_TYPES = ("KIE_ELEMENTS", "STRING")
RETURN_NAMES = ("elements", "data")
FUNCTION = "batch"
CATEGORY = "kie/helpers"
def batch(
self,
element_1: dict | None = None,
element_2: dict | None = None,
element_3: dict | None = None,
element_4: dict | None = None,
element_5: dict | None = None,
element_6: dict | None = None,
element_7: dict | None = None,
element_8: dict | None = None,
element_9: dict | None = None,
):
elements = merge_kling3_elements(
element_1,
element_2,
element_3,
element_4,
element_5,
element_6,
element_7,
element_8,
element_9,
)
if not elements:
raise RuntimeError("Kling Elements Batch requires at least one element.")
return (elements, json.dumps(elements, indent=2, ensure_ascii=False))
class KIE_Kling3_Video:
HELP = """
KIE Kling 3.0 (Video)
Generate video with Kling 3.0 using single-shot or multi-shot prompts.
Inputs:
- mode: std or pro
- aspect_ratio: 1:1, 9:16, 16:9
- duration: 3..15 seconds
- multi_shots: enable multi-shot mode
- prompt: Used in single-shot mode
- shots_text: Used in multi-shot mode, format:
duration | prompt
or
label | duration | prompt
- first_frame: Optional start frame image
- last_frame: Optional end frame image (single-shot only)
- sound: Single-shot only
- element: Optional single KIE_ELEMENT
- elements: Optional KIE_ELEMENTS batch
- log: Console logging on/off
Rules:
- multi_shots=true: last_frame is invalid, sound is invalid
- multi_shots=true: duration is computed from summed shot durations
- If first_frame + last_frame are both used, aspect_ratio is omitted automatically
- @element references in prompt(s) must match provided element names
Outputs:
- VIDEO: ComfyUI video output compatible with SaveVideo
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"mode": ("COMBO", {"options": KLING3_MODE_OPTIONS, "default": "std"}),
"aspect_ratio": ("COMBO", {"options": KLING3_ASPECT_RATIO_OPTIONS, "default": "1:1"}),
"duration": ("COMBO", {"options": KLING3_DURATION_OPTIONS, "default": "5"}),
"multi_shots": ("BOOLEAN", {"default": False}),
"prompt": ("STRING", {"multiline": True, "default": ""}),
},
"optional": {
"shots_text": ("STRING", {"multiline": True, "default": ""}),
"first_frame": ("IMAGE",),
"last_frame": ("IMAGE",),
"sound": ("BOOLEAN", {"default": True}),
"element": ("KIE_ELEMENT",),
"elements": ("KIE_ELEMENTS",),
"log": ("BOOLEAN", {"default": True}),
},
}
RETURN_TYPES = ("VIDEO",)
RETURN_NAMES = ("video",)
FUNCTION = "generate"
CATEGORY = "kie/api"
def generate(
self,
mode: str,
aspect_ratio: str,
duration: str,
multi_shots: bool,
prompt: str,
shots_text: str = "",
first_frame: torch.Tensor | None = None,
last_frame: torch.Tensor | None = None,
sound: bool = True,
element: dict | None = None,
elements: list[dict] | None = None,
log: bool = True,
poll_interval_s: float = 10.0,
timeout_s: int = 1000,
):
merged_elements: list[dict] | None = None
if elements is not None:
if not isinstance(elements, list):
raise RuntimeError("elements input must be a KIE_ELEMENTS payload list.")
merged_elements = list(elements)
if element is not None:
if not isinstance(element, dict):
raise RuntimeError("element input must be a KIE_ELEMENT payload object.")
if merged_elements is None:
merged_elements = []
merged_elements.append(element)
if merged_elements is not None:
merged_elements = merge_kling3_elements(*merged_elements)
video_output = run_kling3_video(
mode=mode,
aspect_ratio=aspect_ratio,
duration=duration,
multi_shots=multi_shots,
sound=sound,
prompt=prompt,
shots_text=shots_text,
first_frame=first_frame,
last_frame=last_frame,
elements=merged_elements,
poll_interval_s=poll_interval_s,
timeout_s=timeout_s,
log=log,
)
return (video_output,)
class KIE_Kling3_Preflight:
HELP = """
KIE Kling 3.0 Preflight
Validate Kling 3.0 inputs, upload media, and build the exact createTask payload JSON
without submitting a generation task.
Use this node to verify wiring/format before spending generation credits.
Inputs:
- Same as KIE Kling 3.0 (Video)
Outputs:
- STRING: payload_json (exact payload that would be sent to createTask)
- STRING: notes
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"mode": ("COMBO", {"options": KLING3_MODE_OPTIONS, "default": "std"}),
"aspect_ratio": ("COMBO", {"options": KLING3_ASPECT_RATIO_OPTIONS, "default": "1:1"}),
"duration": ("COMBO", {"options": KLING3_DURATION_OPTIONS, "default": "5"}),
"multi_shots": ("BOOLEAN", {"default": False}),
"prompt": ("STRING", {"multiline": True, "default": ""}),
},
"optional": {
"shots_text": ("STRING", {"multiline": True, "default": ""}),
"first_frame": ("IMAGE",),
"last_frame": ("IMAGE",),
"sound": ("BOOLEAN", {"default": True}),
"element": ("KIE_ELEMENT",),
"elements": ("KIE_ELEMENTS",),
"log": ("BOOLEAN", {"default": True}),
},
}
RETURN_TYPES = ("STRING", "STRING")
RETURN_NAMES = ("payload_json", "notes")
FUNCTION = "preflight"
CATEGORY = "kie/helpers"
def preflight(
self,
mode: str,
aspect_ratio: str,
duration: str,
multi_shots: bool,
prompt: str,
shots_text: str = "",
first_frame: torch.Tensor | None = None,
last_frame: torch.Tensor | None = None,
sound: bool = True,
element: dict | None = None,
elements: list[dict] | None = None,
log: bool = True,
):
merged_elements: list[dict] | None = None
if elements is not None:
if not isinstance(elements, list):
raise RuntimeError("elements input must be a KIE_ELEMENTS payload list.")
merged_elements = list(elements)
if element is not None:
if not isinstance(element, dict):
raise RuntimeError("element input must be a KIE_ELEMENT payload object.")
if merged_elements is None:
merged_elements = []
merged_elements.append(element)
if merged_elements is not None:
merged_elements = merge_kling3_elements(*merged_elements)
payload = preflight_kling3_payload(
mode=mode,
aspect_ratio=aspect_ratio,
duration=duration,
multi_shots=multi_shots,
sound=sound,
prompt=prompt,
shots_text=shots_text,
first_frame=first_frame,
last_frame=last_frame,
elements=merged_elements,
log=log,
)
notes = (
"Preflight only: payload built after validation/uploads. "
"No createTask call was made."
)
return (json.dumps(payload, indent=2, ensure_ascii=False), notes)
class KIE_Flux2_I2I:
HELP = """
KIE Flux 2 (Image-to-Image)
@@ -1189,6 +1521,10 @@ NODE_CLASS_MAPPINGS = {
"KIE_Kling26_I2V": KIE_Kling26_I2V,
"KIE_Kling26_T2V": KIE_Kling26_T2V,
"KIE_Kling26Motion_I2V": KIE_Kling26Motion_I2V,
"KIE_KlingElements": KIE_KlingElements,
"KIE_KlingElementsBatch": KIE_KlingElementsBatch,
"KIE_Kling3_Video": KIE_Kling3_Video,
"KIE_Kling3_Preflight": KIE_Kling3_Preflight,
"KIE_Flux2_I2I": KIE_Flux2_I2I,
"KIE_Gemini3Pro_LLM": KIE_Gemini3Pro_LLM,
"KIE_Suno_Music_Basic": KIE_Suno_Music_Basic,
@@ -1208,6 +1544,10 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"KIE_Kling26_I2V": "KIE Kling 2.6 (I2V)",
"KIE_Kling26_T2V": "KIE Kling 2.6 (T2V)",
"KIE_Kling26Motion_I2V": "KIE Kling 2.6 Motion-Control (I2V)",
"KIE_KlingElements": "KIE Kling Elements",
"KIE_KlingElementsBatch": "KIE Kling Elements Batch",
"KIE_Kling3_Video": "KIE Kling 3.0 (Video)",
"KIE_Kling3_Preflight": "KIE Kling 3.0 Preflight",
"KIE_Flux2_I2I": "KIE Flux 2 (Image-to-Image)",
"KIE_Gemini3Pro_LLM": "KIE Gemini (LLM) [Experimental]",
"KIE_Suno_Music_Basic": "KIE Suno Music (Basic)",
+1 -6
View File
@@ -10,16 +10,11 @@ Generate a short video clip from a prompt and a required first image, with an op
- negative_prompt (STRING, multiline, optional): Optional negative prompt.
- duration (COMBO, seconds, default: 5): Video length. Options: 5, 10.
- cfg_scale (FLOAT, default: 0.5): Guidance scale from 0.0 to 1.0.
- poll_interval_s (FLOAT, seconds, default: 10.0): How often to poll job status.
- timeout_s (INT, seconds, default: 1000): Max wait time before timing out.
- log (BOOLEAN, default: true): Enable KIE console logs.
- retry_on_fail (BOOLEAN, default: true): Retry on transient API failures.
- max_retries (INT, default: 2): Number of retry attempts.
- retry_backoff_s (FLOAT, seconds, default: 3.0): Delay between retries.
## Outputs
- video (VIDEO): ComfyUI video output referencing a temporary .mp4 file.
## Troubleshooting
- If a job stalls or fails, check https://kie.ai/logs.
- Timeouts: Increase timeout_s or use duration 5 seconds.
- This node uses internal defaults for polling/retries/timeouts (not exposed in the UI).
+2 -7
View File
@@ -9,12 +9,7 @@ Generate a short video clip from a prompt, a single reference image, and a motio
- video (VIDEO, required): Motion reference video clip used for motion control.
- character_orientation (COMBO, default: video): Match character orientation to the image or the video. Options: image, video.
- mode (COMBO, default: 720p): Output resolution. Options: 720p, 1080p.
- poll_interval_s (FLOAT, seconds, default: 10.0): How often to poll job status.
- timeout_s (INT, seconds, default: 900): Max wait time before timing out.
- log (BOOLEAN, default: true): Enable KIE console logs.
- retry_on_fail (BOOLEAN, default: true): Retry on transient API failures.
- max_retries (INT, default: 2): Number of retry attempts.
- retry_backoff_s (FLOAT, seconds, default: 3.0): Delay between retries.
## Outputs
- video (VIDEO): ComfyUI VIDEO output referencing a temporary .mp4 file.
@@ -25,6 +20,6 @@ Character orientation: video
Mode: 720p
## Troubleshooting
- Timeouts: Increase timeout_s or use a shorter motion reference clip.
- Internal error / try later: Enable retries and increase retry_backoff_s.
- This node uses internal defaults for polling/retries/timeouts (not exposed in the UI).
- If a job takes unusually long or fails, check https://kie.ai/logs.
- Insufficient credits: Check remaining credits and top up your KIE account.
+3 -8
View File
@@ -8,15 +8,10 @@ Generate a short video clip from a single input image plus a text prompt using t
- images (IMAGE, required): Source image batch; the first image is used.
- duration (COMBO, seconds, default: 5): Video length. Options: 5, 10.
- sound (BOOLEAN, default: false): Include audio in the output video.
- poll_interval_s (FLOAT, seconds, default: 1.0): How often to poll job status.
- timeout_s (INT, seconds, default: 600): Max wait time before timing out.
- log (BOOLEAN, default: true): Enable KIE console logs.
- retry_on_fail (BOOLEAN, default: true): Retry on transient API failures.
- max_retries (INT, default: 2): Number of retry attempts.
- retry_backoff_s (FLOAT, seconds, default: 3.0): Delay between retries.
## Outputs
- video_path (STRING): Local path to the generated video file.
- video (VIDEO): ComfyUI VIDEO output compatible with SaveVideo.
## Examples
Prompt: "A small robot dances on a wooden desk under warm sunlight."
@@ -24,6 +19,6 @@ Duration: 5
Sound: false
## Troubleshooting
- Timeouts: Increase timeout_s or reduce duration to 5 seconds.
- Internal error / try later: Enable retries and increase retry_backoff_s.
- This node uses internal defaults for polling/retries/timeouts (not exposed in the UI).
- If a job takes unusually long or fails, check https://kie.ai/logs.
- Insufficient credits: Check remaining credits and top up your KIE account.
+21
View File
@@ -0,0 +1,21 @@
# KIE Kling 3.0 Preflight
Validate Kling 3.0 inputs and build the exact payload JSON without submitting a generation task.
> Status: Experimental support node for Kling 3.0 development.
## Purpose
- Catch wiring and formatting errors before expensive generation.
- Preview exactly what would be sent to:
- `POST https://api.kie.ai/api/v1/jobs/createTask`
## Inputs
- Same inputs as `KIE Kling 3.0 (Video)`.
## Outputs
- `payload_json` (STRING): exact request payload after validation and upload URL resolution.
- `notes` (STRING): confirms this node does not call `createTask`.
## Notes
- This node still uploads required input media to produce valid URLs in payload.
- In multi-shot mode, duration is auto-calculated from shot durations.
+37
View File
@@ -0,0 +1,37 @@
# KIE Kling 3.0 (Video)
Generate Kling 3.0 videos in single-shot or multi-shot mode.
> Status: Experimental (development pass). Not production-ready yet.
## Inputs
- `mode` (COMBO): `std` or `pro`
- `aspect_ratio` (COMBO): `1:1`, `9:16`, `16:9`
- `duration` (COMBO): `3` to `15`
- `multi_shots` (BOOLEAN): enables multi-shot mode
- `prompt` (STRING): single-shot prompt
- `shots_text` (STRING, optional): multi-shot lines:
- `duration | prompt`
- or `label | duration | prompt`
- `first_frame` (IMAGE, optional): start frame
- `last_frame` (IMAGE, optional): end frame (single-shot only)
- `sound` (BOOLEAN, optional): single-shot only
- `element` (KIE_ELEMENT, optional): one element
- `elements` (KIE_ELEMENTS, optional): batched elements
- `log` (BOOLEAN, optional)
## Rules
- `multi_shots=true`:
- `last_frame` is invalid
- `sound` is invalid
- shot durations are summed automatically and sent as the final payload duration
- If both start and end frames are provided, aspect ratio is auto-adapted from frames.
- `@element_name` references in prompt(s) must match provided elements.
- Multi-shot total duration must stay within `3..15` seconds.
## Cost note
- Kling 3.0 usage cost is tied to generated duration.
- In multi-shot mode, cost follows the computed total shot duration (max 15s).
## Output
- `VIDEO`: SaveVideo-compatible ComfyUI video output.
+20
View File
@@ -0,0 +1,20 @@
# KIE Kling Elements
Build one Kling element payload from images or video.
## Inputs
- `name` (STRING): element name used in prompts with `@name`
- `description` (STRING, optional)
- `images` (IMAGE, optional): image element source (2-4 images)
- `video` (VIDEO, optional): video element source (single clip)
- `log` (BOOLEAN, optional)
## Rules
- Provide exactly one media type:
- images only, or
- video only
- Using both images and video in one element raises an error.
## Outputs
- `KIE_ELEMENT`: single element payload
- `STRING`: JSON preview of the element payload
+14
View File
@@ -0,0 +1,14 @@
# KIE Kling Elements Batch
Batch up to 9 `KIE_ELEMENT` payloads into one `KIE_ELEMENTS` payload for the Kling 3.0 node.
## Inputs
- `element_1` to `element_9` (KIE_ELEMENT, optional)
## Rules
- At least one element is required.
- Duplicate element names are rejected.
## Outputs
- `KIE_ELEMENTS`: batched elements payload
- `STRING`: JSON preview of batched elements
+2 -7
View File
@@ -19,12 +19,6 @@ ByteDance’s rapid Seedance V1 Pro image-to-video model.
- **Duration**
Output length (5s or 10s).
- **Poll Interval**
How often to check the task status (seconds).
- **Timeout**
Maximum time to wait before failing (seconds).
- **Log**
Enable console logging for troubleshooting.
@@ -33,7 +27,7 @@ ByteDance’s rapid Seedance V1 Pro image-to-video model.
## Outputs
- **VIDEO**
Path to a temporary `.mp4` file saved to disk. Downstream nodes can consume this file immediately.
ComfyUI VIDEO output compatible with the SaveVideo node.
---
@@ -42,6 +36,7 @@ ByteDance’s rapid Seedance V1 Pro image-to-video model.
- The reference image is uploaded temporarily to the KIE API.
- Credits are consumed per request. Monitor remaining credits with the provided node.
- Video rendering can take a minute or more depending on resolution and duration.
- This node uses internal defaults for polling/retries/timeouts (not exposed in the UI).
## Debugging
Visit https://kie.ai/logs to monitor job progress or retrieve outputs if the node times out.
+3 -2
View File
@@ -18,8 +18,8 @@ Generate a short video clip from a text prompt using the Kling 2.6 text-to-video
- **duration**
Output length in seconds: 5 or 10.
- **poll_interval_s / timeout_s / log**
Advanced controls for polling and logging.
- **log**
Enable console logging.
---
@@ -41,3 +41,4 @@ Prompt:
- Aspect ratio and duration must match the allowed enums.
- Sound increases generation cost and may affect runtime.
- This node uses internal defaults for polling/retries/timeouts (not exposed in the UI).