Merge pull request #115 from Runware/feature-KlingElements
Added Kling Elements
This commit is contained in:
@@ -64,6 +64,10 @@ from .modules.speechInputCombine import RunwareSpeechInputCombine
|
||||
from .modules.referenceVideos import referenceVideos
|
||||
from .modules.referenceVoices import referenceVoices
|
||||
from .modules.videoInferenceInputs import videoInferenceInputs
|
||||
from .modules.videoInferenceElements import (
|
||||
RunwareVideoInferenceElements,
|
||||
RunwareVideoInferenceElementsCombine,
|
||||
)
|
||||
from .modules.providerSettings.lightricksProviderSettings import RunwareLightricksProviderSettings
|
||||
from .modules.providerSettings.minimaxProviderSettings import RunwareMiniMaxProviderSettings
|
||||
from .modules.providerSettings.klingProviderSettings import RunwareKlingProviderSettings
|
||||
@@ -181,6 +185,8 @@ NODE_CLASS_MAPPINGS = {
|
||||
"Runware Reference Videos": referenceVideos,
|
||||
"Runware Reference Voices": referenceVoices,
|
||||
"Runware Video Inference Inputs": videoInferenceInputs,
|
||||
"Runware Video Inference Elements": RunwareVideoInferenceElements,
|
||||
"Runware Video Inference Elements Combine": RunwareVideoInferenceElementsCombine,
|
||||
"Runware Video Inference Speech Input": RunwareVideoInferenceSpeechInput,
|
||||
"Runware Video Inference Settings": RunwareVideoSettings,
|
||||
"Runware Lightricks Provider Settings": RunwareLightricksProviderSettings,
|
||||
|
||||
@@ -108,6 +108,8 @@ const RUNWARE_NODE_TYPES = {
|
||||
REFERENCEVIDEOS: "Runware Reference Videos",
|
||||
REFERENCEVOICES: "Runware Reference Voices",
|
||||
VIDEOINFERENCEINPUTS: "Runware Video Inference Inputs",
|
||||
VIDEOINFERENCEELEMENTS: "Runware Video Inference Elements",
|
||||
VIDEOINFERENCEELEMENTSCOMBINE: "Runware Video Inference Elements Combine",
|
||||
VIDEOINFERENCESPEECHINPUT: "Runware Video Inference Speech Input",
|
||||
VIDEOSETTINGS: "Runware Video Inference Settings",
|
||||
LIGHTRICKSPROVIDERSETTINGS: "Runware Lightricks Provider Settings",
|
||||
@@ -407,6 +409,14 @@ const RUNWARE_NODE_PROPS = {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
colorModeOnly: true,
|
||||
},
|
||||
[RUNWARE_NODE_TYPES.VIDEOINFERENCEELEMENTS]: {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
colorModeOnly: true,
|
||||
},
|
||||
[RUNWARE_NODE_TYPES.VIDEOINFERENCEELEMENTSCOMBINE]: {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
colorModeOnly: true,
|
||||
},
|
||||
[RUNWARE_NODE_TYPES.LIGHTRICKSPROVIDERSETTINGS]: {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
colorModeOnly: true,
|
||||
|
||||
@@ -106,18 +106,18 @@ class RunwareMediaUpload:
|
||||
def _convertVideoToBase64(self, videoTensor):
|
||||
"""Convert video file to base64 data URI"""
|
||||
print(f"[Debug] VideoFromFile object detected")
|
||||
|
||||
|
||||
videoFile = self._getVideoFilePath(videoTensor)
|
||||
videoPath = self._extractVideoPath(videoFile)
|
||||
|
||||
|
||||
print(f"[Debug] Processing video file: {videoPath}")
|
||||
|
||||
|
||||
with open(videoPath, 'rb') as f:
|
||||
videoBytes = f.read()
|
||||
|
||||
|
||||
mimeType = self._getMimeType(videoPath)
|
||||
videoBase64 = base64.b64encode(videoBytes).decode('utf-8')
|
||||
|
||||
|
||||
return f"data:{mimeType};base64,{videoBase64}"
|
||||
|
||||
def _getVideoFilePath(self, videoTensor):
|
||||
|
||||
@@ -516,7 +516,6 @@ def convertTensor2IMG(tensorImage):
|
||||
return imgDataUri
|
||||
|
||||
|
||||
|
||||
def convertTensor2IMGBase64Only(tensorImage):
|
||||
"""Convert tensor to base64 data URI without caching - for frame images"""
|
||||
imageNP = (tensorImage.squeeze().numpy() * 255).astype(np.uint8)
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
"""
|
||||
Runware Video Inference Elements — builds inputs.elements[] for videoInference (Kling elements, etc.).
|
||||
"""
|
||||
|
||||
import re
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
from .utils import runwareUtils as rwUtils
|
||||
|
||||
_REF_IMAGE_SLOTS = 3
|
||||
_REF_VIDEO_SLOTS = 4
|
||||
|
||||
_ALLOWED_TAGS = frozenset(
|
||||
{
|
||||
"Hottest",
|
||||
"Character",
|
||||
"Animal",
|
||||
"Item",
|
||||
"Costume",
|
||||
"Scene",
|
||||
"Effect",
|
||||
"Others",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _split_lines_or_csv(value: Optional[str]) -> List[str]:
|
||||
if value is None:
|
||||
return []
|
||||
s = str(value).strip()
|
||||
if not s:
|
||||
return []
|
||||
parts = re.split(r"[\n,]+", s)
|
||||
return [p.strip() for p in parts if p.strip()]
|
||||
|
||||
|
||||
def _parse_tags(value: Optional[str]) -> List[str]:
|
||||
raw = _split_lines_or_csv(value)
|
||||
if not raw:
|
||||
return []
|
||||
out: List[str] = []
|
||||
for t in raw:
|
||||
if t in _ALLOWED_TAGS:
|
||||
if t not in out:
|
||||
out.append(t)
|
||||
return out
|
||||
|
||||
|
||||
def _build_element_dict(
|
||||
id_: str,
|
||||
description: str,
|
||||
frontalImage: Any,
|
||||
refer_images: List[Any],
|
||||
refer_videos: List[str],
|
||||
voice: str,
|
||||
tags: str,
|
||||
) -> Dict[str, Any]:
|
||||
el: Dict[str, Any] = {}
|
||||
id_s = (id_ or "").strip()
|
||||
if id_s:
|
||||
el["id"] = id_s
|
||||
desc = (description or "").strip()
|
||||
if desc:
|
||||
el["description"] = desc
|
||||
if isinstance(frontalImage, str):
|
||||
front = frontalImage.strip()
|
||||
if front:
|
||||
el["frontalImage"] = front
|
||||
elif frontalImage is not None:
|
||||
el["frontalImage"] = frontalImage
|
||||
imgs = [x for x in refer_images if x is not None]
|
||||
if imgs:
|
||||
el["images"] = imgs
|
||||
if refer_videos:
|
||||
el["videos"] = refer_videos
|
||||
voices = _split_lines_or_csv(voice)
|
||||
if voices:
|
||||
el["voice"] = voices
|
||||
tag_list = _parse_tags(tags)
|
||||
if tag_list:
|
||||
el["tags"] = tag_list
|
||||
return el
|
||||
|
||||
|
||||
class RunwareVideoInferenceElements:
|
||||
"""One Kling / video inference element for inputs.elements."""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
optional: Dict[str, tuple] = {
|
||||
"id": ("STRING", {
|
||||
"default": "",
|
||||
"tooltip": "User-facing element id (max 20 chars on API). For reuse, set only id with no reference media.",
|
||||
}),
|
||||
"description": ("STRING", {
|
||||
"default": "",
|
||||
"multiline": True,
|
||||
"tooltip": "Optional element description (e.g. for image_refer / new elements).",
|
||||
}),
|
||||
"frontalImage": ("IMAGE", {
|
||||
"tooltip": "Frontal reference image for the element.",
|
||||
}),
|
||||
"voice": ("STRING", {
|
||||
"default": "",
|
||||
"multiline": True,
|
||||
"tooltip": "Voice file URLs (mp3/wav/mp4/mov), one per line or comma-separated.",
|
||||
}),
|
||||
"tags": ("STRING", {
|
||||
"default": "",
|
||||
"multiline": True,
|
||||
"tooltip": "Comma or newline-separated tags: Hottest, Character, Animal, Item, Costume, Scene, Effect, Others.",
|
||||
}),
|
||||
}
|
||||
for i in range(1, _REF_IMAGE_SLOTS + 1):
|
||||
optional[f"images_{i}"] = ("IMAGE", {
|
||||
"tooltip": f"Optional reference image {i} for image_refer (up to {_REF_IMAGE_SLOTS}).",
|
||||
})
|
||||
for i in range(1, _REF_VIDEO_SLOTS + 1):
|
||||
optional[f"videos_{i}"] = ("STRING", {
|
||||
"default": "",
|
||||
"tooltip": (
|
||||
f"Optional reference video {i}: public URL or mediaUUID from Runware Media Upload "
|
||||
f"(upload a video first, then paste the UUID here)."
|
||||
),
|
||||
})
|
||||
return {"required": {}, "optional": optional}
|
||||
|
||||
RETURN_TYPES = ("RUNWAREVIDEOINFERENCEELEMENTS",)
|
||||
RETURN_NAMES = ("Elements",)
|
||||
FUNCTION = "build_element"
|
||||
CATEGORY = "Runware"
|
||||
DESCRIPTION = (
|
||||
"Define a single video inference element (id, description, frontalImage, images_1…images_3, videos_1…videos_4, voice, tags) "
|
||||
"for inputs.elements on Kling and compatible models. Connect to Runware Video Inference Inputs → Elements, "
|
||||
"or use Runware Video Inference Elements Combine for multiple elements."
|
||||
)
|
||||
|
||||
def build_element(self, **kwargs) -> Tuple[List[Dict[str, Any]],]:
|
||||
frontal_image = kwargs.get("frontalImage", None)
|
||||
frontal_image_value = None
|
||||
if frontal_image is not None:
|
||||
frontal_image_value = rwUtils.convertTensor2IMG(frontal_image)
|
||||
|
||||
refer_images: List[Any] = []
|
||||
for i in range(1, _REF_IMAGE_SLOTS + 1):
|
||||
slot = kwargs.get(f"images_{i}")
|
||||
if slot is not None:
|
||||
refer_images.append(rwUtils.convertTensor2IMG(slot))
|
||||
|
||||
refer_videos: List[str] = []
|
||||
for i in range(1, _REF_VIDEO_SLOTS + 1):
|
||||
raw = kwargs.get(f"videos_{i}")
|
||||
if raw is not None and str(raw).strip() != "":
|
||||
refer_videos.append(str(raw).strip())
|
||||
|
||||
el = _build_element_dict(
|
||||
str(kwargs.get("id") or ""),
|
||||
str(kwargs.get("description") or ""),
|
||||
frontal_image_value,
|
||||
refer_images,
|
||||
refer_videos,
|
||||
str(kwargs.get("voice") or ""),
|
||||
str(kwargs.get("tags") or ""),
|
||||
)
|
||||
if not el:
|
||||
raise ValueError(
|
||||
"Runware Video Inference Elements: provide at least id, description, frontalImage, "
|
||||
"images_1…images_3, videos_1…videos_4, voice, or tags."
|
||||
)
|
||||
return ([el],)
|
||||
|
||||
|
||||
class RunwareVideoInferenceElementsCombine:
|
||||
"""Combine multiple Runware Video Inference Elements (each output is a list) into one list."""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"Elements 1": ("RUNWAREVIDEOINFERENCEELEMENTS", {
|
||||
"tooltip": "Connect a Runware Video Inference Elements node (single-element list).",
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"Elements 2": ("RUNWAREVIDEOINFERENCEELEMENTS", {
|
||||
"tooltip": "Optional second Runware Video Inference Elements node.",
|
||||
}),
|
||||
"Elements 3": ("RUNWAREVIDEOINFERENCEELEMENTS", {
|
||||
"tooltip": "Optional third Runware Video Inference Elements node.",
|
||||
}),
|
||||
"Elements 4": ("RUNWAREVIDEOINFERENCEELEMENTS", {
|
||||
"tooltip": "Optional fourth Runware Video Inference Elements node.",
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("RUNWAREVIDEOINFERENCEELEMENTS",)
|
||||
RETURN_NAMES = ("Elements",)
|
||||
FUNCTION = "combine"
|
||||
CATEGORY = "Runware"
|
||||
DESCRIPTION = (
|
||||
"Combine up to four Runware Video Inference Elements outputs into inputs.elements. "
|
||||
"Connect the result to Runware Video Inference Inputs → Elements."
|
||||
)
|
||||
|
||||
def combine(self, **kwargs) -> Tuple[List[Dict[str, Any]],]:
|
||||
out: List[Dict[str, Any]] = []
|
||||
for i in range(1, 5):
|
||||
key = f"Elements {i}"
|
||||
chunk = kwargs.get(key)
|
||||
if chunk is not None and isinstance(chunk, list):
|
||||
out.extend(
|
||||
e for e in chunk
|
||||
if isinstance(e, dict) and len(e) > 0
|
||||
)
|
||||
if not out:
|
||||
raise ValueError(
|
||||
"Runware Video Inference Elements Combine: connect at least one non-empty Elements input."
|
||||
)
|
||||
return (out,)
|
||||
@@ -60,6 +60,9 @@ class videoInferenceInputs:
|
||||
"multiline": False,
|
||||
"tooltip": "Video ID from a previous generation. Used when extending or editing an existing OpenAI Sora video",
|
||||
}),
|
||||
"Elements": ("RUNWAREVIDEOINFERENCEELEMENTS", {
|
||||
"tooltip": "Connect Runware Video Inference Elements (one element) or Runware Video Inference Elements Combine (several) for inputs.elements (Kling, etc.).",
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +92,7 @@ class videoInferenceInputs:
|
||||
draftId = kwargs.get("draftId", "")
|
||||
videoId = kwargs.get("videoId", "")
|
||||
avatar = kwargs.get("Avatar", "")
|
||||
elements_list = kwargs.get("Elements", None)
|
||||
|
||||
inputs = {}
|
||||
|
||||
@@ -148,4 +152,12 @@ class videoInferenceInputs:
|
||||
if videoId is not None and videoId.strip() != "":
|
||||
inputs["videoId"] = videoId.strip()
|
||||
|
||||
if elements_list is not None and isinstance(elements_list, list):
|
||||
merged_elements = [
|
||||
e for e in elements_list
|
||||
if isinstance(e, dict) and len(e) > 0
|
||||
]
|
||||
if merged_elements:
|
||||
inputs["elements"] = merged_elements
|
||||
|
||||
return (inputs,)
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user