From e1749f3cfa5618bb70c41de5a683b126dabf47e7 Mon Sep 17 00:00:00 2001 From: Dreaming Computers Date: Wed, 11 Feb 2026 09:29:54 +0700 Subject: [PATCH] feat: add kling 3 experimental nodes, preflight, and docs sync --- README.md | 12 + kie_api/grid.py | 15 +- kie_api/kling3_video.py | 381 +++++++++++++++++++++++++ kie_api/suno_music.py | 11 +- nodes.py | 340 ++++++++++++++++++++++ web/docs/KIE_Kling25_I2V_Pro.md | 7 +- web/docs/KIE_Kling26Motion_I2V.md | 9 +- web/docs/KIE_Kling26_I2V.md | 11 +- web/docs/KIE_Kling3_Preflight.md | 21 ++ web/docs/KIE_Kling3_Video.md | 37 +++ web/docs/KIE_Kling_Elements.md | 20 ++ web/docs/KIE_Kling_Elements_Batch.md | 14 + web/docs/KIE_SeedanceV1Pro_Fast_I2V.md | 9 +- web/docs/kie_kling26_t2v.md | 5 +- 14 files changed, 847 insertions(+), 45 deletions(-) create mode 100644 kie_api/kling3_video.py create mode 100644 web/docs/KIE_Kling3_Preflight.md create mode 100644 web/docs/KIE_Kling3_Video.md create mode 100644 web/docs/KIE_Kling_Elements.md create mode 100644 web/docs/KIE_Kling_Elements_Batch.md diff --git a/README.md b/README.md index edcf2af..018b9b1 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/kie_api/grid.py b/kie_api/grid.py index c70da37..be339bb 100644 --- a/kie_api/grid.py +++ b/kie_api/grid.py @@ -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) diff --git a/kie_api/kling3_video.py b/kie_api/kling3_video.py new file mode 100644 index 0000000..ed091aa --- /dev/null +++ b/kie_api/kling3_video.py @@ -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 diff --git a/kie_api/suno_music.py b/kie_api/suno_music.py index 82b7858..76ef8a1 100644 --- a/kie_api/suno_music.py +++ b/kie_api/suno_music.py @@ -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") diff --git a/nodes.py b/nodes.py index 617b016..43dd69e 100644 --- a/nodes.py +++ b/nodes.py @@ -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)", diff --git a/web/docs/KIE_Kling25_I2V_Pro.md b/web/docs/KIE_Kling25_I2V_Pro.md index 67b4563..0f05c00 100644 --- a/web/docs/KIE_Kling25_I2V_Pro.md +++ b/web/docs/KIE_Kling25_I2V_Pro.md @@ -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). diff --git a/web/docs/KIE_Kling26Motion_I2V.md b/web/docs/KIE_Kling26Motion_I2V.md index 9569370..80720e7 100644 --- a/web/docs/KIE_Kling26Motion_I2V.md +++ b/web/docs/KIE_Kling26Motion_I2V.md @@ -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. diff --git a/web/docs/KIE_Kling26_I2V.md b/web/docs/KIE_Kling26_I2V.md index a41105f..c332edc 100644 --- a/web/docs/KIE_Kling26_I2V.md +++ b/web/docs/KIE_Kling26_I2V.md @@ -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. diff --git a/web/docs/KIE_Kling3_Preflight.md b/web/docs/KIE_Kling3_Preflight.md new file mode 100644 index 0000000..663c730 --- /dev/null +++ b/web/docs/KIE_Kling3_Preflight.md @@ -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. diff --git a/web/docs/KIE_Kling3_Video.md b/web/docs/KIE_Kling3_Video.md new file mode 100644 index 0000000..d0c2a8e --- /dev/null +++ b/web/docs/KIE_Kling3_Video.md @@ -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. diff --git a/web/docs/KIE_Kling_Elements.md b/web/docs/KIE_Kling_Elements.md new file mode 100644 index 0000000..0406dab --- /dev/null +++ b/web/docs/KIE_Kling_Elements.md @@ -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 diff --git a/web/docs/KIE_Kling_Elements_Batch.md b/web/docs/KIE_Kling_Elements_Batch.md new file mode 100644 index 0000000..a9f17c8 --- /dev/null +++ b/web/docs/KIE_Kling_Elements_Batch.md @@ -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 diff --git a/web/docs/KIE_SeedanceV1Pro_Fast_I2V.md b/web/docs/KIE_SeedanceV1Pro_Fast_I2V.md index 725d1b1..0c8f050 100644 --- a/web/docs/KIE_SeedanceV1Pro_Fast_I2V.md +++ b/web/docs/KIE_SeedanceV1Pro_Fast_I2V.md @@ -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. diff --git a/web/docs/kie_kling26_t2v.md b/web/docs/kie_kling26_t2v.md index de11996..4f7d36b 100644 --- a/web/docs/kie_kling26_t2v.md +++ b/web/docs/kie_kling26_t2v.md @@ -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).