[Partner Nodes] chore(Kling): remove retired legacy models and Virtual Try-On API (#15249)
Signed-off-by: bigcat88 <bigcat88@icloud.com>
This commit is contained in:
parent
9a9fdb10ed
commit
c44dea1880
|
|
@ -5,17 +5,13 @@ For source of truth on the allowed permutations of request fields, please refere
|
|||
"""
|
||||
|
||||
import logging
|
||||
import math
|
||||
import re
|
||||
|
||||
import torch
|
||||
from typing_extensions import override
|
||||
|
||||
from comfy_api.latest import IO, ComfyExtension, Input, InputImpl
|
||||
from comfy_api.latest import IO, ComfyExtension, Input
|
||||
from comfy_api_nodes.apis import (
|
||||
KlingCameraControl,
|
||||
KlingCameraConfig,
|
||||
KlingCameraControlType,
|
||||
KlingVideoGenDuration,
|
||||
KlingVideoGenMode,
|
||||
KlingVideoGenAspectRatio,
|
||||
|
|
@ -30,23 +26,12 @@ from comfy_api_nodes.apis import (
|
|||
KlingLipSyncInputObject,
|
||||
KlingLipSyncRequest,
|
||||
KlingLipSyncResponse,
|
||||
KlingVirtualTryOnModelName,
|
||||
KlingVirtualTryOnRequest,
|
||||
KlingVirtualTryOnResponse,
|
||||
KlingVideoResult,
|
||||
KlingImageResult,
|
||||
KlingImageGenerationsRequest,
|
||||
KlingImageGenerationsResponse,
|
||||
KlingImageGenImageReferenceType,
|
||||
KlingImageGenAspectRatio,
|
||||
KlingVideoEffectsRequest,
|
||||
KlingVideoEffectsResponse,
|
||||
KlingDualCharacterEffectsScene,
|
||||
KlingSingleImageEffectsScene,
|
||||
KlingDualCharacterEffectInput,
|
||||
KlingSingleImageEffectInput,
|
||||
KlingCharacterEffectModelName,
|
||||
KlingSingleImageEffectModelName,
|
||||
)
|
||||
from comfy_api_nodes.apis.kling import (
|
||||
ImageToVideoWithAudioRequest,
|
||||
|
|
@ -119,9 +104,6 @@ PATH_TEXT_TO_VIDEO = f"/proxy/kling/{KLING_API_VERSION}/videos/text2video"
|
|||
PATH_IMAGE_TO_VIDEO = f"/proxy/kling/{KLING_API_VERSION}/videos/image2video"
|
||||
PATH_VIDEO_EXTEND = f"/proxy/kling/{KLING_API_VERSION}/videos/video-extend"
|
||||
PATH_LIP_SYNC = f"/proxy/kling/{KLING_API_VERSION}/videos/lip-sync"
|
||||
PATH_VIDEO_EFFECTS = f"/proxy/kling/{KLING_API_VERSION}/videos/effects"
|
||||
PATH_CHARACTER_IMAGE = f"/proxy/kling/{KLING_API_VERSION}/images/generations"
|
||||
PATH_VIRTUAL_TRY_ON = f"/proxy/kling/{KLING_API_VERSION}/images/kolors-virtual-try-on"
|
||||
PATH_IMAGE_GENERATIONS = f"/proxy/kling/{KLING_API_VERSION}/images/generations"
|
||||
|
||||
MAX_PROMPT_LENGTH_T2V = 2500
|
||||
|
|
@ -133,21 +115,11 @@ MAX_PROMPT_LENGTH_LIP_SYNC = 120
|
|||
AVERAGE_DURATION_T2V = 319
|
||||
AVERAGE_DURATION_I2V = 164
|
||||
AVERAGE_DURATION_LIP_SYNC = 455
|
||||
AVERAGE_DURATION_VIRTUAL_TRY_ON = 19
|
||||
AVERAGE_DURATION_IMAGE_GEN = 32
|
||||
AVERAGE_DURATION_VIDEO_EFFECTS = 320
|
||||
AVERAGE_DURATION_VIDEO_EXTEND = 320
|
||||
|
||||
|
||||
MODE_TEXT2VIDEO = {
|
||||
"standard mode / 5s duration / kling-v1-6": ("std", "5", "kling-v1-6"),
|
||||
"standard mode / 10s duration / kling-v1-6": ("std", "10", "kling-v1-6"),
|
||||
"pro mode / 5s duration / kling-v2-master": ("pro", "5", "kling-v2-master"),
|
||||
"pro mode / 10s duration / kling-v2-master": ("pro", "10", "kling-v2-master"),
|
||||
"standard mode / 5s duration / kling-v2-master": ("std", "5", "kling-v2-master"),
|
||||
"standard mode / 10s duration / kling-v2-master": ("std", "10", "kling-v2-master"),
|
||||
"pro mode / 5s duration / kling-v2-1-master": ("pro", "5", "kling-v2-1-master"),
|
||||
"pro mode / 10s duration / kling-v2-1-master": ("pro", "10", "kling-v2-1-master"),
|
||||
"pro mode / 5s duration / kling-v2-5-turbo": ("pro", "5", "kling-v2-5-turbo"),
|
||||
"pro mode / 10s duration / kling-v2-5-turbo": ("pro", "10", "kling-v2-5-turbo"),
|
||||
}
|
||||
|
|
@ -160,12 +132,6 @@ See: [Kling API Docs Capability Map](https://app.klingai.com/global/dev/document
|
|||
|
||||
|
||||
MODE_START_END_FRAME = {
|
||||
"pro mode / 5s duration / kling-v1-5": ("pro", "5", "kling-v1-5"),
|
||||
"pro mode / 10s duration / kling-v1-5": ("pro", "10", "kling-v1-5"),
|
||||
"pro mode / 5s duration / kling-v1-6": ("pro", "5", "kling-v1-6"),
|
||||
"pro mode / 10s duration / kling-v1-6": ("pro", "10", "kling-v1-6"),
|
||||
"pro mode / 5s duration / kling-v2-1": ("pro", "5", "kling-v2-1"),
|
||||
"pro mode / 10s duration / kling-v2-1": ("pro", "10", "kling-v2-1"),
|
||||
"pro mode / 5s duration / kling-v2-5-turbo": ("pro", "5", "kling-v2-5-turbo"),
|
||||
"pro mode / 10s duration / kling-v2-5-turbo": ("pro", "10", "kling-v2-5-turbo"),
|
||||
}
|
||||
|
|
@ -287,11 +253,6 @@ async def finish_omni_video_task(cls: type[IO.ComfyNode], response: TaskStatusRe
|
|||
return IO.NodeOutput(await download_url_to_video_output(final_response.data.task_result.videos[0].url))
|
||||
|
||||
|
||||
def is_valid_camera_control_configs(configs: list[float]) -> bool:
|
||||
"""Verifies that at least one camera control configuration is non-zero."""
|
||||
return any(not math.isclose(value, 0.0) for value in configs)
|
||||
|
||||
|
||||
def is_valid_task_creation_response(response: KlingText2VideoResponse) -> bool:
|
||||
"""Verifies that the initial response contains a task ID."""
|
||||
return bool(response.data.task_id)
|
||||
|
|
@ -307,7 +268,7 @@ def is_valid_video_response(response: KlingText2VideoResponse) -> bool:
|
|||
)
|
||||
|
||||
|
||||
def is_valid_image_response(response: KlingVirtualTryOnResponse) -> bool:
|
||||
def is_valid_image_response(response: KlingImageGenerationsResponse) -> bool:
|
||||
"""Verifies that the response contains a task result with at least one image."""
|
||||
return (
|
||||
response.data is not None
|
||||
|
|
@ -430,7 +391,6 @@ async def execute_text2video(
|
|||
model_mode: str,
|
||||
duration: str,
|
||||
aspect_ratio: str,
|
||||
camera_control: KlingCameraControl | None = None,
|
||||
) -> IO.NodeOutput:
|
||||
validate_prompts(prompt, negative_prompt, MAX_PROMPT_LENGTH_T2V)
|
||||
task_creation_response = await sync_op(
|
||||
|
|
@ -445,7 +405,6 @@ async def execute_text2video(
|
|||
model_name=model_name,
|
||||
cfg_scale=cfg_scale,
|
||||
aspect_ratio=KlingVideoGenAspectRatio(aspect_ratio),
|
||||
camera_control=camera_control,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -475,19 +434,11 @@ async def execute_image2video(
|
|||
model_mode: str,
|
||||
aspect_ratio: str,
|
||||
duration: str,
|
||||
camera_control: KlingCameraControl | None = None,
|
||||
end_frame: torch.Tensor | None = None,
|
||||
) -> IO.NodeOutput:
|
||||
validate_prompts(prompt, negative_prompt, MAX_PROMPT_LENGTH_I2V)
|
||||
validate_input_image(start_frame)
|
||||
|
||||
if camera_control is not None:
|
||||
# Camera control type for image 2 video is always `simple`
|
||||
camera_control.type = KlingCameraControlType.simple
|
||||
|
||||
if model_mode == "std" and model_name == KlingVideoGenModelName.kling_v2_5_turbo.value:
|
||||
model_mode = "pro" # October 5: currently "std" mode is not supported for this model
|
||||
|
||||
task_creation_response = await sync_op(
|
||||
cls,
|
||||
ApiEndpoint(path=PATH_IMAGE_TO_VIDEO, method="POST"),
|
||||
|
|
@ -505,7 +456,6 @@ async def execute_image2video(
|
|||
cfg_scale=cfg_scale,
|
||||
mode=KlingVideoGenMode(model_mode),
|
||||
duration=KlingVideoGenDuration(duration),
|
||||
camera_control=camera_control,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -525,59 +475,6 @@ async def execute_image2video(
|
|||
return IO.NodeOutput(await download_url_to_video_output(str(video.url)), str(video.id), str(video.duration))
|
||||
|
||||
|
||||
async def execute_video_effect(
|
||||
cls: type[IO.ComfyNode],
|
||||
dual_character: bool,
|
||||
effect_scene: KlingDualCharacterEffectsScene | KlingSingleImageEffectsScene,
|
||||
model_name: str,
|
||||
duration: KlingVideoGenDuration,
|
||||
image_1: torch.Tensor,
|
||||
image_2: torch.Tensor | None = None,
|
||||
model_mode: KlingVideoGenMode | None = None,
|
||||
) -> tuple[InputImpl.VideoFromFile, str, str]:
|
||||
if dual_character:
|
||||
request_input_field = KlingDualCharacterEffectInput(
|
||||
model_name=model_name,
|
||||
mode=model_mode,
|
||||
images=[
|
||||
tensor_to_base64_string(image_1),
|
||||
tensor_to_base64_string(image_2),
|
||||
],
|
||||
duration=duration,
|
||||
)
|
||||
else:
|
||||
request_input_field = KlingSingleImageEffectInput(
|
||||
model_name=model_name,
|
||||
image=tensor_to_base64_string(image_1),
|
||||
duration=duration,
|
||||
)
|
||||
|
||||
task_creation_response = await sync_op(
|
||||
cls,
|
||||
endpoint=ApiEndpoint(path=PATH_VIDEO_EFFECTS, method="POST"),
|
||||
response_model=KlingVideoEffectsResponse,
|
||||
data=KlingVideoEffectsRequest(
|
||||
effect_scene=effect_scene,
|
||||
input=request_input_field,
|
||||
),
|
||||
)
|
||||
|
||||
validate_task_creation_response(task_creation_response)
|
||||
task_id = task_creation_response.data.task_id
|
||||
|
||||
final_response = await poll_op(
|
||||
cls,
|
||||
ApiEndpoint(path=f"{PATH_VIDEO_EFFECTS}/{task_id}"),
|
||||
response_model=KlingVideoEffectsResponse,
|
||||
estimated_duration=AVERAGE_DURATION_VIDEO_EFFECTS,
|
||||
status_extractor=lambda r: (r.data.task_status.value if r.data and r.data.task_status else None),
|
||||
)
|
||||
validate_video_result_response(final_response)
|
||||
|
||||
video = get_video_from_response(final_response)
|
||||
return await download_url_to_video_output(str(video.url)), str(video.id), str(video.duration)
|
||||
|
||||
|
||||
async def execute_lipsync(
|
||||
cls: type[IO.ComfyNode],
|
||||
video: Input.Video,
|
||||
|
|
@ -640,125 +537,6 @@ async def execute_lipsync(
|
|||
return IO.NodeOutput(await download_url_to_video_output(str(video.url)), str(video.id), str(video.duration))
|
||||
|
||||
|
||||
class KlingCameraControls(IO.ComfyNode):
|
||||
"""Kling Camera Controls Node"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> IO.Schema:
|
||||
return IO.Schema(
|
||||
node_id="KlingCameraControls",
|
||||
display_name="Kling Camera Controls",
|
||||
category="partner/video/Kling",
|
||||
description="Allows specifying configuration options for Kling Camera Controls and motion control effects.",
|
||||
inputs=[
|
||||
IO.Combo.Input("camera_control_type", options=KlingCameraControlType),
|
||||
IO.Float.Input(
|
||||
"horizontal_movement",
|
||||
default=0.0,
|
||||
min=-10.0,
|
||||
max=10.0,
|
||||
step=0.25,
|
||||
display_mode=IO.NumberDisplay.slider,
|
||||
tooltip="Controls camera's movement along horizontal axis (x-axis). Negative indicates left, positive indicates right",
|
||||
),
|
||||
IO.Float.Input(
|
||||
"vertical_movement",
|
||||
default=0.0,
|
||||
min=-10.0,
|
||||
max=10.0,
|
||||
step=0.25,
|
||||
display_mode=IO.NumberDisplay.slider,
|
||||
tooltip="Controls camera's movement along vertical axis (y-axis). Negative indicates downward, positive indicates upward.",
|
||||
),
|
||||
IO.Float.Input(
|
||||
"pan",
|
||||
default=0.5,
|
||||
min=-10.0,
|
||||
max=10.0,
|
||||
step=0.25,
|
||||
display_mode=IO.NumberDisplay.slider,
|
||||
tooltip="Controls camera's rotation in vertical plane (x-axis). Negative indicates downward rotation, positive indicates upward rotation.",
|
||||
),
|
||||
IO.Float.Input(
|
||||
"tilt",
|
||||
default=0.0,
|
||||
min=-10.0,
|
||||
max=10.0,
|
||||
step=0.25,
|
||||
display_mode=IO.NumberDisplay.slider,
|
||||
tooltip="Controls camera's rotation in horizontal plane (y-axis). Negative indicates left rotation, positive indicates right rotation.",
|
||||
),
|
||||
IO.Float.Input(
|
||||
"roll",
|
||||
default=0.0,
|
||||
min=-10.0,
|
||||
max=10.0,
|
||||
step=0.25,
|
||||
display_mode=IO.NumberDisplay.slider,
|
||||
tooltip="Controls camera's rolling amount (z-axis). Negative indicates counterclockwise, positive indicates clockwise.",
|
||||
),
|
||||
IO.Float.Input(
|
||||
"zoom",
|
||||
default=0.0,
|
||||
min=-10.0,
|
||||
max=10.0,
|
||||
step=0.25,
|
||||
display_mode=IO.NumberDisplay.slider,
|
||||
tooltip="Controls change in camera's focal length. Negative indicates narrower field of view, positive indicates wider field of view.",
|
||||
),
|
||||
],
|
||||
outputs=[IO.Custom("CAMERA_CONTROL").Output(display_name="camera_control")],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def validate_inputs(
|
||||
cls,
|
||||
horizontal_movement: float,
|
||||
vertical_movement: float,
|
||||
pan: float,
|
||||
tilt: float,
|
||||
roll: float,
|
||||
zoom: float,
|
||||
) -> bool | str:
|
||||
if not is_valid_camera_control_configs(
|
||||
[
|
||||
horizontal_movement,
|
||||
vertical_movement,
|
||||
pan,
|
||||
tilt,
|
||||
roll,
|
||||
zoom,
|
||||
]
|
||||
):
|
||||
return "Invalid camera control configs: at least one of the values must be non-zero"
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def execute(
|
||||
cls,
|
||||
camera_control_type: str,
|
||||
horizontal_movement: float,
|
||||
vertical_movement: float,
|
||||
pan: float,
|
||||
tilt: float,
|
||||
roll: float,
|
||||
zoom: float,
|
||||
) -> IO.NodeOutput:
|
||||
return IO.NodeOutput(
|
||||
KlingCameraControl(
|
||||
type=KlingCameraControlType(camera_control_type),
|
||||
config=KlingCameraConfig(
|
||||
horizontal=horizontal_movement,
|
||||
vertical=vertical_movement,
|
||||
pan=pan,
|
||||
roll=roll,
|
||||
tilt=tilt,
|
||||
zoom=zoom,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class KlingTextToVideoNode(IO.ComfyNode):
|
||||
"""Kling Text to Video Node"""
|
||||
|
||||
|
|
@ -782,7 +560,7 @@ class KlingTextToVideoNode(IO.ComfyNode):
|
|||
IO.Combo.Input(
|
||||
"mode",
|
||||
options=modes,
|
||||
default=modes[8],
|
||||
default=modes[0],
|
||||
tooltip="The configuration to use for the video generation following the format: mode / duration / model_name.",
|
||||
),
|
||||
],
|
||||
|
|
@ -802,25 +580,7 @@ class KlingTextToVideoNode(IO.ComfyNode):
|
|||
expr="""
|
||||
(
|
||||
$m := widgets.mode;
|
||||
$contains($m,"v2-5-turbo")
|
||||
? ($contains($m,"10") ? {"type":"usd","usd":0.7} : {"type":"usd","usd":0.35})
|
||||
: $contains($m,"v2-1-master")
|
||||
? ($contains($m,"10s") ? {"type":"usd","usd":2.8} : {"type":"usd","usd":1.4})
|
||||
: $contains($m,"v2-master")
|
||||
? ($contains($m,"10s") ? {"type":"usd","usd":2.8} : {"type":"usd","usd":1.4})
|
||||
: $contains($m,"v1-6")
|
||||
? (
|
||||
$contains($m,"pro")
|
||||
? ($contains($m,"10s") ? {"type":"usd","usd":0.98} : {"type":"usd","usd":0.49})
|
||||
: ($contains($m,"10s") ? {"type":"usd","usd":0.56} : {"type":"usd","usd":0.28})
|
||||
)
|
||||
: $contains($m,"v1")
|
||||
? (
|
||||
$contains($m,"pro")
|
||||
? ($contains($m,"10s") ? {"type":"usd","usd":0.98} : {"type":"usd","usd":0.49})
|
||||
: ($contains($m,"10s") ? {"type":"usd","usd":0.28} : {"type":"usd","usd":0.14})
|
||||
)
|
||||
: {"type":"usd","usd":0.14}
|
||||
$contains($m,"10") ? {"type":"usd","usd":0.7} : {"type":"usd","usd":0.35}
|
||||
)
|
||||
""",
|
||||
),
|
||||
|
|
@ -1716,71 +1476,6 @@ class OmniProImageNode(IO.ComfyNode):
|
|||
return IO.NodeOutput(torch.cat(tensors, dim=0))
|
||||
|
||||
|
||||
class KlingCameraControlT2VNode(IO.ComfyNode):
|
||||
"""
|
||||
Kling Text to Video Camera Control Node. This node is a text to video node, but it supports controlling the camera.
|
||||
Duration, mode, and model_name request fields are hard-coded because camera control is only supported in pro mode with the kling-v1-5 model at 5s duration as of 2025-05-02.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> IO.Schema:
|
||||
return IO.Schema(
|
||||
node_id="KlingCameraControlT2VNode",
|
||||
display_name="Kling Text to Video (Camera Control)",
|
||||
category="partner/video/Kling",
|
||||
description="Transform text into cinematic videos with professional camera movements that simulate real-world cinematography. Control virtual camera actions including zoom, rotation, pan, tilt, and first-person view, while maintaining focus on your original text.",
|
||||
inputs=[
|
||||
IO.String.Input("prompt", multiline=True, tooltip="Positive text prompt"),
|
||||
IO.String.Input("negative_prompt", multiline=True, tooltip="Negative text prompt"),
|
||||
IO.Float.Input("cfg_scale", default=0.75, min=0.0, max=1.0),
|
||||
IO.Combo.Input(
|
||||
"aspect_ratio",
|
||||
options=KlingVideoGenAspectRatio,
|
||||
default="16:9",
|
||||
),
|
||||
IO.Custom("CAMERA_CONTROL").Input(
|
||||
"camera_control",
|
||||
tooltip="Can be created using the Kling Camera Controls node. Controls the camera movement and motion during the video generation.",
|
||||
),
|
||||
],
|
||||
outputs=[
|
||||
IO.Video.Output(),
|
||||
IO.String.Output(display_name="video_id"),
|
||||
IO.String.Output(display_name="duration"),
|
||||
],
|
||||
hidden=[
|
||||
IO.Hidden.auth_token_comfy_org,
|
||||
IO.Hidden.api_key_comfy_org,
|
||||
IO.Hidden.unique_id,
|
||||
],
|
||||
is_api_node=True,
|
||||
price_badge=IO.PriceBadge(
|
||||
expr="""{"type":"usd","usd":0.14}""",
|
||||
),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def execute(
|
||||
cls,
|
||||
prompt: str,
|
||||
negative_prompt: str,
|
||||
cfg_scale: float,
|
||||
aspect_ratio: str,
|
||||
camera_control: KlingCameraControl | None = None,
|
||||
) -> IO.NodeOutput:
|
||||
return await execute_text2video(
|
||||
cls,
|
||||
model_name=KlingVideoGenModelName.kling_v1,
|
||||
cfg_scale=cfg_scale,
|
||||
model_mode=KlingVideoGenMode.std,
|
||||
aspect_ratio=KlingVideoGenAspectRatio(aspect_ratio),
|
||||
duration=KlingVideoGenDuration.field_5,
|
||||
prompt=prompt,
|
||||
negative_prompt=negative_prompt,
|
||||
camera_control=camera_control,
|
||||
)
|
||||
|
||||
|
||||
class KlingImage2VideoNode(IO.ComfyNode):
|
||||
"""Kling Image to Video Node"""
|
||||
|
||||
|
|
@ -1796,11 +1491,10 @@ class KlingImage2VideoNode(IO.ComfyNode):
|
|||
IO.String.Input("negative_prompt", multiline=True, tooltip="Negative text prompt"),
|
||||
IO.Combo.Input(
|
||||
"model_name",
|
||||
options=KlingVideoGenModelName,
|
||||
default="kling-v2-master",
|
||||
options=["kling-v2-5-turbo"],
|
||||
),
|
||||
IO.Float.Input("cfg_scale", default=0.8, min=0.0, max=1.0),
|
||||
IO.Combo.Input("mode", options=KlingVideoGenMode, default=KlingVideoGenMode.std),
|
||||
IO.Combo.Input("mode", options=["pro"]),
|
||||
IO.Combo.Input(
|
||||
"aspect_ratio",
|
||||
options=KlingVideoGenAspectRatio,
|
||||
|
|
@ -1820,29 +1514,10 @@ class KlingImage2VideoNode(IO.ComfyNode):
|
|||
],
|
||||
is_api_node=True,
|
||||
price_badge=IO.PriceBadge(
|
||||
depends_on=IO.PriceBadgeDepends(widgets=["mode", "model_name", "duration"]),
|
||||
depends_on=IO.PriceBadgeDepends(widgets=["duration"]),
|
||||
expr="""
|
||||
(
|
||||
$mode := widgets.mode;
|
||||
$model := widgets.model_name;
|
||||
$dur := widgets.duration;
|
||||
$contains($model,"v2-5-turbo")
|
||||
? ($contains($dur,"10") ? {"type":"usd","usd":0.7} : {"type":"usd","usd":0.35})
|
||||
: ($contains($model,"v2-1-master") or $contains($model,"v2-master"))
|
||||
? ($contains($dur,"10") ? {"type":"usd","usd":2.8} : {"type":"usd","usd":1.4})
|
||||
: ($contains($model,"v2-1") or $contains($model,"v1-6") or $contains($model,"v1-5"))
|
||||
? (
|
||||
$contains($mode,"pro")
|
||||
? ($contains($dur,"10") ? {"type":"usd","usd":0.98} : {"type":"usd","usd":0.49})
|
||||
: ($contains($dur,"10") ? {"type":"usd","usd":0.56} : {"type":"usd","usd":0.28})
|
||||
)
|
||||
: $contains($model,"v1")
|
||||
? (
|
||||
$contains($mode,"pro")
|
||||
? ($contains($dur,"10") ? {"type":"usd","usd":0.98} : {"type":"usd","usd":0.49})
|
||||
: ($contains($dur,"10") ? {"type":"usd","usd":0.28} : {"type":"usd","usd":0.14})
|
||||
)
|
||||
: {"type":"usd","usd":0.14}
|
||||
$contains(widgets.duration,"10") ? {"type":"usd","usd":0.7} : {"type":"usd","usd":0.35}
|
||||
)
|
||||
""",
|
||||
),
|
||||
|
|
@ -1859,8 +1534,6 @@ class KlingImage2VideoNode(IO.ComfyNode):
|
|||
mode: str,
|
||||
aspect_ratio: str,
|
||||
duration: str,
|
||||
camera_control: KlingCameraControl | None = None,
|
||||
end_frame: torch.Tensor | None = None,
|
||||
) -> IO.NodeOutput:
|
||||
return await execute_image2video(
|
||||
cls,
|
||||
|
|
@ -1872,79 +1545,6 @@ class KlingImage2VideoNode(IO.ComfyNode):
|
|||
aspect_ratio=aspect_ratio,
|
||||
model_mode=mode,
|
||||
duration=duration,
|
||||
camera_control=camera_control,
|
||||
end_frame=end_frame,
|
||||
)
|
||||
|
||||
|
||||
class KlingCameraControlI2VNode(IO.ComfyNode):
|
||||
"""
|
||||
Kling Image to Video Camera Control Node. This node is a image to video node, but it supports controlling the camera.
|
||||
Duration, mode, and model_name request fields are hard-coded because camera control is only supported in pro mode with the kling-v1-5 model at 5s duration as of 2025-05-02.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> IO.Schema:
|
||||
return IO.Schema(
|
||||
node_id="KlingCameraControlI2VNode",
|
||||
display_name="Kling Image to Video (Camera Control)",
|
||||
category="partner/video/Kling",
|
||||
description="Transform still images into cinematic videos with professional camera movements that simulate real-world cinematography. Control virtual camera actions including zoom, rotation, pan, tilt, and first-person view, while maintaining focus on your original image.",
|
||||
inputs=[
|
||||
IO.Image.Input(
|
||||
"start_frame",
|
||||
tooltip="Reference Image - URL or Base64 encoded string, cannot exceed 10MB, resolution not less than 300*300px, aspect ratio between 1:2.5 ~ 2.5:1. Base64 should not include data:image prefix.",
|
||||
),
|
||||
IO.String.Input("prompt", multiline=True, tooltip="Positive text prompt"),
|
||||
IO.String.Input("negative_prompt", multiline=True, tooltip="Negative text prompt"),
|
||||
IO.Float.Input("cfg_scale", default=0.75, min=0.0, max=1.0),
|
||||
IO.Combo.Input(
|
||||
"aspect_ratio",
|
||||
options=KlingVideoGenAspectRatio,
|
||||
default=KlingVideoGenAspectRatio.field_16_9,
|
||||
),
|
||||
IO.Custom("CAMERA_CONTROL").Input(
|
||||
"camera_control",
|
||||
tooltip="Can be created using the Kling Camera Controls node. Controls the camera movement and motion during the video generation.",
|
||||
),
|
||||
],
|
||||
outputs=[
|
||||
IO.Video.Output(),
|
||||
IO.String.Output(display_name="video_id"),
|
||||
IO.String.Output(display_name="duration"),
|
||||
],
|
||||
hidden=[
|
||||
IO.Hidden.auth_token_comfy_org,
|
||||
IO.Hidden.api_key_comfy_org,
|
||||
IO.Hidden.unique_id,
|
||||
],
|
||||
is_api_node=True,
|
||||
price_badge=IO.PriceBadge(
|
||||
expr="""{"type":"usd","usd":0.49}""",
|
||||
),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def execute(
|
||||
cls,
|
||||
start_frame: torch.Tensor,
|
||||
prompt: str,
|
||||
negative_prompt: str,
|
||||
cfg_scale: float,
|
||||
aspect_ratio: str,
|
||||
camera_control: KlingCameraControl,
|
||||
) -> IO.NodeOutput:
|
||||
return await execute_image2video(
|
||||
cls,
|
||||
model_name=KlingVideoGenModelName.kling_v1_5,
|
||||
start_frame=start_frame,
|
||||
cfg_scale=cfg_scale,
|
||||
model_mode=KlingVideoGenMode.pro,
|
||||
aspect_ratio=KlingVideoGenAspectRatio(aspect_ratio),
|
||||
duration=KlingVideoGenDuration.field_5,
|
||||
prompt=prompt,
|
||||
negative_prompt=negative_prompt,
|
||||
camera_control=camera_control,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -1977,7 +1577,7 @@ class KlingStartEndFrameNode(IO.ComfyNode):
|
|||
IO.Combo.Input(
|
||||
"mode",
|
||||
options=modes,
|
||||
default=modes[6],
|
||||
default=modes[0],
|
||||
tooltip="The configuration to use for the video generation following the format: mode / duration / model_name.",
|
||||
),
|
||||
],
|
||||
|
|
@ -1997,25 +1597,7 @@ class KlingStartEndFrameNode(IO.ComfyNode):
|
|||
expr="""
|
||||
(
|
||||
$m := widgets.mode;
|
||||
$contains($m,"v2-5-turbo")
|
||||
? ($contains($m,"10") ? {"type":"usd","usd":0.7} : {"type":"usd","usd":0.35})
|
||||
: $contains($m,"v2-1")
|
||||
? ($contains($m,"10s") ? {"type":"usd","usd":0.98} : {"type":"usd","usd":0.49})
|
||||
: $contains($m,"v2-master")
|
||||
? ($contains($m,"10s") ? {"type":"usd","usd":2.8} : {"type":"usd","usd":1.4})
|
||||
: $contains($m,"v1-6")
|
||||
? (
|
||||
$contains($m,"pro")
|
||||
? ($contains($m,"10s") ? {"type":"usd","usd":0.98} : {"type":"usd","usd":0.49})
|
||||
: ($contains($m,"10s") ? {"type":"usd","usd":0.56} : {"type":"usd","usd":0.28})
|
||||
)
|
||||
: $contains($m,"v1")
|
||||
? (
|
||||
$contains($m,"pro")
|
||||
? ($contains($m,"10s") ? {"type":"usd","usd":0.98} : {"type":"usd","usd":0.49})
|
||||
: ($contains($m,"10s") ? {"type":"usd","usd":0.28} : {"type":"usd","usd":0.14})
|
||||
)
|
||||
: {"type":"usd","usd":0.14}
|
||||
$contains($m,"10") ? {"type":"usd","usd":0.7} : {"type":"usd","usd":0.35}
|
||||
)
|
||||
""",
|
||||
),
|
||||
|
|
@ -2126,169 +1708,6 @@ class KlingVideoExtendNode(IO.ComfyNode):
|
|||
return IO.NodeOutput(await download_url_to_video_output(str(video.url)), str(video.id), str(video.duration))
|
||||
|
||||
|
||||
class KlingDualCharacterVideoEffectNode(IO.ComfyNode):
|
||||
"""Kling Dual Character Video Effect Node"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> IO.Schema:
|
||||
return IO.Schema(
|
||||
node_id="KlingDualCharacterVideoEffectNode",
|
||||
display_name="Kling Dual Character Video Effects",
|
||||
category="partner/video/Kling",
|
||||
description="Achieve different special effects when generating a video based on the effect_scene. First image will be positioned on left side, second on right side of the composite.",
|
||||
inputs=[
|
||||
IO.Image.Input("image_left", tooltip="Left side image"),
|
||||
IO.Image.Input("image_right", tooltip="Right side image"),
|
||||
IO.Combo.Input(
|
||||
"effect_scene",
|
||||
options=[i.value for i in KlingDualCharacterEffectsScene],
|
||||
),
|
||||
IO.Combo.Input(
|
||||
"model_name",
|
||||
options=[i.value for i in KlingCharacterEffectModelName],
|
||||
default="kling-v1",
|
||||
),
|
||||
IO.Combo.Input(
|
||||
"mode",
|
||||
options=[i.value for i in KlingVideoGenMode],
|
||||
default="std",
|
||||
),
|
||||
IO.Combo.Input(
|
||||
"duration",
|
||||
options=[i.value for i in KlingVideoGenDuration],
|
||||
),
|
||||
],
|
||||
outputs=[
|
||||
IO.Video.Output(),
|
||||
IO.String.Output(display_name="duration"),
|
||||
],
|
||||
hidden=[
|
||||
IO.Hidden.auth_token_comfy_org,
|
||||
IO.Hidden.api_key_comfy_org,
|
||||
IO.Hidden.unique_id,
|
||||
],
|
||||
is_api_node=True,
|
||||
price_badge=IO.PriceBadge(
|
||||
depends_on=IO.PriceBadgeDepends(widgets=["mode", "model_name", "duration"]),
|
||||
expr="""
|
||||
(
|
||||
$mode := widgets.mode;
|
||||
$model := widgets.model_name;
|
||||
$dur := widgets.duration;
|
||||
($contains($model,"v1-6") or $contains($model,"v1-5"))
|
||||
? (
|
||||
$contains($mode,"pro")
|
||||
? ($contains($dur,"10") ? {"type":"usd","usd":0.98} : {"type":"usd","usd":0.49})
|
||||
: ($contains($dur,"10") ? {"type":"usd","usd":0.56} : {"type":"usd","usd":0.28})
|
||||
)
|
||||
: $contains($model,"v1")
|
||||
? (
|
||||
$contains($mode,"pro")
|
||||
? ($contains($dur,"10") ? {"type":"usd","usd":0.98} : {"type":"usd","usd":0.49})
|
||||
: ($contains($dur,"10") ? {"type":"usd","usd":0.28} : {"type":"usd","usd":0.14})
|
||||
)
|
||||
: {"type":"usd","usd":0.14}
|
||||
)
|
||||
""",
|
||||
),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def execute(
|
||||
cls,
|
||||
image_left: torch.Tensor,
|
||||
image_right: torch.Tensor,
|
||||
effect_scene: KlingDualCharacterEffectsScene,
|
||||
model_name: KlingCharacterEffectModelName,
|
||||
mode: KlingVideoGenMode,
|
||||
duration: KlingVideoGenDuration,
|
||||
) -> IO.NodeOutput:
|
||||
video, _, duration = await execute_video_effect(
|
||||
cls,
|
||||
dual_character=True,
|
||||
effect_scene=effect_scene,
|
||||
model_name=model_name,
|
||||
model_mode=mode,
|
||||
duration=duration,
|
||||
image_1=image_left,
|
||||
image_2=image_right,
|
||||
)
|
||||
return IO.NodeOutput(video, duration)
|
||||
|
||||
|
||||
class KlingSingleImageVideoEffectNode(IO.ComfyNode):
|
||||
"""Kling Single Image Video Effect Node"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> IO.Schema:
|
||||
return IO.Schema(
|
||||
node_id="KlingSingleImageVideoEffectNode",
|
||||
display_name="Kling Video Effects",
|
||||
category="partner/video/Kling",
|
||||
description="Achieve different special effects when generating a video based on the effect_scene.",
|
||||
inputs=[
|
||||
IO.Image.Input(
|
||||
"image",
|
||||
tooltip=" Reference Image. URL or Base64 encoded string (without data:image prefix). File size cannot exceed 10MB, resolution not less than 300*300px, aspect ratio between 1:2.5 ~ 2.5:1",
|
||||
),
|
||||
IO.Combo.Input(
|
||||
"effect_scene",
|
||||
options=[i.value for i in KlingSingleImageEffectsScene],
|
||||
),
|
||||
IO.Combo.Input(
|
||||
"model_name",
|
||||
options=[i.value for i in KlingSingleImageEffectModelName],
|
||||
),
|
||||
IO.Combo.Input(
|
||||
"duration",
|
||||
options=[i.value for i in KlingVideoGenDuration],
|
||||
),
|
||||
],
|
||||
outputs=[
|
||||
IO.Video.Output(),
|
||||
IO.String.Output(display_name="video_id"),
|
||||
IO.String.Output(display_name="duration"),
|
||||
],
|
||||
hidden=[
|
||||
IO.Hidden.auth_token_comfy_org,
|
||||
IO.Hidden.api_key_comfy_org,
|
||||
IO.Hidden.unique_id,
|
||||
],
|
||||
is_api_node=True,
|
||||
price_badge=IO.PriceBadge(
|
||||
depends_on=IO.PriceBadgeDepends(widgets=["effect_scene"]),
|
||||
expr="""
|
||||
(
|
||||
($contains(widgets.effect_scene,"dizzydizzy") or $contains(widgets.effect_scene,"bloombloom"))
|
||||
? {"type":"usd","usd":0.49}
|
||||
: {"type":"usd","usd":0.28}
|
||||
)
|
||||
""",
|
||||
),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def execute(
|
||||
cls,
|
||||
image: torch.Tensor,
|
||||
effect_scene: KlingSingleImageEffectsScene,
|
||||
model_name: KlingSingleImageEffectModelName,
|
||||
duration: KlingVideoGenDuration,
|
||||
) -> IO.NodeOutput:
|
||||
return IO.NodeOutput(
|
||||
*(
|
||||
await execute_video_effect(
|
||||
cls,
|
||||
dual_character=False,
|
||||
effect_scene=effect_scene,
|
||||
model_name=model_name,
|
||||
duration=duration,
|
||||
image_1=image,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class KlingLipSyncAudioToVideoNode(IO.ComfyNode):
|
||||
"""Kling Lip Sync Audio to Video Node. Syncs mouth movements in a video file to the audio content of an audio file."""
|
||||
|
||||
|
|
@ -2409,73 +1828,6 @@ class KlingLipSyncTextToVideoNode(IO.ComfyNode):
|
|||
)
|
||||
|
||||
|
||||
class KlingVirtualTryOnNode(IO.ComfyNode):
|
||||
"""Kling Virtual Try On Node."""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> IO.Schema:
|
||||
return IO.Schema(
|
||||
node_id="KlingVirtualTryOnNode",
|
||||
display_name="Kling Virtual Try On",
|
||||
category="partner/image/Kling",
|
||||
description="Kling Virtual Try On Node. Input a human image and a cloth image to try on the cloth on the human. You can merge multiple clothing item pictures into one image with a white background.",
|
||||
inputs=[
|
||||
IO.Image.Input("human_image"),
|
||||
IO.Image.Input("cloth_image"),
|
||||
IO.Combo.Input(
|
||||
"model_name",
|
||||
options=[i.value for i in KlingVirtualTryOnModelName],
|
||||
default="kolors-virtual-try-on-v1",
|
||||
),
|
||||
],
|
||||
outputs=[
|
||||
IO.Image.Output(),
|
||||
],
|
||||
hidden=[
|
||||
IO.Hidden.auth_token_comfy_org,
|
||||
IO.Hidden.api_key_comfy_org,
|
||||
IO.Hidden.unique_id,
|
||||
],
|
||||
is_api_node=True,
|
||||
price_badge=IO.PriceBadge(
|
||||
expr="""{"type":"usd","usd":0.7}""",
|
||||
),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def execute(
|
||||
cls,
|
||||
human_image: torch.Tensor,
|
||||
cloth_image: torch.Tensor,
|
||||
model_name: KlingVirtualTryOnModelName,
|
||||
) -> IO.NodeOutput:
|
||||
task_creation_response = await sync_op(
|
||||
cls,
|
||||
ApiEndpoint(path=PATH_VIRTUAL_TRY_ON, method="POST"),
|
||||
response_model=KlingVirtualTryOnResponse,
|
||||
data=KlingVirtualTryOnRequest(
|
||||
human_image=tensor_to_base64_string(human_image),
|
||||
cloth_image=tensor_to_base64_string(cloth_image),
|
||||
model_name=model_name,
|
||||
),
|
||||
)
|
||||
|
||||
validate_task_creation_response(task_creation_response)
|
||||
task_id = task_creation_response.data.task_id
|
||||
|
||||
final_response = await poll_op(
|
||||
cls,
|
||||
ApiEndpoint(path=f"{PATH_VIRTUAL_TRY_ON}/{task_id}"),
|
||||
response_model=KlingVirtualTryOnResponse,
|
||||
estimated_duration=AVERAGE_DURATION_VIRTUAL_TRY_ON,
|
||||
status_extractor=lambda r: (r.data.task_status.value if r.data and r.data.task_status else None),
|
||||
)
|
||||
validate_image_result_response(final_response)
|
||||
|
||||
images = get_images_from_response(final_response)
|
||||
return IO.NodeOutput(await image_result_to_node_output(images))
|
||||
|
||||
|
||||
class KlingImageGenerationNode(IO.ComfyNode):
|
||||
"""Kling Image Generation Node. Generate an image from a text prompt with an optional reference image."""
|
||||
|
||||
|
|
@ -2514,7 +1866,7 @@ class KlingImageGenerationNode(IO.ComfyNode):
|
|||
tooltip="Subject reference similarity",
|
||||
advanced=True,
|
||||
),
|
||||
IO.Combo.Input("model_name", options=["kling-v3", "kling-v2", "kling-v1-5"]),
|
||||
IO.Combo.Input("model_name", options=["kling-v3", "kling-v2"]),
|
||||
IO.Combo.Input(
|
||||
"aspect_ratio",
|
||||
options=[i.value for i in KlingImageGenAspectRatio],
|
||||
|
|
@ -2550,14 +1902,10 @@ class KlingImageGenerationNode(IO.ComfyNode):
|
|||
],
|
||||
is_api_node=True,
|
||||
price_badge=IO.PriceBadge(
|
||||
depends_on=IO.PriceBadgeDepends(widgets=["model_name", "n"], inputs=["image"]),
|
||||
depends_on=IO.PriceBadgeDepends(widgets=["model_name", "n"]),
|
||||
expr="""
|
||||
(
|
||||
$m := widgets.model_name;
|
||||
$base :=
|
||||
$contains($m,"kling-v1-5")
|
||||
? (inputs.image.connected ? 0.028 : 0.014)
|
||||
: $contains($m,"kling-v3") ? 0.028 : 0.014;
|
||||
$base := $contains(widgets.model_name,"kling-v3") ? 0.028 : 0.014;
|
||||
{"type":"usd","usd": $base * widgets.n}
|
||||
)
|
||||
""",
|
||||
|
|
@ -3394,19 +2742,13 @@ class KlingExtension(ComfyExtension):
|
|||
@override
|
||||
async def get_node_list(self) -> list[type[IO.ComfyNode]]:
|
||||
return [
|
||||
KlingCameraControls,
|
||||
KlingTextToVideoNode,
|
||||
KlingImage2VideoNode,
|
||||
KlingCameraControlI2VNode,
|
||||
KlingCameraControlT2VNode,
|
||||
KlingStartEndFrameNode,
|
||||
KlingVideoExtendNode,
|
||||
KlingLipSyncAudioToVideoNode,
|
||||
KlingLipSyncTextToVideoNode,
|
||||
KlingVirtualTryOnNode,
|
||||
KlingImageGenerationNode,
|
||||
KlingSingleImageVideoEffectNode,
|
||||
KlingDualCharacterVideoEffectNode,
|
||||
OmniProTextToVideoNode,
|
||||
OmniProFirstLastFrameNode,
|
||||
OmniProImageToVideoNode,
|
||||
|
|
|
|||
Loading…
Reference in New Issue