feat: add kling 3 experimental nodes, preflight, and docs sync
This commit is contained in:
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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")
|
||||
|
||||
@@ -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)",
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
@@ -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).
|
||||
|
||||
Reference in New Issue
Block a user