adding kling 2.5 i2v
This commit is contained in:
@@ -42,6 +42,10 @@ This node pack currently includes the following nodes:
|
||||
- Generates video from a single input image.
|
||||
- Uses the Kling 2.6 image-to-video model.
|
||||
|
||||
- **Kling 2.5 I2V Pro**
|
||||
- Generates video from a required first image and optional tail image.
|
||||
- Uses the Kling 2.5 Turbo image-to-video Pro model.
|
||||
|
||||
- **Kling 2.6 Text-to-Video**
|
||||
- Generates video directly from a text prompt.
|
||||
- Supports aspect ratio, duration, and sound options as exposed by the API.
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
"""Kling 2.5 Turbo Image-to-Video Pro helper."""
|
||||
|
||||
import json
|
||||
import time
|
||||
from typing import Any, Callable
|
||||
|
||||
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_bytes_to_comfy_video
|
||||
|
||||
|
||||
CREATE_TASK_URL = "https://api.kie.ai/api/v1/jobs/createTask"
|
||||
MODEL_NAME = "kling/v2-5-turbo-image-to-video-pro"
|
||||
PROMPT_MAX_LENGTH = 1000
|
||||
DURATION_OPTIONS = ["5", "10"]
|
||||
|
||||
|
||||
def _validate_options(duration: str, cfg_scale: float) -> None:
|
||||
if duration not in DURATION_OPTIONS:
|
||||
raise RuntimeError("Invalid duration. Use the pinned enum options.")
|
||||
if not isinstance(cfg_scale, (int, float)):
|
||||
raise RuntimeError("cfg_scale must be a float value.")
|
||||
if cfg_scale < 0.0 or cfg_scale > 1.0:
|
||||
raise RuntimeError("cfg_scale must be between 0.0 and 1.0.")
|
||||
|
||||
|
||||
def _validate_image_input(images: torch.Tensor | None, *, label: str, required: bool) -> torch.Tensor | None:
|
||||
if images is None:
|
||||
if required:
|
||||
raise RuntimeError(f"{label} input is required.")
|
||||
return None
|
||||
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 _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_kling25_i2v_job(
|
||||
image: torch.Tensor,
|
||||
tail_image: torch.Tensor | None,
|
||||
prompt: str,
|
||||
negative_prompt: str = "",
|
||||
duration: str = "5",
|
||||
cfg_scale: float = 0.5,
|
||||
timeout_seconds: int | None = None,
|
||||
log: Callable[[str], None] | None = None,
|
||||
) -> dict:
|
||||
_validate_prompt(prompt, max_length=PROMPT_MAX_LENGTH)
|
||||
_validate_options(duration, cfg_scale)
|
||||
images = _validate_image_input(image, label="image", required=True)
|
||||
tail_images = _validate_image_input(tail_image, label="tail_image", required=False)
|
||||
|
||||
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.")
|
||||
if tail_images is not None and tail_images.shape[0] > 1:
|
||||
_log(log, f"More than 1 tail image provided ({tail_images.shape[0]}); only the first will be used.")
|
||||
|
||||
_log(log, "Uploading source image for Kling 2.5 I2V Pro...")
|
||||
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)}")
|
||||
|
||||
tail_image_url = None
|
||||
if tail_images is not None:
|
||||
_log(log, "Uploading tail image for Kling 2.5 I2V Pro...")
|
||||
tail_png_bytes = _image_tensor_to_png_bytes(tail_images[0])
|
||||
tail_image_url = _upload_image(api_key, tail_png_bytes)
|
||||
_log(log, f"Tail image upload success: {_truncate_url(tail_image_url)}")
|
||||
|
||||
payload_input: dict[str, Any] = {
|
||||
"prompt": prompt,
|
||||
"image_url": image_url,
|
||||
"duration": duration,
|
||||
"cfg_scale": cfg_scale,
|
||||
}
|
||||
if tail_image_url:
|
||||
payload_input["tail_image_url"] = tail_image_url
|
||||
if negative_prompt.strip():
|
||||
payload_input["negative_prompt"] = negative_prompt
|
||||
|
||||
payload = {"model": MODEL_NAME, "input": payload_input}
|
||||
|
||||
_log(log, "Creating Kling 2.5 I2V Pro 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...")
|
||||
_log(log, "Check https://kie.ai/logs for request status if needed.")
|
||||
|
||||
effective_timeout = 1000 if timeout_seconds is None else timeout_seconds
|
||||
record_data = _poll_task_until_complete(
|
||||
api_key,
|
||||
task_id,
|
||||
10.0,
|
||||
effective_timeout,
|
||||
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
|
||||
@@ -27,6 +27,10 @@ from .kie_api.kling26_i2v import (
|
||||
DURATION_OPTIONS as KLING26_DURATION_OPTIONS,
|
||||
run_kling26_i2v_video,
|
||||
)
|
||||
from .kie_api.kling25_i2v import (
|
||||
DURATION_OPTIONS as KLING25_DURATION_OPTIONS,
|
||||
run_kling25_i2v_job,
|
||||
)
|
||||
from .kie_api.kling26motion_i2v import (
|
||||
CHARACTER_ORIENTATION_OPTIONS as KLING26MOTION_CHARACTER_ORIENTATION_OPTIONS,
|
||||
MODE_OPTIONS as KLING26MOTION_MODE_OPTIONS,
|
||||
@@ -250,6 +254,86 @@ Outputs:
|
||||
return (image_tensor,)
|
||||
|
||||
|
||||
class KIE_Kling25_I2V_Pro:
|
||||
HELP = """
|
||||
KIE Kling 2.5 I2V Pro
|
||||
|
||||
Generate a short video clip from a prompt and one or two input images using Kling 2.5 Turbo I2V Pro.
|
||||
|
||||
Inputs:
|
||||
- prompt: Text prompt (required)
|
||||
- first_frame: Source image batch (first image used)
|
||||
- last_frame: Optional tail image batch (first image used)
|
||||
- negative_prompt: Optional negative prompt
|
||||
- duration: 5s or 10s
|
||||
- cfg_scale: 0.0 to 1.0
|
||||
- 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": {
|
||||
"first_frame": ("IMAGE",),
|
||||
"prompt": ("STRING", {"multiline": True, "default": ""}),
|
||||
},
|
||||
"optional": {
|
||||
"last_frame": ("IMAGE",),
|
||||
"negative_prompt": ("STRING", {"multiline": True, "default": ""}),
|
||||
"duration": ("COMBO", {"options": KLING25_DURATION_OPTIONS, "default": "5"}),
|
||||
"cfg_scale": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.1}),
|
||||
"log": ("BOOLEAN", {"default": True}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("VIDEO",)
|
||||
RETURN_NAMES = ("video",)
|
||||
FUNCTION = "generate"
|
||||
CATEGORY = "kie/api"
|
||||
|
||||
def generate(
|
||||
self,
|
||||
first_frame: torch.Tensor,
|
||||
prompt: str,
|
||||
last_frame: torch.Tensor | None = None,
|
||||
negative_prompt: str = "",
|
||||
duration: str = "5",
|
||||
cfg_scale: float = 0.5,
|
||||
log: bool = True,
|
||||
poll_interval_s: float = 10.0,
|
||||
timeout_s: int = 1000,
|
||||
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_kling25_i2v_job(
|
||||
image=first_frame,
|
||||
tail_image=last_frame,
|
||||
prompt=prompt,
|
||||
negative_prompt=negative_prompt,
|
||||
duration=duration,
|
||||
cfg_scale=cfg_scale,
|
||||
timeout_seconds=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_Kling26_I2V:
|
||||
HELP = """
|
||||
KIE Kling 2.6 (Video)
|
||||
@@ -641,6 +725,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"KIE_Seedream45_Edit": KIE_Seedream45_Edit,
|
||||
"KIE_SeedanceV1Pro_Fast_I2V": KIE_SeedanceV1Pro_Fast_I2V,
|
||||
"KIE_Seedance15Pro_I2V": KIE_Seedance15Pro_I2V,
|
||||
"KIE_Kling25_I2V_Pro": KIE_Kling25_I2V_Pro,
|
||||
"KIE_Kling26_I2V": KIE_Kling26_I2V,
|
||||
"KIE_Kling26_T2V": KIE_Kling26_T2V,
|
||||
"KIE_Kling26Motion_I2V": KIE_Kling26Motion_I2V,
|
||||
@@ -654,6 +739,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"KIE_Seedream45_Edit": "KIE Seedream 4.5 Edit",
|
||||
"KIE_SeedanceV1Pro_Fast_I2V": "KIE Seedance V1 Pro Fast (I2V)",
|
||||
"KIE_Seedance15Pro_I2V": "KIE Seedance 1.5 Pro (I2V/T2V)",
|
||||
"KIE_Kling25_I2V_Pro": "KIE Kling 2.5 I2V Pro",
|
||||
"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)",
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# KIE Kling 2.5 I2V Pro
|
||||
|
||||
## Overview
|
||||
Generate a short video clip from a prompt and a required first image, with an optional tail image, using the Kling 2.5 Turbo I2V Pro model.
|
||||
|
||||
## Inputs
|
||||
- first_frame (IMAGE, required): Source image batch; the first image is used.
|
||||
- prompt (STRING, multiline, required): The text prompt for the video.
|
||||
- last_frame (IMAGE, optional): Optional tail frame; the first image is used if provided.
|
||||
- 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.
|
||||
Reference in New Issue
Block a user