1533 lines
52 KiB
Python
1533 lines
52 KiB
Python
import json
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parent
|
|
NODE_PREFIX = "Comfyui-Kling-Wrapper"
|
|
CLIENT_TYPE = "COMFYUI_KLING_WRAPPER_API_CLIENT"
|
|
ELEMENT_TYPE = "COMFYUI_KLING_WRAPPER_ELEMENT"
|
|
ELEMENT_LIST_TYPE = "COMFYUI_KLING_WRAPPER_ELEMENT_LIST"
|
|
LIPSYNC_INPUT_TYPE = "COMFYUI_KLING_WRAPPER_LIPSYNC_INPUT"
|
|
|
|
|
|
def input_slot(name, type_name, link=None, shape=7):
|
|
slot = {"name": name, "type": type_name, "link": link}
|
|
if type_name in {"IMAGE", "MASK"}:
|
|
slot["shape"] = shape
|
|
return slot
|
|
|
|
|
|
def output_slot(name, type_name, links=None, slot_index=None):
|
|
slot = {"name": name, "type": type_name, "links": links}
|
|
if slot_index is not None:
|
|
slot["slot_index"] = slot_index
|
|
return slot
|
|
|
|
|
|
def node(node_id, type_name, pos, size, order, widgets_values, inputs=None, outputs=None):
|
|
return {
|
|
"id": node_id,
|
|
"type": type_name,
|
|
"pos": pos,
|
|
"size": size,
|
|
"flags": {},
|
|
"order": order,
|
|
"mode": 0,
|
|
"inputs": inputs or [],
|
|
"outputs": outputs or [],
|
|
"properties": {"Node name for S&R": type_name},
|
|
"widgets_values": widgets_values,
|
|
}
|
|
|
|
|
|
def workflow(nodes, links):
|
|
return {
|
|
"id": "Comfyui-Kling-Wrapper-example",
|
|
"revision": 0,
|
|
"last_node_id": max(node["id"] for node in nodes),
|
|
"last_link_id": max((link[0] for link in links), default=0),
|
|
"nodes": nodes,
|
|
"links": links,
|
|
"groups": [],
|
|
"config": {},
|
|
"extra": {
|
|
"ds": {
|
|
"scale": 0.9,
|
|
"offset": [0, 0],
|
|
},
|
|
"frontendVersion": "1.42.8",
|
|
},
|
|
"version": 0.4,
|
|
}
|
|
|
|
|
|
def strip_client_dependency(data):
|
|
data = json.loads(json.dumps(data))
|
|
client_node_type = f"{NODE_PREFIX} Client"
|
|
removed_link_ids = set()
|
|
removed_node_ids = set()
|
|
input_index_maps = {}
|
|
cleaned_nodes = []
|
|
|
|
for workflow_node in data["nodes"]:
|
|
if workflow_node.get("type") == client_node_type:
|
|
removed_node_ids.add(workflow_node["id"])
|
|
continue
|
|
|
|
cleaned_inputs = []
|
|
input_index_maps[workflow_node["id"]] = {}
|
|
for old_index, node_input in enumerate(workflow_node.get("inputs", [])):
|
|
if node_input.get("type") == CLIENT_TYPE or node_input.get("name") == "client":
|
|
if node_input.get("link") is not None:
|
|
removed_link_ids.add(node_input["link"])
|
|
continue
|
|
input_index_maps[workflow_node["id"]][old_index] = len(cleaned_inputs)
|
|
cleaned_inputs.append(node_input)
|
|
|
|
workflow_node["inputs"] = cleaned_inputs
|
|
cleaned_nodes.append(workflow_node)
|
|
|
|
cleaned_links = []
|
|
for workflow_link in data.get("links", []):
|
|
link_id, src_node, _, dst_node, _, link_type = workflow_link
|
|
if link_type == CLIENT_TYPE or src_node in removed_node_ids or dst_node in removed_node_ids:
|
|
removed_link_ids.add(link_id)
|
|
continue
|
|
cleaned_link = list(workflow_link)
|
|
destination_map = input_index_maps.get(dst_node, {})
|
|
cleaned_link[4] = destination_map.get(cleaned_link[4], cleaned_link[4])
|
|
cleaned_links.append(cleaned_link)
|
|
|
|
for workflow_node in cleaned_nodes:
|
|
for node_input in workflow_node.get("inputs", []):
|
|
if node_input.get("link") in removed_link_ids:
|
|
node_input["link"] = None
|
|
|
|
for node_output in workflow_node.get("outputs", []):
|
|
links = node_output.get("links")
|
|
if isinstance(links, list):
|
|
links = [link_id for link_id in links if link_id not in removed_link_ids]
|
|
node_output["links"] = links or None
|
|
|
|
data["nodes"] = cleaned_nodes
|
|
data["links"] = cleaned_links
|
|
data["last_node_id"] = max((node["id"] for node in cleaned_nodes), default=0)
|
|
data["last_link_id"] = max((link[0] for link in cleaned_links), default=0)
|
|
return data
|
|
|
|
|
|
def client_node(node_id=1, order=0, area="china"):
|
|
type_name = f"{NODE_PREFIX} Client"
|
|
return node(
|
|
node_id,
|
|
type_name,
|
|
[40, 60],
|
|
[320, 210],
|
|
order,
|
|
["", "", 1, 30, area],
|
|
outputs=[output_slot("client", CLIENT_TYPE, slot_index=0)],
|
|
)
|
|
|
|
|
|
def load_image_node(node_id, filename, pos, order):
|
|
return node(
|
|
node_id,
|
|
"LoadImage",
|
|
pos,
|
|
[290, 340],
|
|
order,
|
|
[filename, "image"],
|
|
outputs=[
|
|
output_slot("IMAGE", "IMAGE", slot_index=0),
|
|
output_slot("MASK", "MASK", slot_index=1),
|
|
],
|
|
)
|
|
|
|
|
|
def load_video_node(node_id, filename, pos, order):
|
|
return node(
|
|
node_id,
|
|
"LoadVideo",
|
|
pos,
|
|
[320, 120],
|
|
order,
|
|
[filename],
|
|
outputs=[output_slot("VIDEO", "VIDEO", slot_index=0)],
|
|
)
|
|
|
|
|
|
def load_audio_node(node_id, filename, pos, order):
|
|
return node(
|
|
node_id,
|
|
"LoadAudio",
|
|
pos,
|
|
[320, 120],
|
|
order,
|
|
[filename],
|
|
outputs=[output_slot("AUDIO", "AUDIO", slot_index=0)],
|
|
)
|
|
|
|
|
|
def save_image_node(node_id, filename_prefix, pos, order, link_id):
|
|
return node(
|
|
node_id,
|
|
"SaveImage",
|
|
pos,
|
|
[320, 100],
|
|
order,
|
|
[filename_prefix],
|
|
inputs=[input_slot("images", "IMAGE", link_id)],
|
|
)
|
|
|
|
|
|
def preview_video_node(node_id, pos, order, link_id):
|
|
type_name = f"{NODE_PREFIX} Preview Video"
|
|
return node(
|
|
node_id,
|
|
type_name,
|
|
pos,
|
|
[430, 820],
|
|
order,
|
|
[],
|
|
inputs=[input_slot("video_url", "STRING", link_id)],
|
|
outputs=[output_slot("file_path", "STRING", slot_index=0)],
|
|
)
|
|
|
|
|
|
def preview_audio_node(node_id, pos, order, link_id):
|
|
type_name = f"{NODE_PREFIX} Preview Audio"
|
|
return node(
|
|
node_id,
|
|
type_name,
|
|
pos,
|
|
[360, 180],
|
|
order,
|
|
[NODE_PREFIX + "-audio", True],
|
|
inputs=[input_slot("audio_url", "STRING", link_id)],
|
|
outputs=[
|
|
output_slot("audio", "AUDIO", slot_index=0),
|
|
output_slot("file_path", "STRING", slot_index=1),
|
|
],
|
|
)
|
|
|
|
|
|
def image_generator_node(node_id, pos, order):
|
|
type_name = f"{NODE_PREFIX} Image Generator"
|
|
return node(
|
|
node_id,
|
|
type_name,
|
|
pos,
|
|
[430, 370],
|
|
order,
|
|
[
|
|
"kling-v3",
|
|
"A fashion editorial portrait of a young woman in soft morning light, realistic skin texture, elegant framing, filmic color grading.",
|
|
"",
|
|
"None",
|
|
0.5,
|
|
0.45,
|
|
1,
|
|
"16:9",
|
|
"1k",
|
|
],
|
|
inputs=[input_slot("client", CLIENT_TYPE)],
|
|
outputs=[output_slot("image", "IMAGE", slot_index=0)],
|
|
)
|
|
|
|
|
|
def text2video_node(node_id, pos, order, model, prompt, mode="pro", aspect_ratio="16:9", duration="5",
|
|
sound="off", voice_preset="None", shot_type="single", reference_video=""):
|
|
type_name = f"{NODE_PREFIX} Text2Video"
|
|
return node(
|
|
node_id,
|
|
type_name,
|
|
pos,
|
|
[430, 520],
|
|
order,
|
|
[
|
|
model,
|
|
prompt,
|
|
"",
|
|
0.5,
|
|
mode,
|
|
aspect_ratio,
|
|
duration,
|
|
"None",
|
|
"horizontal",
|
|
0.5,
|
|
sound,
|
|
voice_preset,
|
|
shot_type,
|
|
reference_video,
|
|
],
|
|
inputs=[input_slot("client", CLIENT_TYPE)],
|
|
outputs=[
|
|
output_slot("url", "STRING", slot_index=0),
|
|
output_slot("video_id", "STRING", slot_index=1),
|
|
],
|
|
)
|
|
|
|
|
|
def image2video_node(
|
|
node_id,
|
|
pos,
|
|
order,
|
|
model,
|
|
prompt="",
|
|
mode="pro",
|
|
duration="5",
|
|
shot_type="single",
|
|
reference_video="",
|
|
sound="off",
|
|
voice_preset="None",
|
|
):
|
|
type_name = f"{NODE_PREFIX} Image2Video"
|
|
return node(
|
|
node_id,
|
|
type_name,
|
|
pos,
|
|
[430, 520],
|
|
order,
|
|
[
|
|
model,
|
|
prompt,
|
|
"",
|
|
0.5,
|
|
mode,
|
|
duration,
|
|
"None",
|
|
"horizontal",
|
|
0.5,
|
|
sound,
|
|
voice_preset,
|
|
shot_type,
|
|
reference_video,
|
|
],
|
|
inputs=[
|
|
input_slot("client", CLIENT_TYPE),
|
|
input_slot("image", "IMAGE"),
|
|
input_slot("image_tail", "IMAGE", None),
|
|
input_slot("custom_voice_id", "STRING", None),
|
|
],
|
|
outputs=[
|
|
output_slot("url", "STRING", slot_index=0),
|
|
output_slot("video_id", "STRING", slot_index=1),
|
|
],
|
|
)
|
|
|
|
|
|
def multi_images_to_video_node(node_id, pos, order):
|
|
type_name = f"{NODE_PREFIX} Multi Images To Video"
|
|
return node(
|
|
node_id,
|
|
type_name,
|
|
pos,
|
|
[430, 360],
|
|
order,
|
|
[
|
|
"kling-v3",
|
|
"Keep the same person identity across the shot while the subject turns toward camera and smiles naturally.",
|
|
"",
|
|
"pro",
|
|
"5",
|
|
"16:9",
|
|
],
|
|
inputs=[
|
|
input_slot("client", CLIENT_TYPE),
|
|
input_slot("image_list", "IMAGE"),
|
|
input_slot("image_tail", "IMAGE", None),
|
|
],
|
|
outputs=[
|
|
output_slot("url", "STRING", slot_index=0),
|
|
output_slot("video_id", "STRING", slot_index=1),
|
|
],
|
|
)
|
|
|
|
|
|
def image_batch_node(node_id, pos, order):
|
|
return node(
|
|
node_id,
|
|
"ImageBatch",
|
|
pos,
|
|
[320, 120],
|
|
order,
|
|
[],
|
|
inputs=[
|
|
input_slot("image1", "IMAGE"),
|
|
input_slot("image2", "IMAGE"),
|
|
],
|
|
outputs=[output_slot("IMAGE", "IMAGE", slot_index=0)],
|
|
)
|
|
|
|
|
|
def virtual_try_on_node(node_id, pos, order):
|
|
type_name = f"{NODE_PREFIX} Virtual Try On"
|
|
return node(
|
|
node_id,
|
|
type_name,
|
|
pos,
|
|
[360, 180],
|
|
order,
|
|
["kolors-virtual-try-on-v1-5"],
|
|
inputs=[
|
|
input_slot("client", CLIENT_TYPE),
|
|
input_slot("human_image", "IMAGE"),
|
|
input_slot("cloth_image", "IMAGE"),
|
|
],
|
|
outputs=[output_slot("image", "IMAGE", slot_index=0)],
|
|
)
|
|
|
|
|
|
def clothing_dupe_node(node_id, pos, order):
|
|
type_name = f"{NODE_PREFIX} Clothing Dupe"
|
|
return node(
|
|
node_id,
|
|
type_name,
|
|
pos,
|
|
[430, 420],
|
|
order,
|
|
[
|
|
"Lightweight breathable shirt for a clean commercial product showcase.",
|
|
"720p",
|
|
"16:9",
|
|
"无",
|
|
],
|
|
inputs=[
|
|
input_slot("source_video", "VIDEO"),
|
|
input_slot("product_image_1", "IMAGE"),
|
|
input_slot("product_image_2", "IMAGE", None),
|
|
input_slot("product_image_3", "IMAGE", None),
|
|
input_slot("product_image_4", "IMAGE", None),
|
|
input_slot("product_image_5", "IMAGE", None),
|
|
input_slot("model_image_1", "IMAGE", None),
|
|
input_slot("model_image_2", "IMAGE", None),
|
|
input_slot("model_image_3", "IMAGE", None),
|
|
input_slot("model_image_4", "IMAGE", None),
|
|
input_slot("model_image_5", "IMAGE", None),
|
|
input_slot("bgm", "AUDIO", None),
|
|
],
|
|
outputs=[
|
|
output_slot("video_url", "STRING", slot_index=0),
|
|
output_slot("watermark_video_url", "STRING", slot_index=1),
|
|
output_slot("task_id", "STRING", slot_index=2),
|
|
output_slot("result_json", "STRING", slot_index=3),
|
|
],
|
|
)
|
|
|
|
|
|
def image_expander_node(node_id, pos, order):
|
|
type_name = f"{NODE_PREFIX} Image Expander"
|
|
return node(
|
|
node_id,
|
|
type_name,
|
|
pos,
|
|
[360, 260],
|
|
order,
|
|
[0.25, 0.0, 0.0, 0.0, "Extend the background naturally with coherent lighting and scene details.", 1],
|
|
inputs=[
|
|
input_slot("client", CLIENT_TYPE),
|
|
input_slot("image", "IMAGE"),
|
|
],
|
|
outputs=[output_slot("image", "IMAGE", slot_index=0)],
|
|
)
|
|
|
|
|
|
def video_extender_node(node_id, pos, order):
|
|
type_name = f"{NODE_PREFIX} Video Extender"
|
|
return node(
|
|
node_id,
|
|
type_name,
|
|
pos,
|
|
[360, 180],
|
|
order,
|
|
["", "Continue the shot naturally for a few more moments, keeping the same scene and motion direction."],
|
|
inputs=[
|
|
input_slot("client", CLIENT_TYPE),
|
|
input_slot("video_id", "STRING"),
|
|
],
|
|
outputs=[
|
|
output_slot("url", "STRING", slot_index=0),
|
|
output_slot("video_id", "STRING", slot_index=1),
|
|
],
|
|
)
|
|
|
|
|
|
def text_to_audio_node(node_id, pos, order):
|
|
type_name = f"{NODE_PREFIX} TextToAudio"
|
|
return node(
|
|
node_id,
|
|
type_name,
|
|
pos,
|
|
[360, 180],
|
|
order,
|
|
["Generate a calm cinematic ambient track with gentle piano and soft pads.", 6.0],
|
|
inputs=[input_slot("client", CLIENT_TYPE)],
|
|
outputs=[
|
|
output_slot("id", "STRING", slot_index=0),
|
|
output_slot("url", "STRING", slot_index=1),
|
|
],
|
|
)
|
|
|
|
|
|
def custom_voice_create_node(node_id, pos, order):
|
|
type_name = f"{NODE_PREFIX} Custom Voice Create"
|
|
return node(
|
|
node_id,
|
|
type_name,
|
|
pos,
|
|
[390, 260],
|
|
order,
|
|
["Demo Voice", "", "", "", "", ""],
|
|
inputs=[input_slot("audio", "AUDIO")],
|
|
outputs=[
|
|
output_slot("voice_id", "STRING", slot_index=0),
|
|
output_slot("voice_name", "STRING", slot_index=1),
|
|
output_slot("trial_url", "STRING", slot_index=2),
|
|
output_slot("task_id", "STRING", slot_index=3),
|
|
output_slot("voice_json", "STRING", slot_index=4),
|
|
],
|
|
)
|
|
|
|
|
|
def tts_node(node_id, pos, order):
|
|
type_name = f"{NODE_PREFIX} TTS"
|
|
return node(
|
|
node_id,
|
|
type_name,
|
|
pos,
|
|
[390, 260],
|
|
order,
|
|
["这是一段用于测试克隆声音效果的短句。", "", "zh", 1.0],
|
|
inputs=[input_slot("voice_id", "STRING")],
|
|
outputs=[
|
|
output_slot("audio_id", "STRING", slot_index=0),
|
|
output_slot("audio_url", "STRING", slot_index=1),
|
|
output_slot("duration", "STRING", slot_index=2),
|
|
output_slot("audio_json", "STRING", slot_index=3),
|
|
],
|
|
)
|
|
|
|
|
|
def video_to_audio_node(node_id, pos, order):
|
|
type_name = f"{NODE_PREFIX} Video2Audio"
|
|
return node(
|
|
node_id,
|
|
type_name,
|
|
pos,
|
|
[390, 220],
|
|
order,
|
|
["", "", "Footsteps on stone floor and distant city ambience.", "Cinematic ambient soundtrack with low strings.", False],
|
|
inputs=[
|
|
input_slot("client", CLIENT_TYPE),
|
|
input_slot("video_id", "STRING"),
|
|
],
|
|
outputs=[
|
|
output_slot("videos_id", "STRING", slot_index=0),
|
|
output_slot("videos_url", "STRING", slot_index=1),
|
|
output_slot("audio_id", "STRING", slot_index=2),
|
|
output_slot("audio_url_mp3", "STRING", slot_index=3),
|
|
],
|
|
)
|
|
|
|
|
|
def lip_sync_text_input_node(node_id, pos, order):
|
|
type_name = f"{NODE_PREFIX} Lip Sync Text Input"
|
|
return node(
|
|
node_id,
|
|
type_name,
|
|
pos,
|
|
[360, 220],
|
|
order,
|
|
["Welcome to Comfyui Kling Wrapper demo. This is a lip sync example generated from text.", "The Reader", "en", 1.0],
|
|
outputs=[output_slot("input", LIPSYNC_INPUT_TYPE, slot_index=0)],
|
|
)
|
|
|
|
|
|
def lip_sync_audio_input_node(node_id, pos, order):
|
|
type_name = f"{NODE_PREFIX} Lip Sync Audio Input"
|
|
return node(
|
|
node_id,
|
|
type_name,
|
|
pos,
|
|
[360, 240],
|
|
order,
|
|
["", "", "", 0, 0],
|
|
inputs=[input_slot("audio", "AUDIO")],
|
|
outputs=[output_slot("input", LIPSYNC_INPUT_TYPE, slot_index=0)],
|
|
)
|
|
|
|
|
|
def lip_sync_node(node_id, pos, order):
|
|
type_name = f"{NODE_PREFIX} Lip Sync"
|
|
return node(
|
|
node_id,
|
|
type_name,
|
|
pos,
|
|
[390, 220],
|
|
order,
|
|
["", "", "", "", 0, 1.0, 1.0],
|
|
inputs=[
|
|
input_slot("client", CLIENT_TYPE),
|
|
input_slot("input", LIPSYNC_INPUT_TYPE),
|
|
input_slot("video_id", "STRING"),
|
|
],
|
|
outputs=[
|
|
output_slot("url", "STRING", slot_index=0),
|
|
output_slot("video_id", "STRING", slot_index=1),
|
|
],
|
|
)
|
|
|
|
|
|
def avatar_node(node_id, pos, order):
|
|
type_name = f"{NODE_PREFIX} Avatar"
|
|
return node(
|
|
node_id,
|
|
type_name,
|
|
pos,
|
|
[420, 320],
|
|
order,
|
|
["std", "", "", "", "Speak naturally with subtle head movement and a friendly expression."],
|
|
inputs=[
|
|
input_slot("image", "IMAGE"),
|
|
input_slot("audio", "AUDIO"),
|
|
],
|
|
outputs=[
|
|
output_slot("url", "STRING", slot_index=0),
|
|
output_slot("video_id", "STRING", slot_index=1),
|
|
],
|
|
)
|
|
|
|
|
|
def effects_node(node_id, pos, order):
|
|
type_name = f"{NODE_PREFIX} Effects"
|
|
return node(
|
|
node_id,
|
|
type_name,
|
|
pos,
|
|
[390, 220],
|
|
order,
|
|
["yearbook", "kling-v1-6", "std", "5"],
|
|
inputs=[
|
|
input_slot("client", CLIENT_TYPE),
|
|
input_slot("image0", "IMAGE"),
|
|
input_slot("image1", "IMAGE", None),
|
|
],
|
|
outputs=[
|
|
output_slot("url", "STRING", slot_index=0),
|
|
output_slot("video_id", "STRING", slot_index=1),
|
|
],
|
|
)
|
|
|
|
|
|
def advanced_element_create_node(node_id, pos, order):
|
|
type_name = f"{NODE_PREFIX} Advanced Element Create"
|
|
return node(
|
|
node_id,
|
|
type_name,
|
|
pos,
|
|
[390, 278],
|
|
order,
|
|
[
|
|
"image_subject",
|
|
"demo_subject",
|
|
"A clear portrait of the subject with consistent identity traits for reuse in video generation.",
|
|
"",
|
|
"",
|
|
"",
|
|
],
|
|
inputs=[
|
|
input_slot("client", CLIENT_TYPE),
|
|
input_slot("image", "IMAGE"),
|
|
input_slot("image_list", "IMAGE", None),
|
|
],
|
|
outputs=[
|
|
output_slot("element", ELEMENT_TYPE, slot_index=0),
|
|
output_slot("element_id", "STRING", slot_index=1),
|
|
output_slot("element_json", "STRING", slot_index=2),
|
|
],
|
|
)
|
|
|
|
|
|
def element_list_builder_node(node_id, pos, order):
|
|
type_name = f"{NODE_PREFIX} Element List Builder"
|
|
return node(
|
|
node_id,
|
|
type_name,
|
|
pos,
|
|
[340, 180],
|
|
order,
|
|
[""],
|
|
inputs=[input_slot("element_1", ELEMENT_TYPE)],
|
|
outputs=[
|
|
output_slot("element_list", ELEMENT_LIST_TYPE, slot_index=0),
|
|
output_slot("element_list_json", "STRING", slot_index=1),
|
|
],
|
|
)
|
|
|
|
|
|
def motion_control_node(node_id, pos, order):
|
|
type_name = f"{NODE_PREFIX} Motion Control"
|
|
return node(
|
|
node_id,
|
|
type_name,
|
|
pos,
|
|
[430, 420],
|
|
order,
|
|
[
|
|
"kling-v2-6",
|
|
"",
|
|
"Transfer the body motion naturally while keeping the subject stable and realistic.",
|
|
"",
|
|
"pro",
|
|
"auto",
|
|
"video",
|
|
True,
|
|
"",
|
|
],
|
|
inputs=[
|
|
input_slot("reference_image", "IMAGE"),
|
|
input_slot("reference_video_input", "VIDEO"),
|
|
],
|
|
outputs=[
|
|
output_slot("url", "STRING", slot_index=0),
|
|
output_slot("video_id", "STRING", slot_index=1),
|
|
],
|
|
)
|
|
|
|
|
|
def multimodal_video_edit_node(node_id, pos, order):
|
|
type_name = f"{NODE_PREFIX} Multi Modal Video Edit"
|
|
return node(
|
|
node_id,
|
|
type_name,
|
|
pos,
|
|
[470, 460],
|
|
order,
|
|
[
|
|
"kling-v3-omni",
|
|
"Replace the subject's coat with the garment shown in the reference image while preserving motion and camera movement.",
|
|
"https://example.com/base-video.mp4",
|
|
"",
|
|
"base",
|
|
False,
|
|
"std",
|
|
"3",
|
|
"16:9",
|
|
"",
|
|
],
|
|
inputs=[input_slot("image_list", "IMAGE", None)],
|
|
outputs=[
|
|
output_slot("url", "STRING", slot_index=0),
|
|
output_slot("video_id", "STRING", slot_index=1),
|
|
],
|
|
)
|
|
|
|
|
|
def link(link_id, src_node, src_slot, dst_node, dst_slot, type_name):
|
|
return [link_id, src_node, src_slot, dst_node, dst_slot, type_name]
|
|
|
|
|
|
def write_json(name, data):
|
|
(ROOT / name).write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
|
|
|
|
def write_examples_readme():
|
|
content = """# Comfyui-Kling-Wrapper Examples
|
|
|
|
This directory contains importable ComfyUI workflow JSON examples and small Python API snippets for `Comfyui-Kling-Wrapper`.
|
|
|
|
## Before running the workflows
|
|
|
|
- Create and fill in `config.local.json` in the repository root before importing the workflows.
|
|
- You can copy `config.example.json` in the repository root as a starting template.
|
|
- The provider is configured globally in `config.local.json` and is intentionally not exposed as a node widget. Use `"provider_default": "official"` for Kling or `"provider_default": "tencent_vod"` for Tencent VOD.
|
|
- For Tencent VOD workflows, set `provider_default` to `tencent_vod` and configure `tencent_secret_id`, `tencent_secret_key`, and `tencent_sub_app_id`, or use the `TENCENTCLOUD_SECRET_ID` and `TENCENTCLOUD_SECRET_KEY` environment variables.
|
|
- Replace placeholder filenames such as `example_portrait.png`, `example_subject_front.png`, `example_subject_ref_1.png`, `example_cloth.png`, `example_scene.png`, `example_lipsync_source.mp4`, `example_lipsync_audio.wav`, and `example_voice_reference.mp3` with files that exist in your ComfyUI input directory.
|
|
- Replace placeholder URLs such as `https://example.com/reference-motion.mp4` with real URLs when needed.
|
|
- Native preset and cloned voices are available for `kling-v2-6`; the active API rejects `voice_list` for `kling-v3`.
|
|
- `kling-v3-omni` is visible in the video generation dropdowns for comparison with `kling-v3`; `kling-video-o1` remains hidden until live endpoint support is confirmed.
|
|
|
|
## Important workflow notes
|
|
|
|
- `05_comfyui_kling_wrapper_text2video_v26_sound.json` is the native audio example and is built around `kling-v2-6`.
|
|
- `06_comfyui_kling_wrapper_advanced_element_subject_to_image2video.json` requires 1 frontal portrait plus 1-3 additional photos of the same subject. Background or scene images do not count as advanced-element reference images.
|
|
- If you need both subject binding and speech, generate the bound video first and then add speech with the lip-sync or audio nodes.
|
|
- `kling-v3-omni` can combine `custom_voice_id` or `custom_voice_list` with `element_list`; older models still keep stricter local guards.
|
|
- `Motion Control` accepts either a direct http(s) reference-video URL or uploaded/local video workflow inputs. For local media, use a video loader node that provides either `VIDEO` or `reference_video_frames` plus `reference_video_info`. Set `duration` to `auto` if you want it to match the uploaded motion reference length.
|
|
- `Lip Sync` now calls `identify-face` and `advanced-lip-sync`; leave `face_id` empty to use the first detected face.
|
|
- `Avatar` accepts one and only one of `AUDIO`, `audio_id`, `audio_file`, or `audio_url`.
|
|
- `Image2Video.custom_voice_id` is the single-custom-voice path. For multiple voices, connect `Custom Voice List Builder.custom_voice_list` to `Image2Video.custom_voice_list`, set `sound=on`, and use ordered markers such as `<<<voice_1>>>` and `<<<voice_2>>>`.
|
|
|
|
## Included files
|
|
|
|
- `01_comfyui_kling_wrapper_image_generation_v3.json`: basic image generation with a verified `kling-v3` preset.
|
|
- `02_comfyui_kling_wrapper_text2video_v3_intelligence.json`: text-to-video with `kling-v3` intelligent multi-shot mode.
|
|
- `03_comfyui_kling_wrapper_image2video_v3.json`: single-image to video using the visible 3.0-era model set.
|
|
- `04_comfyui_kling_wrapper_multi_images_to_video_v3.json`: multi-image identity-consistent video generation.
|
|
- `05_comfyui_kling_wrapper_text2video_v26_sound.json`: `kling-v2-6` talking-head example with native voice presets.
|
|
- `06_comfyui_kling_wrapper_advanced_element_subject_to_image2video.json`: advanced custom element creation plus `element_list` binding in image-to-video.
|
|
- `07_comfyui_kling_wrapper_motion_control_v26.json`: motion-control workflow centered on a reference image plus uploaded reference-video inputs.
|
|
- `08_comfyui_kling_wrapper_virtual_try_on.json`: virtual try-on image workflow.
|
|
- `09_comfyui_kling_wrapper_image_expand.json`: image expansion workflow.
|
|
- `10_comfyui_kling_wrapper_video_extend_chain.json`: clip generation followed by video extension.
|
|
- `11_comfyui_kling_wrapper_text_to_audio.json`: text-to-audio workflow.
|
|
- `12_comfyui_kling_wrapper_video_to_audio.json`: video-to-audio workflow.
|
|
- `13_comfyui_kling_wrapper_lip_sync_from_text.json`: lip-sync workflow driven by text input.
|
|
- `14_comfyui_kling_wrapper_effects_single_image.json`: single-image effects workflow.
|
|
- `15_comfyui_kling_wrapper_advanced_element_subject_with_video2audio_music.json`: advanced subject binding followed by `Video2Audio` to add generated music/ambience to the result.
|
|
- `16_comfyui_kling_wrapper_video_character_to_image2video.json`: create a `video_character` element from a public `video_url`, then bind that element into `Image2Video`.
|
|
- `17_comfyui_kling_wrapper_lip_sync_from_uploaded_audio_video.json`: advanced lip-sync workflow using direct `LoadAudio` and `LoadVideo` uploads.
|
|
- `18_comfyui_kling_wrapper_custom_voice_tts_preview.json`: create a custom voice and preview its returned `trial_url`.
|
|
- `19_comfyui_kling_wrapper_avatar_from_uploaded_audio_image.json`: create an Avatar video from one image and uploaded audio.
|
|
- `20_comfyui_kling_wrapper_custom_voice_image2video.json`: clone a voice and connect its `voice_id` to Kling v2.6 image-to-video.
|
|
- `21_tencent_vod_text2video.json`: minimal Tencent VOD text-to-video workflow.
|
|
- `22_tencent_vod_image2video.json`: minimal Tencent VOD image-to-video workflow.
|
|
- `23_tencent_vod_multi_images_to_video.json`: Tencent VOD multi-image reference generation.
|
|
- `24_tencent_vod_motion_control.json`: Tencent VOD motion-control workflow.
|
|
- `25_tencent_vod_multimodal_video_edit.json`: Tencent VOD 3.0-Omni video editing with an image reference.
|
|
- `26_tencent_vod_advanced_element_create.json`: Tencent VOD advanced subject creation.
|
|
- `27_tencent_vod_custom_voice_create.json`: Tencent VOD custom voice creation and trial preview.
|
|
- `28_comfyui_kling_wrapper_clothing_dupe.json`: official Kling Clothing Dupe workflow using uploaded video, product/model images, and BGM audio.
|
|
- `api_examples.py`: small Python snippets that mirror the same upgraded API wrappers.
|
|
"""
|
|
(ROOT / "README.md").write_text(content, encoding="utf-8")
|
|
|
|
|
|
def write_api_examples():
|
|
content = '''import base64
|
|
import os
|
|
from pathlib import Path
|
|
|
|
from py.api import (
|
|
AdvancedLipSync,
|
|
AdvancedCustomElements,
|
|
Avatar,
|
|
Client,
|
|
CustomVoiceCreate,
|
|
FaceIdentify,
|
|
Image2Video,
|
|
ImageGenerator,
|
|
MotionControl,
|
|
MultiImages2Video,
|
|
TTS,
|
|
Text2Video,
|
|
)
|
|
|
|
|
|
def image_to_base64(path: str) -> str:
|
|
return base64.b64encode(Path(path).read_bytes()).decode("utf-8")
|
|
|
|
|
|
def build_client(in_china: bool = True) -> Client:
|
|
access_key = os.environ["KLING_ACCESS_KEY"]
|
|
secret_key = os.environ["KLING_SECRET_KEY"]
|
|
return Client(access_key, secret_key, in_china=in_china, timeout=30, poll_interval=2.0)
|
|
|
|
|
|
def example_image_generation(client: Client):
|
|
task = ImageGenerator()
|
|
task.model_name = "kling-v3"
|
|
task.prompt = "A cinematic editorial portrait with elegant framing and realistic skin texture."
|
|
task.aspect_ratio = "16:9"
|
|
task.resolution = "1k"
|
|
return task.run(client)
|
|
|
|
|
|
def example_text2video_v3(client: Client):
|
|
task = Text2Video()
|
|
task.model_name = "kling-v3"
|
|
task.prompt = "A traveler walks through an old alley at dusk with cinematic multi-shot storytelling."
|
|
task.mode = "pro"
|
|
task.aspect_ratio = "16:9"
|
|
task.duration = "5"
|
|
task.shot_type = "intelligence"
|
|
return task.run(client)
|
|
|
|
|
|
def example_text2video_v26_sound(client: Client):
|
|
task = Text2Video()
|
|
task.model_name = "kling-v2-6"
|
|
task.prompt = "A presenter speaks directly to camera in a clean studio."
|
|
task.mode = "pro"
|
|
task.aspect_ratio = "16:9"
|
|
task.duration = "5"
|
|
task.sound = "on"
|
|
task.voice_list = [{"voice_id": "reader_en_m-v1"}]
|
|
return task.run(client)
|
|
|
|
|
|
def example_create_advanced_element(client: Client, frontal_image_url: str, reference_image_urls: list[str]):
|
|
if not 1 <= len(reference_image_urls) <= 3:
|
|
raise ValueError("Provide 1-3 additional reference image URLs for the same subject.")
|
|
task = AdvancedCustomElements()
|
|
task.element_name = "demo_subject"
|
|
task.element_description = "A clear portrait of the subject with consistent identity traits for reuse in video generation."
|
|
task.reference_type = "image_refer"
|
|
task.element_image_list = {
|
|
"frontal_image": frontal_image_url,
|
|
"refer_images": [{"image_url": url} for url in reference_image_urls],
|
|
}
|
|
return task.run(client)
|
|
|
|
|
|
def example_image2video_with_element(client: Client, image_path: str, element_id: str):
|
|
task = Image2Video()
|
|
task.model_name = "kling-v3"
|
|
task.image = image_to_base64(image_path)
|
|
task.prompt = "Keep the same subject identity while the camera slowly pushes in."
|
|
task.mode = "pro"
|
|
task.duration = "5"
|
|
task.element_list = [{"element_id": element_id}]
|
|
return task.run(client)
|
|
|
|
|
|
def example_multi_images_to_video(client: Client, image_paths: list[str]):
|
|
if len(image_paths) < 2:
|
|
raise ValueError("Provide at least two image paths.")
|
|
task = MultiImages2Video()
|
|
task.model_name = "kling-v3"
|
|
encoded = [image_to_base64(path) for path in image_paths]
|
|
task.image = encoded[0]
|
|
task.image_list = encoded[1:]
|
|
task.prompt = "Maintain the same identity across the shot with natural motion."
|
|
task.mode = "pro"
|
|
task.duration = "5"
|
|
task.aspect_ratio = "16:9"
|
|
return task.run(client)
|
|
|
|
|
|
def example_motion_control(client: Client, image_path: str, reference_video_url: str):
|
|
task = MotionControl()
|
|
task.model_name = "kling-v2-6"
|
|
task.reference_image = image_to_base64(image_path)
|
|
task.reference_video = reference_video_url
|
|
task.prompt = "Transfer the motion naturally while keeping the subject stable and realistic."
|
|
task.mode = "pro"
|
|
task.duration = "5"
|
|
return task.run(client)
|
|
|
|
|
|
def example_create_custom_voice(client: Client, voice_url: str):
|
|
task = CustomVoiceCreate()
|
|
task.voice_name = "Demo Voice"
|
|
task.voice_url = voice_url
|
|
return task.run(client)
|
|
|
|
|
|
def example_avatar(client: Client, image_path: str, audio_url: str):
|
|
task = Avatar()
|
|
task.image = image_to_base64(image_path)
|
|
task.sound_file = audio_url
|
|
task.prompt = "Speak naturally with subtle head movement and a friendly expression."
|
|
task.mode = "std"
|
|
return task.run(client)
|
|
|
|
|
|
def example_advanced_lip_sync(client: Client, video_url: str, audio_url: str, audio_duration_ms: int):
|
|
identify = FaceIdentify()
|
|
identify.video_url = video_url
|
|
face_result = identify.run(client)
|
|
if not face_result.face_data:
|
|
raise ValueError("No face was detected in the source video.")
|
|
|
|
task = AdvancedLipSync()
|
|
task.session_id = face_result.session_id
|
|
task.face_choose = [{
|
|
"face_id": face_result.face_data[0].face_id,
|
|
"sound_file": audio_url,
|
|
"sound_start_time": 0,
|
|
"sound_end_time": audio_duration_ms,
|
|
"sound_insert_time": 0,
|
|
"sound_volume": 1.0,
|
|
"original_audio_volume": 1.0,
|
|
}]
|
|
return task.run(client)
|
|
|
|
|
|
def example_image2video_with_custom_voice(
|
|
client: Client,
|
|
image_path: str,
|
|
voice_id: str,
|
|
dialogue: str,
|
|
):
|
|
task = Image2Video()
|
|
task.model_name = "kling-v2-6"
|
|
task.image = image_to_base64(image_path)
|
|
task.prompt = f'Portrait subject <<<voice_1>>> says: "{dialogue}"'
|
|
task.mode = "pro"
|
|
task.duration = "10"
|
|
task.sound = "on"
|
|
task.voice_list = [{"voice_id": voice_id}]
|
|
return task.run(client)
|
|
|
|
|
|
def example_tts_with_voice_id(client: Client, voice_id: str):
|
|
task = TTS()
|
|
task.text = "这是一段用于测试克隆声音效果的短句。"
|
|
task.voice_id = voice_id
|
|
task.voice_language = "zh"
|
|
task.voice_speed = 1.0
|
|
return task.run(client)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
print("Set KLING_ACCESS_KEY and KLING_SECRET_KEY before running these examples.")
|
|
print("Import this file and call the helper that matches the workflow you want to test.")
|
|
'''
|
|
(ROOT / "api_examples.py").write_text(content, encoding="utf-8")
|
|
|
|
|
|
def generate_workflows():
|
|
examples = {}
|
|
|
|
nodes = [
|
|
client_node(1, 0),
|
|
image_generator_node(2, [430, 40], 1),
|
|
save_image_node(3, "Comfyui-Kling-Wrapper-image-generation", [900, 60], 2, 2),
|
|
]
|
|
links = [
|
|
link(1, 1, 0, 2, 0, CLIENT_TYPE),
|
|
link(2, 2, 0, 3, 0, "IMAGE"),
|
|
]
|
|
nodes[1]["inputs"][0]["link"] = 1
|
|
nodes[1]["outputs"][0]["links"] = [2]
|
|
examples["01_comfyui_kling_wrapper_image_generation_v3.json"] = workflow(nodes, links)
|
|
|
|
nodes = [
|
|
client_node(1, 0),
|
|
text2video_node(
|
|
2,
|
|
[430, 20],
|
|
1,
|
|
"kling-v3",
|
|
"A traveler moves through an old alley at dusk, cinematic atmosphere, natural motion, evolving composition with coherent multi-shot storytelling.",
|
|
mode="pro",
|
|
shot_type="intelligence",
|
|
),
|
|
preview_video_node(3, [910, 20], 2, 2),
|
|
]
|
|
links = [
|
|
link(1, 1, 0, 2, 0, CLIENT_TYPE),
|
|
link(2, 2, 0, 3, 0, "STRING"),
|
|
]
|
|
nodes[1]["inputs"][0]["link"] = 1
|
|
nodes[1]["outputs"][0]["links"] = [2]
|
|
examples["02_comfyui_kling_wrapper_text2video_v3_intelligence.json"] = workflow(nodes, links)
|
|
|
|
nodes = [
|
|
client_node(1, 0),
|
|
load_image_node(2, "example_portrait.png", [60, 340], 1),
|
|
image2video_node(
|
|
3,
|
|
[430, 200],
|
|
2,
|
|
"kling-v3",
|
|
"A cinematic close-up portrait. The subject breathes naturally, hair and clothing move subtly in the wind, and the camera slowly pushes in.",
|
|
mode="pro",
|
|
),
|
|
preview_video_node(4, [920, 200], 3, 3),
|
|
]
|
|
links = [
|
|
link(1, 1, 0, 3, 0, CLIENT_TYPE),
|
|
link(2, 2, 0, 3, 1, "IMAGE"),
|
|
link(3, 3, 0, 4, 0, "STRING"),
|
|
]
|
|
nodes[2]["inputs"][0]["link"] = 1
|
|
nodes[2]["inputs"][1]["link"] = 2
|
|
nodes[2]["outputs"][0]["links"] = [3]
|
|
examples["03_comfyui_kling_wrapper_image2video_v3.json"] = workflow(nodes, links)
|
|
|
|
nodes = [
|
|
client_node(1, 0),
|
|
load_image_node(2, "example_subject_a.png", [40, 330], 1),
|
|
load_image_node(3, "example_subject_b.png", [40, 700], 2),
|
|
image_batch_node(4, [370, 510], 3),
|
|
multi_images_to_video_node(5, [700, 420], 4),
|
|
preview_video_node(6, [1180, 420], 5, 5),
|
|
]
|
|
links = [
|
|
link(1, 1, 0, 5, 0, CLIENT_TYPE),
|
|
link(2, 2, 0, 4, 0, "IMAGE"),
|
|
link(3, 3, 0, 4, 1, "IMAGE"),
|
|
link(4, 4, 0, 5, 1, "IMAGE"),
|
|
link(5, 5, 0, 6, 0, "STRING"),
|
|
]
|
|
nodes[3]["inputs"][0]["link"] = 2
|
|
nodes[3]["inputs"][1]["link"] = 3
|
|
nodes[4]["inputs"][0]["link"] = 1
|
|
nodes[4]["inputs"][1]["link"] = 4
|
|
nodes[4]["outputs"][0]["links"] = [5]
|
|
examples["04_comfyui_kling_wrapper_multi_images_to_video_v3.json"] = workflow(nodes, links)
|
|
|
|
nodes = [
|
|
client_node(1, 0),
|
|
text2video_node(
|
|
2,
|
|
[430, 20],
|
|
1,
|
|
"kling-v2-6",
|
|
"A presenter speaks directly to camera in a clean studio, natural gestures, subtle head motion, clear talking-head composition.",
|
|
mode="pro",
|
|
sound="on",
|
|
voice_preset="The Reader | reader_en_m-v1",
|
|
),
|
|
preview_video_node(3, [910, 20], 2, 2),
|
|
]
|
|
links = [
|
|
link(1, 1, 0, 2, 0, CLIENT_TYPE),
|
|
link(2, 2, 0, 3, 0, "STRING"),
|
|
]
|
|
nodes[1]["inputs"][0]["link"] = 1
|
|
nodes[1]["outputs"][0]["links"] = [2]
|
|
examples["05_comfyui_kling_wrapper_text2video_v26_sound.json"] = workflow(nodes, links)
|
|
|
|
nodes = [
|
|
client_node(1, 0),
|
|
load_image_node(2, "example_subject_front.png", [30, 100], 1),
|
|
load_image_node(3, "example_subject_ref_1.png", [30, 470], 2),
|
|
load_image_node(4, "example_subject_ref_2.png", [30, 840], 3),
|
|
image_batch_node(5, [360, 640], 4),
|
|
load_image_node(6, "example_scene.png", [360, 980], 5),
|
|
advanced_element_create_node(7, [720, 180], 6),
|
|
element_list_builder_node(8, [1160, 180], 7),
|
|
image2video_node(
|
|
9,
|
|
[1160, 500],
|
|
8,
|
|
"kling-v3",
|
|
"Keep the same subject identity while the camera slowly moves and the background remains coherent.",
|
|
mode="pro",
|
|
),
|
|
preview_video_node(10, [1640, 500], 9, 10),
|
|
]
|
|
links = [
|
|
link(1, 1, 0, 7, 0, CLIENT_TYPE),
|
|
link(2, 2, 0, 7, 1, "IMAGE"),
|
|
link(3, 3, 0, 5, 0, "IMAGE"),
|
|
link(4, 4, 0, 5, 1, "IMAGE"),
|
|
link(5, 5, 0, 7, 2, "IMAGE"),
|
|
link(6, 7, 0, 8, 0, ELEMENT_TYPE),
|
|
link(7, 1, 0, 9, 0, CLIENT_TYPE),
|
|
link(8, 6, 0, 9, 1, "IMAGE"),
|
|
link(9, 8, 0, 9, 4, ELEMENT_LIST_TYPE),
|
|
link(10, 9, 0, 10, 0, "STRING"),
|
|
]
|
|
nodes[6]["inputs"][0]["link"] = 1
|
|
nodes[6]["inputs"][1]["link"] = 2
|
|
nodes[6]["inputs"][2]["link"] = 5
|
|
nodes[4]["inputs"][0]["link"] = 3
|
|
nodes[4]["inputs"][1]["link"] = 4
|
|
nodes[7]["inputs"][0]["link"] = 6
|
|
nodes[8]["inputs"][0]["link"] = 7
|
|
nodes[8]["inputs"][1]["link"] = 8
|
|
nodes[8]["inputs"].append(input_slot("element_list", ELEMENT_LIST_TYPE, 9))
|
|
nodes[8]["outputs"][0]["links"] = [10]
|
|
examples["06_comfyui_kling_wrapper_advanced_element_subject_to_image2video.json"] = workflow(nodes, links)
|
|
|
|
nodes = [
|
|
load_image_node(2, "example_motion_subject.png", [40, 260], 1),
|
|
load_video_node(3, "example_motion_reference.mp4", [40, 670], 2),
|
|
motion_control_node(4, [460, 220], 3),
|
|
preview_video_node(5, [980, 220], 4, 3),
|
|
]
|
|
links = [
|
|
link(1, 2, 0, 4, 0, "IMAGE"),
|
|
link(2, 3, 0, 4, 1, "VIDEO"),
|
|
link(3, 4, 0, 5, 0, "STRING"),
|
|
]
|
|
nodes[0]["outputs"][0]["links"] = [1]
|
|
nodes[1]["outputs"][0]["links"] = [2]
|
|
nodes[2]["inputs"][0]["link"] = 1
|
|
nodes[2]["inputs"][1]["link"] = 2
|
|
nodes[2]["outputs"][0]["links"] = [3]
|
|
examples["07_comfyui_kling_wrapper_motion_control_v26.json"] = workflow(nodes, links)
|
|
|
|
nodes = [
|
|
client_node(1, 0),
|
|
load_image_node(2, "example_model.png", [40, 150], 1),
|
|
load_image_node(3, "example_cloth.png", [40, 520], 2),
|
|
virtual_try_on_node(4, [420, 310], 3),
|
|
save_image_node(5, "Comfyui-Kling-Wrapper-virtual-try-on", [860, 320], 4, 4),
|
|
]
|
|
links = [
|
|
link(1, 1, 0, 4, 0, CLIENT_TYPE),
|
|
link(2, 2, 0, 4, 1, "IMAGE"),
|
|
link(3, 3, 0, 4, 2, "IMAGE"),
|
|
link(4, 4, 0, 5, 0, "IMAGE"),
|
|
]
|
|
nodes[3]["inputs"][0]["link"] = 1
|
|
nodes[3]["inputs"][1]["link"] = 2
|
|
nodes[3]["inputs"][2]["link"] = 3
|
|
nodes[3]["outputs"][0]["links"] = [4]
|
|
examples["08_comfyui_kling_wrapper_virtual_try_on.json"] = workflow(nodes, links)
|
|
|
|
nodes = [
|
|
client_node(1, 0),
|
|
load_image_node(2, "example_expand.png", [40, 220], 1),
|
|
image_expander_node(3, [430, 180], 2),
|
|
save_image_node(4, "Comfyui-Kling-Wrapper-image-expand", [860, 190], 3, 3),
|
|
]
|
|
links = [
|
|
link(1, 1, 0, 3, 0, CLIENT_TYPE),
|
|
link(2, 2, 0, 3, 1, "IMAGE"),
|
|
link(3, 3, 0, 4, 0, "IMAGE"),
|
|
]
|
|
nodes[2]["inputs"][0]["link"] = 1
|
|
nodes[2]["inputs"][1]["link"] = 2
|
|
nodes[2]["outputs"][0]["links"] = [3]
|
|
examples["09_comfyui_kling_wrapper_image_expand.json"] = workflow(nodes, links)
|
|
|
|
nodes = [
|
|
client_node(1, 0),
|
|
text2video_node(
|
|
2,
|
|
[420, 20],
|
|
1,
|
|
"kling-v3",
|
|
"A quiet street scene at sunrise with subtle camera drift and natural environmental motion.",
|
|
mode="pro",
|
|
),
|
|
video_extender_node(3, [870, 40], 2),
|
|
preview_video_node(4, [1290, 40], 3, 4),
|
|
]
|
|
links = [
|
|
link(1, 1, 0, 2, 0, CLIENT_TYPE),
|
|
link(2, 1, 0, 3, 0, CLIENT_TYPE),
|
|
link(3, 2, 1, 3, 1, "STRING"),
|
|
link(4, 3, 0, 4, 0, "STRING"),
|
|
]
|
|
nodes[1]["inputs"][0]["link"] = 1
|
|
nodes[2]["inputs"][0]["link"] = 2
|
|
nodes[2]["inputs"][1]["link"] = 3
|
|
nodes[2]["outputs"][0]["links"] = [4]
|
|
examples["10_comfyui_kling_wrapper_video_extend_chain.json"] = workflow(nodes, links)
|
|
|
|
nodes = [
|
|
client_node(1, 0),
|
|
text_to_audio_node(2, [430, 80], 1),
|
|
preview_audio_node(3, [870, 80], 2, 2),
|
|
]
|
|
links = [
|
|
link(1, 1, 0, 2, 0, CLIENT_TYPE),
|
|
link(2, 2, 1, 3, 0, "STRING"),
|
|
]
|
|
nodes[1]["inputs"][0]["link"] = 1
|
|
nodes[1]["outputs"][1]["links"] = [2]
|
|
examples["11_comfyui_kling_wrapper_text_to_audio.json"] = workflow(nodes, links)
|
|
|
|
nodes = [
|
|
client_node(1, 0),
|
|
text2video_node(
|
|
2,
|
|
[420, 20],
|
|
1,
|
|
"kling-v3",
|
|
"A person walks through an empty hall with slow cinematic movement.",
|
|
mode="pro",
|
|
),
|
|
video_to_audio_node(3, [880, 30], 2),
|
|
preview_audio_node(4, [1330, 30], 3, 4),
|
|
]
|
|
links = [
|
|
link(1, 1, 0, 2, 0, CLIENT_TYPE),
|
|
link(2, 1, 0, 3, 0, CLIENT_TYPE),
|
|
link(3, 2, 1, 3, 1, "STRING"),
|
|
link(4, 3, 3, 4, 0, "STRING"),
|
|
]
|
|
nodes[1]["inputs"][0]["link"] = 1
|
|
nodes[2]["inputs"][0]["link"] = 2
|
|
nodes[2]["inputs"][1]["link"] = 3
|
|
nodes[2]["outputs"][3]["links"] = [4]
|
|
examples["12_comfyui_kling_wrapper_video_to_audio.json"] = workflow(nodes, links)
|
|
|
|
nodes = [
|
|
client_node(1, 0),
|
|
text2video_node(
|
|
2,
|
|
[420, 20],
|
|
1,
|
|
"kling-v3",
|
|
"A portrait subject looks at camera with subtle idle motion.",
|
|
mode="pro",
|
|
),
|
|
lip_sync_text_input_node(3, [420, 420], 2),
|
|
lip_sync_node(4, [880, 240], 3),
|
|
preview_video_node(5, [1320, 240], 4, 5),
|
|
]
|
|
links = [
|
|
link(1, 1, 0, 2, 0, CLIENT_TYPE),
|
|
link(2, 1, 0, 4, 0, CLIENT_TYPE),
|
|
link(3, 3, 0, 4, 1, LIPSYNC_INPUT_TYPE),
|
|
link(4, 2, 1, 4, 2, "STRING"),
|
|
link(5, 4, 0, 5, 0, "STRING"),
|
|
]
|
|
nodes[1]["inputs"][0]["link"] = 1
|
|
nodes[3]["inputs"][0]["link"] = 2
|
|
nodes[3]["inputs"][1]["link"] = 3
|
|
nodes[3]["inputs"][2]["link"] = 4
|
|
nodes[3]["outputs"][0]["links"] = [5]
|
|
examples["13_comfyui_kling_wrapper_lip_sync_from_text.json"] = workflow(nodes, links)
|
|
|
|
nodes = [
|
|
client_node(1, 0),
|
|
load_image_node(2, "example_effect_input.png", [40, 220], 1),
|
|
effects_node(3, [430, 180], 2),
|
|
preview_video_node(4, [900, 180], 3, 3),
|
|
]
|
|
links = [
|
|
link(1, 1, 0, 3, 0, CLIENT_TYPE),
|
|
link(2, 2, 0, 3, 1, "IMAGE"),
|
|
link(3, 3, 0, 4, 0, "STRING"),
|
|
]
|
|
nodes[2]["inputs"][0]["link"] = 1
|
|
nodes[2]["inputs"][1]["link"] = 2
|
|
nodes[2]["outputs"][0]["links"] = [3]
|
|
examples["14_comfyui_kling_wrapper_effects_single_image.json"] = workflow(nodes, links)
|
|
|
|
nodes = [
|
|
load_audio_node(1, "example_lipsync_audio.wav", [40, 40], 0),
|
|
load_video_node(2, "example_lipsync_source.mp4", [40, 300], 1),
|
|
lip_sync_audio_input_node(3, [420, 40], 2),
|
|
lip_sync_node(4, [860, 180], 3),
|
|
preview_video_node(5, [1340, 180], 4, 4),
|
|
]
|
|
nodes[3]["inputs"] = nodes[3]["inputs"][:2] + [input_slot("video_input", "VIDEO")]
|
|
links = [
|
|
link(1, 1, 0, 3, 0, "AUDIO"),
|
|
link(2, 3, 0, 4, 1, LIPSYNC_INPUT_TYPE),
|
|
link(3, 2, 0, 4, 2, "VIDEO"),
|
|
link(4, 4, 0, 5, 0, "STRING"),
|
|
]
|
|
nodes[0]["outputs"][0]["links"] = [1]
|
|
nodes[1]["outputs"][0]["links"] = [3]
|
|
nodes[2]["inputs"][0]["link"] = 1
|
|
nodes[2]["outputs"][0]["links"] = [2]
|
|
nodes[3]["inputs"][1]["link"] = 2
|
|
nodes[3]["inputs"][2]["link"] = 3
|
|
nodes[3]["outputs"][0]["links"] = [4]
|
|
examples["17_comfyui_kling_wrapper_lip_sync_from_uploaded_audio_video.json"] = workflow(nodes, links)
|
|
|
|
nodes = [
|
|
load_audio_node(1, "example_voice_reference.mp3", [40, 80], 0),
|
|
custom_voice_create_node(2, [420, 80], 1),
|
|
preview_audio_node(3, [880, 120], 2, 2),
|
|
]
|
|
links = [
|
|
link(1, 1, 0, 2, 0, "AUDIO"),
|
|
link(2, 2, 2, 3, 0, "STRING"),
|
|
]
|
|
nodes[0]["outputs"][0]["links"] = [1]
|
|
nodes[1]["inputs"][0]["link"] = 1
|
|
nodes[1]["outputs"][2]["links"] = [2]
|
|
nodes[2]["inputs"][0]["link"] = 2
|
|
examples["18_comfyui_kling_wrapper_custom_voice_tts_preview.json"] = workflow(nodes, links)
|
|
|
|
nodes = [
|
|
load_image_node(1, "example_avatar_portrait.png", [40, 80], 0),
|
|
load_audio_node(2, "example_avatar_speech.wav", [40, 480], 1),
|
|
avatar_node(3, [460, 200], 2),
|
|
preview_video_node(4, [980, 200], 3, 3),
|
|
]
|
|
links = [
|
|
link(1, 1, 0, 3, 0, "IMAGE"),
|
|
link(2, 2, 0, 3, 1, "AUDIO"),
|
|
link(3, 3, 0, 4, 0, "STRING"),
|
|
]
|
|
nodes[0]["outputs"][0]["links"] = [1]
|
|
nodes[1]["outputs"][0]["links"] = [2]
|
|
nodes[2]["inputs"][0]["link"] = 1
|
|
nodes[2]["inputs"][1]["link"] = 2
|
|
nodes[2]["outputs"][0]["links"] = [3]
|
|
examples["19_comfyui_kling_wrapper_avatar_from_uploaded_audio_image.json"] = workflow(nodes, links)
|
|
|
|
nodes = [
|
|
load_audio_node(1, "example_voice_reference.mp3", [40, 40], 0),
|
|
load_image_node(2, "example_talking_portrait.png", [40, 360], 1),
|
|
custom_voice_create_node(3, [420, 40], 2),
|
|
image2video_node(
|
|
4,
|
|
[860, 260],
|
|
3,
|
|
"kling-v2-6",
|
|
'The portrait subject <<<voice_1>>> says: "Welcome to this custom voice demonstration."',
|
|
mode="pro",
|
|
duration="10",
|
|
sound="on",
|
|
),
|
|
preview_video_node(5, [1380, 260], 4, 4),
|
|
]
|
|
links = [
|
|
link(1, 1, 0, 3, 0, "AUDIO"),
|
|
link(2, 2, 0, 4, 1, "IMAGE"),
|
|
link(3, 3, 0, 4, 3, "STRING"),
|
|
link(4, 4, 0, 5, 0, "STRING"),
|
|
]
|
|
nodes[0]["outputs"][0]["links"] = [1]
|
|
nodes[1]["outputs"][0]["links"] = [2]
|
|
nodes[2]["inputs"][0]["link"] = 1
|
|
nodes[2]["outputs"][0]["links"] = [3]
|
|
nodes[3]["inputs"][1]["link"] = 2
|
|
nodes[3]["inputs"][3]["link"] = 3
|
|
nodes[3]["outputs"][0]["links"] = [4]
|
|
examples["20_comfyui_kling_wrapper_custom_voice_image2video.json"] = workflow(nodes, links)
|
|
|
|
nodes = [
|
|
text2video_node(
|
|
1,
|
|
[60, 60],
|
|
0,
|
|
"kling-v3",
|
|
"A red fox runs through fresh snow, cinematic natural motion.",
|
|
mode="std",
|
|
duration="3",
|
|
),
|
|
preview_video_node(2, [580, 60], 1, 1),
|
|
]
|
|
links = [link(1, 1, 0, 2, 0, "STRING")]
|
|
nodes[0]["outputs"][0]["links"] = [1]
|
|
examples["21_tencent_vod_text2video.json"] = workflow(nodes, links)
|
|
|
|
nodes = [
|
|
load_image_node(1, "example_portrait.png", [40, 60], 0),
|
|
image2video_node(
|
|
2,
|
|
[390, 60],
|
|
1,
|
|
"kling-v3",
|
|
"The subject turns toward camera while the clothing moves naturally.",
|
|
mode="std",
|
|
duration="3",
|
|
),
|
|
preview_video_node(3, [900, 60], 2, 2),
|
|
]
|
|
links = [
|
|
link(1, 1, 0, 2, 1, "IMAGE"),
|
|
link(2, 2, 0, 3, 0, "STRING"),
|
|
]
|
|
nodes[0]["outputs"][0]["links"] = [1]
|
|
nodes[1]["inputs"][1]["link"] = 1
|
|
nodes[1]["outputs"][0]["links"] = [2]
|
|
examples["22_tencent_vod_image2video.json"] = workflow(nodes, links)
|
|
|
|
nodes = [
|
|
load_image_node(1, "example_subject_a.png", [40, 40], 0),
|
|
load_image_node(2, "example_subject_b.png", [40, 420], 1),
|
|
image_batch_node(3, [390, 230], 2),
|
|
multi_images_to_video_node(4, [730, 160], 3),
|
|
preview_video_node(5, [1220, 160], 4, 4),
|
|
]
|
|
links = [
|
|
link(1, 1, 0, 3, 0, "IMAGE"),
|
|
link(2, 2, 0, 3, 1, "IMAGE"),
|
|
link(3, 3, 0, 4, 1, "IMAGE"),
|
|
link(4, 4, 0, 5, 0, "STRING"),
|
|
]
|
|
nodes[0]["outputs"][0]["links"] = [1]
|
|
nodes[1]["outputs"][0]["links"] = [2]
|
|
nodes[2]["inputs"][0]["link"] = 1
|
|
nodes[2]["inputs"][1]["link"] = 2
|
|
nodes[2]["outputs"][0]["links"] = [3]
|
|
nodes[3]["inputs"][1]["link"] = 3
|
|
nodes[3]["outputs"][0]["links"] = [4]
|
|
examples["23_tencent_vod_multi_images_to_video.json"] = workflow(nodes, links)
|
|
|
|
nodes = [
|
|
load_image_node(1, "example_portrait.png", [40, 40], 0),
|
|
load_video_node(2, "example_motion_reference.mp4", [40, 420], 1),
|
|
motion_control_node(3, [430, 170], 2),
|
|
preview_video_node(4, [930, 170], 3, 3),
|
|
]
|
|
links = [
|
|
link(1, 1, 0, 3, 0, "IMAGE"),
|
|
link(2, 2, 0, 3, 1, "VIDEO"),
|
|
link(3, 3, 0, 4, 0, "STRING"),
|
|
]
|
|
nodes[0]["outputs"][0]["links"] = [1]
|
|
nodes[1]["outputs"][0]["links"] = [2]
|
|
nodes[2]["inputs"][0]["link"] = 1
|
|
nodes[2]["inputs"][1]["link"] = 2
|
|
nodes[2]["outputs"][0]["links"] = [3]
|
|
examples["24_tencent_vod_motion_control.json"] = workflow(nodes, links)
|
|
|
|
nodes = [
|
|
load_image_node(1, "example_garment_reference.png", [40, 120], 0),
|
|
multimodal_video_edit_node(2, [410, 80], 1),
|
|
preview_video_node(3, [940, 80], 2, 2),
|
|
]
|
|
links = [
|
|
link(1, 1, 0, 2, 0, "IMAGE"),
|
|
link(2, 2, 0, 3, 0, "STRING"),
|
|
]
|
|
nodes[0]["outputs"][0]["links"] = [1]
|
|
nodes[1]["inputs"][0]["link"] = 1
|
|
nodes[1]["outputs"][0]["links"] = [2]
|
|
examples["25_tencent_vod_multimodal_video_edit.json"] = workflow(nodes, links)
|
|
|
|
nodes = [
|
|
load_image_node(1, "example_subject_front.png", [40, 40], 0),
|
|
load_image_node(2, "example_subject_ref_1.png", [40, 420], 1),
|
|
image_batch_node(3, [390, 230], 2),
|
|
advanced_element_create_node(4, [730, 150], 3),
|
|
]
|
|
links = [
|
|
link(1, 1, 0, 4, 1, "IMAGE"),
|
|
link(2, 2, 0, 3, 0, "IMAGE"),
|
|
link(3, 3, 0, 4, 2, "IMAGE"),
|
|
]
|
|
nodes[0]["outputs"][0]["links"] = [1]
|
|
nodes[1]["outputs"][0]["links"] = [2]
|
|
nodes[2]["inputs"][0]["link"] = 2
|
|
nodes[2]["outputs"][0]["links"] = [3]
|
|
nodes[3]["inputs"][1]["link"] = 1
|
|
nodes[3]["inputs"][2]["link"] = 3
|
|
examples["26_tencent_vod_advanced_element_create.json"] = workflow(nodes, links)
|
|
|
|
nodes = [
|
|
load_audio_node(1, "example_voice_reference.mp3", [40, 80], 0),
|
|
custom_voice_create_node(2, [420, 80], 1),
|
|
preview_audio_node(3, [880, 120], 2, 2),
|
|
]
|
|
links = [
|
|
link(1, 1, 0, 2, 0, "AUDIO"),
|
|
link(2, 2, 2, 3, 0, "STRING"),
|
|
]
|
|
nodes[0]["outputs"][0]["links"] = [1]
|
|
nodes[1]["inputs"][0]["link"] = 1
|
|
nodes[1]["outputs"][2]["links"] = [2]
|
|
nodes[2]["inputs"][0]["link"] = 2
|
|
examples["27_tencent_vod_custom_voice_create.json"] = workflow(nodes, links)
|
|
|
|
nodes = [
|
|
load_video_node(1, "example_clothing_reference.mp4", [40, 40], 0),
|
|
load_image_node(2, "example_product.png", [40, 250], 1),
|
|
load_image_node(3, "example_model.png", [40, 610], 2),
|
|
load_audio_node(4, "example_bgm.wav", [40, 980], 3),
|
|
clothing_dupe_node(5, [430, 300], 4),
|
|
preview_video_node(6, [930, 300], 5, 5),
|
|
]
|
|
links = [
|
|
link(1, 1, 0, 5, 0, "VIDEO"),
|
|
link(2, 2, 0, 5, 1, "IMAGE"),
|
|
link(3, 3, 0, 5, 6, "IMAGE"),
|
|
link(4, 4, 0, 5, 11, "AUDIO"),
|
|
link(5, 5, 0, 6, 0, "STRING"),
|
|
]
|
|
for index, link_id in enumerate((1, 2, 3, 4)):
|
|
nodes[index]["outputs"][0]["links"] = [link_id]
|
|
nodes[4]["inputs"][0]["link"] = 1
|
|
nodes[4]["inputs"][1]["link"] = 2
|
|
nodes[4]["inputs"][6]["link"] = 3
|
|
nodes[4]["inputs"][11]["link"] = 4
|
|
nodes[4]["outputs"][0]["links"] = [5]
|
|
examples["28_comfyui_kling_wrapper_clothing_dupe.json"] = workflow(nodes, links)
|
|
|
|
for name, data in examples.items():
|
|
write_json(name, strip_client_dependency(data))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
write_examples_readme()
|
|
write_api_examples()
|
|
generate_workflows()
|
|
print(f"Wrote refreshed examples into {ROOT}")
|