Merge pull request #45 from Runware/fix-Runway

Fix runway, positivePrompt( optional), add Safety
This commit is contained in:
Sirshendu Ganguly
2025-11-17 21:40:39 -05:00
committed by GitHub
9 changed files with 55 additions and 18 deletions
+3
View File
@@ -1347,6 +1347,7 @@ function videoInferenceDimensionsHandler(videoInferenceNode) {
"klingai:3@1": {width: 1280, height: 720}, "klingai:3@2": {width: 1920, height: 1080},
"klingai:4@3": {width: 1280, height: 720}, "klingai:5@1": {width: 1280, height: 720},
"klingai:5@2": {width: 1920, height: 1080}, "klingai:5@3": {width: 1920, height: 1080},
"klingai:6@1": {width: 1920, height: 1080},
"klingai:7@1": {width: 0, height: 0},
"google:2@0": {width: 1280, height: 720}, "google:3@0": {width: 1280, height: 720},
"google:3@1": {width: 1280, height: 720}, "google:3@2": {width: 1280, height: 720},
@@ -1568,6 +1569,7 @@ function videoModelSearchFilterHandler(videoModelSearchNode) {
"klingai:3@1 (KlingAI V1.6 Standard)", "klingai:3@2 (KlingAI V1.6 Pro)",
"klingai:4@3 (KlingAI V2.1 Master)", "klingai:5@1 (KlingAI V2.1 Standard (I2V))",
"klingai:5@2 (KlingAI V2.1 Pro (I2V))", "klingai:5@3 (KlingAI V2.0 Master)",
"klingai:6@1 (KlingAI 2.5 Turbo Pro)",
"klingai:7@1 (KlingAI Lip-Sync)",
],
"Veo": [
@@ -1626,6 +1628,7 @@ function videoModelSearchFilterHandler(videoModelSearchNode) {
"klingai:5@1": {"width": 1280, "height": 720},
"klingai:5@2": {"width": 1920, "height": 1080},
"klingai:5@3": {"width": 1920, "height": 1080},
"klingai:6@1": {"width": 1920, "height": 1080},
"klingai:7@1": {"width": 0, "height": 0},
"google:2@0": {"width": 1280, "height": 720},
"google:3@0": {"width": 1280, "height": 720},
+1 -1
View File
@@ -50,7 +50,7 @@ class RunwareFrameImages:
def _createFrameData(self, image, position):
"""Create frame data object from image and position"""
imageData = rwUtils.convertTensor2IMG(image)
imageData = rwUtils.convertTensor2IMGBase64Only(image)
frameData = {"inputImage": imageData}
if position != "auto":
+30 -14
View File
@@ -6,9 +6,6 @@ class txt2img:
def INPUT_TYPES(cls):
return {
"required": {
"Model": ("RUNWAREMODEL", {
"tooltip": "Connect a Runware Model From Runware Model Node.",
}),
"positivePrompt": ("STRING", {
"multiline": True,
"placeholder": "Positive Prompt: a text instruction to guide the model on generating the image. It is usually a sentence or a paragraph that provides positive guidance for the task. This parameter is essential to shape the desired results.\n\nYou Can Press (Ctrl + Alt + E) To Enhance The Prompt!",
@@ -19,6 +16,9 @@ class txt2img:
"placeholder": "Negative Prompt: a text instruction to guide the model on generating the image. It is usually a sentence or a paragraph that provides negative guidance for the task. This parameter helps to avoid certain undesired results.",
"tooltip": "Negative Prompt: a text instruction to guide the model on generating the image."
}),
"Model": ("RUNWAREMODEL", {
"tooltip": "Connect a Runware Model From Runware Model Node.",
}),
"Multi Inference Mode": ("BOOLEAN", {
"tooltip": "If Enabled the node will skip the image generation process and will only return the Runware Task Object to be used in the Multi Inference Node.",
"default": False,
@@ -175,25 +175,32 @@ class txt2img:
"providerSettings": ("RUNWAREPROVIDERSETTINGS", {
"tooltip": "Connect a Runware Provider Settings Node to configure provider-specific parameters.",
}),
"safetyInputs": ("RUNWARESAFETYINPUTS", {
"tooltip": "Connect Runware Safety Inputs node to configure safety and content moderation settings.",
}),
}
}
@classmethod
def VALIDATE_INPUTS(cls, positivePrompt, negativePrompt):
if (positivePrompt is not None and (positivePrompt == "" or len(positivePrompt) < 3 or len(positivePrompt) > 2000)):
raise Exception(
"Positive Prompt Must Be Between 3 And 2000 characters!")
if (negativePrompt is not None and negativePrompt != "" and (len(negativePrompt) < 3 or len(negativePrompt) > 2000)):
raise Exception(
"Negative Prompt Must Be Between 3 And 2000 characters!")
return True
DESCRIPTION = "Generates Images Lightning Fast With Runware Image Inference Sonic Engine."
FUNCTION = "generateImage"
RETURN_TYPES = ("IMAGE", "RUNWARETASK")
RETURN_NAMES = ("IMAGE", "RW-Task")
CATEGORY = "Runware"
@classmethod
def VALIDATE_INPUTS(cls, **kwargs):
positivePrompt = kwargs.get("positivePrompt", "")
negativePrompt = kwargs.get("negativePrompt", "")
# Only validate positivePrompt length if it's provided
if positivePrompt and len(positivePrompt) > 2000:
return "Positive Prompt is too long. Maximum length is 2000 characters."
if negativePrompt and len(negativePrompt) > 2000:
return "Negative Prompt is too long. Maximum length is 2000 characters."
return True
def generateImage(self, **kwargs):
runwareModel = kwargs.get("Model")
positivePrompt = kwargs.get("positivePrompt")
@@ -211,6 +218,7 @@ class txt2img:
referenceImages = kwargs.get("referenceImages", None)
inputs = kwargs.get("inputs", None)
providerSettings = kwargs.get("providerSettings", None)
safetyInputs = kwargs.get("safetyInputs", None)
seedImage = kwargs.get("seedImage", None)
seedImageStrength = kwargs.get("strength", 0.8)
maskImage = kwargs.get("maskImage", None)
@@ -240,7 +248,6 @@ class txt2img:
{
"taskType": "imageInference",
"taskUUID": rwUtils.genRandUUID(),
"positivePrompt": positivePrompt,
"model": runwareModel,
"outputType": "base64Data",
"outputFormat": outputFormat,
@@ -248,6 +255,11 @@ class txt2img:
"numberResults": batchSize,
}
]
# Add positivePrompt only if provided
if positivePrompt is not None and positivePrompt != "":
genConfig[0]["positivePrompt"] = positivePrompt
# For Debugging Purposes Only
print(f"[Debugging] Task UUID: {genConfig[0]['taskUUID']}")
@@ -344,6 +356,10 @@ class txt2img:
# If it's already in the correct format, use it directly
genConfig[0]["providerSettings"] = providerSettings
# Add safety inputs if provided
if safetyInputs is not None and isinstance(safetyInputs, dict) and len(safetyInputs) > 0:
genConfig[0]["safety"] = safetyInputs
if (multiInferenceMode):
return (None, genConfig)
else:
+14
View File
@@ -467,6 +467,20 @@ def convertTensor2IMG(tensorImage):
def convertTensor2IMGBase64Only(tensorImage):
"""Convert tensor to base64 data URI without caching - for frame images"""
imageNP = (tensorImage.squeeze().numpy() * 255).astype(np.uint8)
image = Image.fromarray(imageNP)
with io.BytesIO() as buffer:
image.save(buffer, format="PNG")
imageRawData = buffer.getvalue()
imgb64 = base64.b64encode(imageRawData).decode("utf-8")
imgDataUri = f"data:image/png;base64,{imgb64}"
return imgDataUri
def convertTensor2IMGForVideo(tensorImage):
"""Convert tensor to image and force upload to get UUID for video frame images"""
imageNP = (tensorImage.squeeze().numpy() * 255).astype(np.uint8)
+2 -2
View File
@@ -49,8 +49,8 @@ class RunwareVideoInputsFrameImages:
return (frameImages,)
def _createFrameEntry(self, image, frameLabel):
imageData = rwUtils.convertTensor2IMG(image)
entry = {"image": imageData}
imageData = rwUtils.convertTensor2IMGBase64Only(image)
entry = {"inputImage": imageData}
if isinstance(frameLabel, str) and frameLabel.strip() != "":
if frameLabel.strip().isdigit():
+2
View File
@@ -14,6 +14,7 @@ class videoModelSearch:
"klingai:5@1 (KlingAI V2.1 Standard (I2V))",
"klingai:5@2 (KlingAI V2.1 Pro (I2V))",
"klingai:5@3 (KlingAI V2.0 Master)",
"klingai:6@1 (KlingAI 2.5 Turbo Pro)",
"klingai:7@1 (KlingAI Lip-Sync)",
],
"Veo": [
@@ -89,6 +90,7 @@ class videoModelSearch:
"klingai:5@1": {"width": 1280, "height": 720},
"klingai:5@2": {"width": 1920, "height": 1080},
"klingai:5@3": {"width": 1920, "height": 1080},
"klingai:6@1": {"width": 1920, "height": 1080},
"klingai:7@1": {"width": 0, "height": 0},
# Veo Models
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long