From de946d248b3f03ee2756c8d1f5f04f405d229f0b Mon Sep 17 00:00:00 2001 From: Steve Tietze Date: Thu, 8 Jan 2026 09:59:02 +0700 Subject: [PATCH] adding kling motion 2.6 --- docs/KIE_AI_KLING26MOTION_I2V_SPEC.md | 196 +++++++++++++++++++++++++ kie_api/kling26motion_i2v.py | 201 ++++++++++++++++++++++++++ nodes.py | 87 +++++++++++ web/docs/KIE_Kling26Motion_I2V.md | 30 ++++ 4 files changed, 514 insertions(+) create mode 100644 docs/KIE_AI_KLING26MOTION_I2V_SPEC.md create mode 100644 kie_api/kling26motion_i2v.py create mode 100644 web/docs/KIE_Kling26Motion_I2V.md diff --git a/docs/KIE_AI_KLING26MOTION_I2V_SPEC.md b/docs/KIE_AI_KLING26MOTION_I2V_SPEC.md new file mode 100644 index 0000000..28b57bd --- /dev/null +++ b/docs/KIE_AI_KLING26MOTION_I2V_SPEC.md @@ -0,0 +1,196 @@ +Kling 26 Motion I2V + +inputs +- Ref Image (Single Image) +- Motion video (Single mp4) + +These files are uploaded to Kie AI similar to how we upload the images in kling26_i2v.py how its done in their. + +KIE API Documents: + +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/motion-control" +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 +A text description of the desired output. Maximum length is 2500 characters. + +Max length: 2500 characters +Example: + +"The cartoon character is dancing." +input.input_urls +Required +array(URL) +An array containing a single image URL. The photo must clearly show the subject's head, shoulders, and torso. + +Please provide the URL of the uploaded file; Accepted types: image/jpeg, image/png, image/webp; Max size: 10.0MB +Example: + +["https://static.aiquickdraw.com/tools/example/1767694885407_pObJoMcy.png"] +input.video_urls +Required +array(URL) +An array containing a single video URL. The duration must be between 3 to 30 seconds, and the video must clearly show the subject's head, shoulders, and torso.The minimum width and height for videos must be 720 pixels, and only jpeg/jpg/png image formats are supported. + +Please provide the URL of the uploaded file; Accepted types: video/mp4, video/quicktime, video/x-matroska; Max size: 100.0MB +Example: + +["https://static.aiquickdraw.com/tools/example/1767525918769_QyvTNib2.mp4"] +input.character_orientation +Required +string +Generate the orientation of the characters in the video. 'image': same orientation as the person in the picture (max 10s video). 'video': consistent with the orientation of the characters in the video (max 30s video). + +Available options: + +image +- +Image +video +- +Video +Example: + +"video" +input.mode +Required +string +Output resolution mode. Use 'std' for 720p or 'pro' for 1080p. + +Available options: + +720p +- +720p +1080p +- +1080p +Example: + +"720p" +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/motion-control", + "callBackUrl": "https://your-domain.com/api/callback", + "input": { + "prompt": "The cartoon character is dancing.", + "input_urls": [ + "https://static.aiquickdraw.com/tools/example/1767694885407_pObJoMcy.png" + ], + "video_urls": [ + "https://static.aiquickdraw.com/tools/example/1767525918769_QyvTNib2.mp4" + ], + "character_orientation": "video", + "mode": "720p" + } +} + +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, + "costTime": 8, + "createTime": 1755599634000, + "model": "kling-2.6/motion-control", + "param": "{\"callBackUrl\":\"https://your-domain.com/api/callback\",\"model\":\"kling-2.6/motion-control\",\"input\":{\"prompt\":\"The cartoon character is dancing.\",\"input_urls\":[\"https://static.aiquickdraw.com/tools/example/1767694885407_pObJoMcy.png\"],\"video_urls\":[\"https://static.aiquickdraw.com/tools/example/1767525918769_QyvTNib2.mp4\"],\"character_orientation\":\"video\",\"mode\":\"720p\"}}", + "resultJson": "{\"resultUrls\":[\"https://example.com/generated-image.jpg\"]}", + "state": "success", + "taskId": "e989621f54392584b05867f87b160672", + "failCode": null, + "failMsg": null, + }, + "msg": "Playground task completed successfully." +} +Failure Callback Example +{ + "code": 501, + "data": { + "completeTime": 1755597081000, + "costTime": 0, + "createTime": 1755596341000, + "failCode": "500", + "failMsg": "Internal server error", + "model": "kling-2.6/motion-control", + "param": "{\"callBackUrl\":\"https://your-domain.com/api/callback\",\"model\":\"kling-2.6/motion-control\",\"input\":{\"prompt\":\"The cartoon character is dancing.\",\"input_urls\":[\"https://static.aiquickdraw.com/tools/example/1767694885407_pObJoMcy.png\"],\"video_urls\":[\"https://static.aiquickdraw.com/tools/example/1767525918769_QyvTNib2.mp4\"],\"character_orientation\":\"video\",\"mode\":\"720p\"}}", + "state": "fail", + "taskId": "bd3a37c523149e4adf45a3ddb5faf1a8", + "resultJson": null, + }, + "msg": "Playground task failed." +} +Important Notes +The callback content structure is identical to the Query Task API response +The param field contains the complete Create Task request parameters, not just the input section +If callBackUrl is not provided, no callback notifications will be sent \ No newline at end of file diff --git a/kie_api/kling26motion_i2v.py b/kie_api/kling26motion_i2v.py new file mode 100644 index 0000000..6804b82 --- /dev/null +++ b/kie_api/kling26motion_i2v.py @@ -0,0 +1,201 @@ +"""Kling 2.6 motion-control image-to-video helper.""" + +import json +import time +from pathlib import Path +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, _video_path_to_comfy_video_output, _write_video_to_temp_file + + +CREATE_TASK_URL = "https://api.kie.ai/api/v1/jobs/createTask" +MODEL_NAME = "kling-2.6/motion-control" +PROMPT_MAX_LENGTH = 2500 +CHARACTER_ORIENTATION_OPTIONS = ["image", "video"] +MODE_OPTIONS = ["720p", "1080p"] + + +def _validate_options(character_orientation: str, mode: str) -> None: + if character_orientation not in CHARACTER_ORIENTATION_OPTIONS: + raise RuntimeError("Invalid character_orientation. Use the pinned enum options.") + if mode not in MODE_OPTIONS: + raise RuntimeError("Invalid mode. Use the pinned enum options.") + + +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 _validate_video_input(video: Any) -> Any: + if video is None: + raise RuntimeError("video input is required.") + return video + + +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_kling26motion_i2v_video( + prompt: str, + images: torch.Tensor, + video: Any, + character_orientation: str = "video", + mode: str = "720p", + poll_interval_s: float = 10.0, + timeout_s: int = 900, + log: bool = True, +) -> Any: + # Validate prompt length and required inputs. + _validate_prompt(prompt, max_length=PROMPT_MAX_LENGTH) + _validate_options(character_orientation, mode) + images = _validate_image_input(images) + video = _validate_video_input(video) + + # Load the API key once for uploads and task creation. + 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.") + + # Upload the reference image using the shared upload helper. + _log(log, "Uploading reference image for Kling 2.6 Motion 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)}") + + # Resolve the input video into bytes so it can be uploaded. + video_bytes: bytes + if isinstance(video, (bytes, bytearray)): + video_bytes = bytes(video) + elif isinstance(video, str): + try: + video_bytes = Path(video).read_bytes() + except OSError as exc: + raise RuntimeError(f"Failed to read video file: {exc}") from exc + elif isinstance(video, dict): + video_path = video.get("path") or video.get("filename") + if not video_path: + raise RuntimeError("video input dict must include a 'path' or 'filename'.") + try: + video_bytes = Path(video_path).read_bytes() + except OSError as exc: + raise RuntimeError(f"Failed to read video file: {exc}") from exc + else: + raise RuntimeError("video input must be bytes, a file path string, or a dict with a path.") + + # Upload the motion reference video using the shared upload helper. + _log(log, "Uploading motion reference video for Kling 2.6 Motion I2V...") + video_url = _upload_image(api_key, video_bytes) + _log(log, f"Video upload success: {_truncate_url(video_url)}") + + # Build the createTask payload exactly as required by the KIE API spec. + payload = { + "model": MODEL_NAME, + "input": { + "prompt": prompt, + "input_urls": [image_url], + "video_urls": [video_url], + "character_orientation": character_orientation, + "mode": mode, + }, + } + + # Create the task and log the raw response for troubleshooting. + _log(log, "Creating Kling 2.6 Motion 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...") + + # Poll until the job finishes or times out. + record_data = _poll_task_until_complete( + api_key, + task_id, + poll_interval_s, + timeout_s, + log, + start_time, + ) + + # Extract the final video URL from the result payload. + result_urls = _extract_result_urls(record_data) + result_video_url = result_urls[0] + _log(log, f"Final video URL: {result_video_url}") + + # Download the video bytes, write to a temp file, and return a VIDEO-compatible object. + result_video_bytes = _download_video(result_video_url) + video_path = _write_video_to_temp_file(result_video_bytes) + video_output = _video_path_to_comfy_video_output(video_path) + + _log_remaining_credits(log, record_data, api_key, _log) + return video_output + + +def run_kling26motion_i2v( + prompt: str, + images: torch.Tensor, + video: Any, + character_orientation: str = "video", + mode: str = "720p", + poll_interval_s: float = 10.0, + timeout_s: int = 900, + log: bool = True, +) -> Any: + """Backward-compatible alias for existing imports.""" + return run_kling26motion_i2v_video( + prompt=prompt, + images=images, + video=video, + character_orientation=character_orientation, + mode=mode, + poll_interval_s=poll_interval_s, + timeout_s=timeout_s, + log=log, + ) diff --git a/nodes.py b/nodes.py index 1436e4b..cb31f0d 100644 --- a/nodes.py +++ b/nodes.py @@ -27,6 +27,11 @@ from .kie_api.kling26_i2v import ( DURATION_OPTIONS as KLING26_DURATION_OPTIONS, run_kling26_i2v_video, ) +from .kie_api.kling26motion_i2v import ( + CHARACTER_ORIENTATION_OPTIONS as KLING26MOTION_CHARACTER_ORIENTATION_OPTIONS, + MODE_OPTIONS as KLING26MOTION_MODE_OPTIONS, + run_kling26motion_i2v_video, +) from .kie_api.grid import slice_grid_tensor from .kie_api.http import TransientKieError @@ -312,6 +317,86 @@ Outputs: time.sleep(backoff) +class KIE_Kling26Motion_I2V: + HELP = """ +KIE Kling 2.6 Motion-Control (I2V) + +Generate a short video clip from a prompt, a reference image, and a motion reference video. + +Inputs: +- prompt: Text prompt (required) +- images: Source image batch (first image used) +- video: Motion reference video input (single clip) +- character_orientation: Match character orientation to image or video +- mode: 720p or 1080p output resolution +- poll_interval_s / timeout_s / log +- retry_on_fail / max_retries / retry_backoff_s + +Outputs: +- VIDEO: ComfyUI video output referencing a temporary .mp4 file +""" + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "prompt": ("STRING", {"multiline": True}), + "images": ("IMAGE",), + "video": ("VIDEO",), + }, + "optional": { + "character_orientation": ( + "COMBO", + {"options": KLING26MOTION_CHARACTER_ORIENTATION_OPTIONS, "default": "video"}, + ), + "mode": ("COMBO", {"options": KLING26MOTION_MODE_OPTIONS, "default": "720p"}), + "log": ("BOOLEAN", {"default": True}), + }, + } + + RETURN_TYPES = ("VIDEO",) + RETURN_NAMES = ("video",) + FUNCTION = "generate" + CATEGORY = "kie/api" + + def generate( + self, + prompt: str, + images: torch.Tensor, + video: object, + character_orientation: str = "video", + mode: str = "720p", + log: bool = True, + poll_interval_s: float = 10.0, + timeout_s: int = 900, + 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_output = run_kling26motion_i2v_video( + prompt=prompt, + images=images, + video=video, + character_orientation=character_orientation, + mode=mode, + poll_interval_s=poll_interval_s, + timeout_s=timeout_s, + log=log, + ) + return (video_output,) + 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 @@ -382,6 +467,7 @@ NODE_CLASS_MAPPINGS = { "KIE_SeedanceV1Pro_Fast_I2V": KIE_SeedanceV1Pro_Fast_I2V, "KIE_Seedance15Pro_I2V": KIE_Seedance15Pro_I2V, "KIE_Kling26_I2V": KIE_Kling26_I2V, + "KIE_Kling26Motion_I2V": KIE_Kling26Motion_I2V, "KIE_GridSlice": KIE_GridSlice, } NODE_DISPLAY_NAME_MAPPINGS = { @@ -392,5 +478,6 @@ NODE_DISPLAY_NAME_MAPPINGS = { "KIE_SeedanceV1Pro_Fast_I2V": "KIE Seedance V1 Pro Fast (I2V)", "KIE_Seedance15Pro_I2V": "KIE Seedance 1.5 Pro (I2V/T2V)", "KIE_Kling26_I2V": "KIE Kling 2.6 (I2V/T2V)", + "KIE_Kling26Motion_I2V": "KIE Kling 2.6 Motion-Control (I2V)", "KIE_GridSlice": "KIE Grid Slice", } diff --git a/web/docs/KIE_Kling26Motion_I2V.md b/web/docs/KIE_Kling26Motion_I2V.md new file mode 100644 index 0000000..9569370 --- /dev/null +++ b/web/docs/KIE_Kling26Motion_I2V.md @@ -0,0 +1,30 @@ +# KIE Kling 2.6 Motion-Control (I2V) + +## Overview +Generate a short video clip from a prompt, a single reference image, and a motion reference video using the Kling 2.6 motion-control model. + +## Inputs +- prompt (STRING, multiline, required): The text prompt for the video. +- images (IMAGE, required): Source image batch; the first image is used. +- 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. + +## Examples +Prompt: "The character dances in sync with the reference motion video." +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. +- Insufficient credits: Check remaining credits and top up your KIE account.