From a1cc46a046ff8cb2edb7aef0ab9c76f56c34698b Mon Sep 17 00:00:00 2001 From: Steve Tietze Date: Mon, 22 Dec 2025 12:32:03 +0700 Subject: [PATCH] added kling 2.6 image to video --- docs/KIE_API_KLING26_I2V_SPEC.md | 168 +++++++++++++++++++++++++++++++ kie_api/kling26_i2v.py | 134 ++++++++++++++++++++++++ nodes.py | 84 ++++++++++++++++ web/docs/KIE_Kling26_I2V.md | 29 ++++++ 4 files changed, 415 insertions(+) create mode 100644 docs/KIE_API_KLING26_I2V_SPEC.md create mode 100644 kie_api/kling26_i2v.py create mode 100644 web/docs/KIE_Kling26_I2V.md diff --git a/docs/KIE_API_KLING26_I2V_SPEC.md b/docs/KIE_API_KLING26_I2V_SPEC.md new file mode 100644 index 0000000..5db3918 --- /dev/null +++ b/docs/KIE_API_KLING26_I2V_SPEC.md @@ -0,0 +1,168 @@ +spec to create a new node for kling , this is a video mode... which is image to text. + +POST +/api/v1/jobs/createTask +Create Task +Create a new generation task + +Request Parameters +The API accepts a JSON payload with the following structure: + +Request Body Structure +{ + "model": "string", + "callBackUrl": "string (optional)", + "input": { + // Input parameters based on form configuration + } +} +Root Level Parameters +model +Required +string +The model name to use for generation + +Example: + +"kling-2.6/image-to-video" +callBackUrl +Optional +string +Callback URL for task completion notifications. Optional parameter. If provided, the system will send POST requests to this URL when the task completes (success or failure). If not provided, no callback notifications will be sent. + +Example: + +"https://your-domain.com/api/callback" +Input Object Parameters +The input object contains the following parameters based on the form configuration: + +input.prompt +Required +string +The text prompt used to generate the video + +Max length: 1000 characters +Example: + +"In a bright rehearsal room, sunlight streams through the window, and a standing microphone is placed in the center of the room. [Campus band female lead singer] stands in front of the microphone with her eyes closed, while the other members stand around her. [Campus band female lead singer, full voice] leads: \"I will try to fix you, with all my heart and soul...\" The background is an a cappella harmony, and the camera slowly circles around the band members." +input.image_urls +Required +array(URL) +The URL of the image used to generate video + +File URL after upload, not file content; Accepted types: image/jpeg, image/png, image/webp; Max size: 10.0MB +Example: + +["https://static.aiquickdraw.com/tools/example/1764851002741_i0lEiI8I.png"] +input.sound +Required +boolean +This parameter is used to specify whether the generated video contains sound + +Boolean value (true/false) +Example: + +false +input.duration +Required +string +Duration of the video in seconds + +Available options: + +5 +- +5 +10 +- +10 +Example: + +"5" +Request Example + +cURL + +JavaScript + +Python +import requests +import json + +url = "https://api.kie.ai/api/v1/jobs/createTask" +headers = { + "Content-Type": "application/json", + "Authorization": "Bearer YOUR_API_KEY" +} + +payload = { + "model": "kling-2.6/image-to-video", + "callBackUrl": "https://your-domain.com/api/callback", + "input": { + "prompt": "In a bright rehearsal room, sunlight streams through the window, and a standing microphone is placed in the center of the room. [Campus band female lead singer] stands in front of the microphone with her eyes closed, while the other members stand around her. [Campus band female lead singer, full voice] leads: \"I will try to fix you, with all my heart and soul...\" The background is an a cappella harmony, and the camera slowly circles around the band members.", + "image_urls": [ + "https://static.aiquickdraw.com/tools/example/1764851002741_i0lEiI8I.png" + ], + "sound": false, + "duration": "5" + } +} + +response = requests.post(url, headers=headers, data=json.dumps(payload)) +result = response.json() +print(result) +Response Example +{ + "code": 200, + "message": "success", + "data": { + "taskId": "task_12345678" + } +} +Response Fields +code +Status code, 200 for success, others for failure +message +Response message, error description when failed +data.taskId +Task ID for querying task status +Callback Notifications +When you provide the callBackUrl parameter when creating a task, the system will send POST requests to the specified URL upon task completion (success or failure). + +Success Callback Example +{ + "code": 200, + "data": { + "completeTime": 1755599644000, + "consumeCredits": 100, + "costTime": 8, + "createTime": 1755599634000, + "model": "kling-2.6/image-to-video", + "param": "{\"callBackUrl\":\"https://your-domain.com/api/callback\",\"model\":\"kling-2.6/image-to-video\",\"input\":{\"prompt\":\"In a bright rehearsal room, sunlight streams through the window, and a standing microphone is placed in the center of the room. [Campus band female lead singer] stands in front of the microphone with her eyes closed, while the other members stand around her. [Campus band female lead singer, full voice] leads: \\"I will try to fix you, with all my heart and soul...\\" The background is an a cappella harmony, and the camera slowly circles around the band members.\",\"image_urls\":[\"https://static.aiquickdraw.com/tools/example/1764851002741_i0lEiI8I.png\"],\"sound\":false,\"duration\":\"5\"}}", + "remainedCredits": 2510330, + "resultJson": "{\"resultUrls\":[\"https://example.com/generated-image.jpg\"]}", + "state": "success", + "taskId": "e989621f54392584b05867f87b160672", + "updateTime": 1755599644000 + }, + "msg": "Playground task completed successfully." +} +Failure Callback Example +{ + "code": 501, + "data": { + "completeTime": 1755597081000, + "consumeCredits": 0, + "costTime": 0, + "createTime": 1755596341000, + "failCode": "500", + "failMsg": "Internal server error", + "model": "kling-2.6/image-to-video", + "param": "{\"callBackUrl\":\"https://your-domain.com/api/callback\",\"model\":\"kling-2.6/image-to-video\",\"input\":{\"prompt\":\"In a bright rehearsal room, sunlight streams through the window, and a standing microphone is placed in the center of the room. [Campus band female lead singer] stands in front of the microphone with her eyes closed, while the other members stand around her. [Campus band female lead singer, full voice] leads: \\"I will try to fix you, with all my heart and soul...\\" The background is an a cappella harmony, and the camera slowly circles around the band members.\",\"image_urls\":[\"https://static.aiquickdraw.com/tools/example/1764851002741_i0lEiI8I.png\"],\"sound\":false,\"duration\":\"5\"}}", + "remainedCredits": 2510430, + "state": "fail", + "taskId": "bd3a37c523149e4adf45a3ddb5faf1a8", + "updateTime": 1755597097000 + }, + "msg": "Playground task failed." +} \ No newline at end of file diff --git a/kie_api/kling26_i2v.py b/kie_api/kling26_i2v.py new file mode 100644 index 0000000..903b7a6 --- /dev/null +++ b/kie_api/kling26_i2v.py @@ -0,0 +1,134 @@ +"""Kling 2.6 image-to-video helper.""" + +import json +import time +from typing import Any + +import torch + +from .auth import _load_api_key +from .credits import _log_remaining_credits +from .http import TransientKieError, requests +from .jobs import _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 +from .validation import _validate_prompt +from .video import _download_video, _write_video_to_temp_file + + +CREATE_TASK_URL = "https://api.kie.ai/api/v1/jobs/createTask" +MODEL_NAME = "kling-2.6/image-to-video" +PROMPT_MAX_LENGTH = 1000 +DURATION_OPTIONS = ["5", "10"] + + +def _validate_options(duration: str, sound: bool) -> None: + if duration not in DURATION_OPTIONS: + raise RuntimeError("Invalid duration. Use the pinned enum options.") + if not isinstance(sound, bool): + raise RuntimeError("sound must be a boolean value.") + + +def _validate_image_input(images: torch.Tensor | None) -> torch.Tensor: + if images is None: + raise RuntimeError("images input is required.") + if not isinstance(images, torch.Tensor): + raise RuntimeError("images input must be a tensor batch.") + if images.dim() != 4 or images.shape[-1] != 3: + raise RuntimeError("images input must have shape [B, H, W, 3].") + if images.shape[0] < 1: + raise RuntimeError("images input batch is empty.") + return images + + +def _create_kling_task(api_key: str, payload: dict[str, Any]) -> tuple[str, str]: + try: + response = requests.post( + CREATE_TASK_URL, + headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}, + json=payload, + timeout=30, + ) + except requests.RequestException as exc: + raise RuntimeError(f"Failed to call createTask endpoint: {exc}") from exc + + if response.status_code == 429 or response.status_code >= 500: + raise TransientKieError( + f"createTask returned HTTP {response.status_code}: {response.text}", status_code=response.status_code + ) + + try: + payload_json: Any = response.json() + except json.JSONDecodeError as exc: + raise RuntimeError("createTask endpoint did not return valid JSON.") from exc + + if payload_json.get("code") != 200: + message = payload_json.get("message") or payload_json.get("msg") + raise RuntimeError(f"createTask endpoint returned error code {payload_json.get('code')}: {message}") + + task_id = (payload_json.get("data") or {}).get("taskId") + if not task_id: + raise RuntimeError("createTask endpoint did not return a taskId.") + + return task_id, response.text + + +def run_kling26_i2v( + prompt: str, + images: torch.Tensor, + duration: str = "5", + sound: bool = False, + poll_interval_s: float = 1.0, + timeout_s: int = 600, + log: bool = True, +) -> str: + _validate_prompt(prompt, max_length=PROMPT_MAX_LENGTH) + _validate_options(duration, sound) + images = _validate_image_input(images) + + api_key = _load_api_key() + + if images.shape[0] > 1: + _log(log, f"More than 1 image provided ({images.shape[0]}); only the first will be used.") + + _log(log, "Uploading source image for Kling 2.6 I2V...") + png_bytes = _image_tensor_to_png_bytes(images[0]) + image_url = _upload_image(api_key, png_bytes) + _log(log, f"Image upload success: {_truncate_url(image_url)}") + + payload = { + "model": MODEL_NAME, + "input": { + "prompt": prompt, + "image_urls": [image_url], + "sound": sound, + "duration": duration, + }, + } + + _log(log, "Creating Kling 2.6 I2V task...") + start_time = time.time() + task_id, create_response_text = _create_kling_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"Downloading video result from {video_url}...") + + video_bytes = _download_video(video_url) + video_path = _write_video_to_temp_file(video_bytes) + _log(log, f"Video saved to {video_path}") + + _log_remaining_credits(log, record_data, api_key, _log) + return video_path diff --git a/nodes.py b/nodes.py index 8c9b41a..96d3f17 100644 --- a/nodes.py +++ b/nodes.py @@ -1,3 +1,5 @@ +import time + import torch from .kie_api.auth import _load_api_key @@ -20,7 +22,9 @@ from .kie_api.seedream45_edit import ( run_seedream45_edit, ) from .kie_api.seedancev1pro_fast_i2v import KIE_SeedanceV1Pro_Fast_I2V +from .kie_api.kling26_i2v import DURATION_OPTIONS as KLING26_DURATION_OPTIONS, run_kling26_i2v from .kie_api.grid import slice_grid_tensor +from .kie_api.http import TransientKieError class KIE_GetRemainingCredits: @@ -240,6 +244,84 @@ Outputs: return (image_tensor,) +class KIE_Kling26_I2V: + HELP = """ +KIE Kling 2.6 (Video) + +Generate a short video clip from a prompt and a single input image using Kling 2.6. + +Inputs: +- prompt: Text prompt (required) +- images: Source image batch (first image used) +- duration: 5s or 10s +- sound: Include audio in the output video +- poll_interval_s / timeout_s / log +- retry_on_fail / max_retries / retry_backoff_s + +Outputs: +- STRING: Path to a temporary video file +""" + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "prompt": ("STRING", {"multiline": True}), + "images": ("IMAGE",), + }, + "optional": { + "duration": ("COMBO", {"options": KLING26_DURATION_OPTIONS, "default": "5"}), + "sound": ("BOOLEAN", {"default": False}), + "log": ("BOOLEAN", {"default": True}), + "poll_interval_s": ("FLOAT", {"default": 1.0, "min": 0.5, "max": 60.0, "step": 0.5}), + "timeout_s": ("INT", {"default": 600, "min": 1, "max": 3600, "step": 1}), + "retry_on_fail": ("BOOLEAN", {"default": True}), + "max_retries": ("INT", {"default": 2, "min": 0, "max": 10, "step": 1}), + "retry_backoff_s": ("FLOAT", {"default": 3.0, "min": 0.0, "max": 300.0, "step": 0.5}), + }, + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("video_path",) + FUNCTION = "generate" + CATEGORY = "kie/api" + + def generate( + self, + prompt: str, + images: torch.Tensor, + duration: str = "5", + sound: bool = False, + log: bool = True, + poll_interval_s: float = 1.0, + timeout_s: int = 600, + retry_on_fail: bool = True, + max_retries: int = 2, + retry_backoff_s: float = 3.0, + ): + attempts = max_retries + 1 if retry_on_fail else 1 + attempts = max(attempts, 1) + backoff = retry_backoff_s if retry_backoff_s >= 0 else 0.0 + + for attempt in range(1, attempts + 1): + try: + video_path = run_kling26_i2v( + prompt=prompt, + images=images, + duration=duration, + sound=sound, + poll_interval_s=poll_interval_s, + timeout_s=timeout_s, + log=log, + ) + return (video_path,) + except TransientKieError: + if not retry_on_fail or attempt >= attempts: + raise + _log(log, f"Retrying (attempt {attempt + 1}/{attempts}) after {backoff}s") + time.sleep(backoff) + + class KIE_GridSlice: HELP = """ KIE Grid Slice @@ -308,6 +390,7 @@ NODE_CLASS_MAPPINGS = { "KIE_Seedream45_TextToImage": KIE_Seedream45_TextToImage, "KIE_Seedream45_Edit": KIE_Seedream45_Edit, "KIE_SeedanceV1Pro_Fast_I2V": KIE_SeedanceV1Pro_Fast_I2V, + "KIE_Kling26_I2V": KIE_Kling26_I2V, "KIE_GridSlice": KIE_GridSlice, } NODE_DISPLAY_NAME_MAPPINGS = { @@ -316,5 +399,6 @@ NODE_DISPLAY_NAME_MAPPINGS = { "KIE_Seedream45_TextToImage": "KIE Seedream 4.5 Text-To-Image", "KIE_Seedream45_Edit": "KIE Seedream 4.5 Edit", "KIE_SeedanceV1Pro_Fast_I2V": "KIE Seedance V1 Pro Fast (I2V)", + "KIE_Kling26_I2V": "KIE Kling 2.6 (Video)", "KIE_GridSlice": "KIE Grid Slice", } diff --git a/web/docs/KIE_Kling26_I2V.md b/web/docs/KIE_Kling26_I2V.md new file mode 100644 index 0000000..a41105f --- /dev/null +++ b/web/docs/KIE_Kling26_I2V.md @@ -0,0 +1,29 @@ +# KIE Kling 2.6 (Video) + +## Overview +Generate a short video clip from a single input image plus a text prompt using the Kling 2.6 image-to-video model. + +## Inputs +- prompt (STRING, multiline, required): The text prompt for the video. +- 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. + +## Examples +Prompt: "A small robot dances on a wooden desk under warm sunlight." +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. +- Insufficient credits: Check remaining credits and top up your KIE account.