Update image saving with timestamps, fix upscaler steps placement, rename vectorize output to Image
This commit is contained in:
+2
-4
@@ -7,8 +7,6 @@ from .modules.modelSearch import modelSearch
|
||||
from .modules.bridges import mainRoute
|
||||
from .modules.controlNet import controlNet
|
||||
from .modules.multiInference import multiInference
|
||||
from .modules.runwareBFL import runwareKontext
|
||||
from .modules.runwareImagen import runwareImagen
|
||||
from .modules.loraSearch import loraSearch
|
||||
from .modules.loraCombine import loraCombine
|
||||
from .modules.refiner import refiner
|
||||
@@ -71,6 +69,7 @@ from .modules.safetyInputs import safetyInputs
|
||||
from .modules.settings import RunwareSettings
|
||||
from .modules.videoAdvancedFeatureInputs import videoAdvancedFeatureInputs
|
||||
from .modules.advancedFeatureSettings.wanAnimateAdvancedFeatureSettings import RunwareWanAnimateAdvancedFeatureSettings
|
||||
from .modules.saveImage import RunwareSaveImage
|
||||
|
||||
RUNWARE_COMFYUI_VERSION = "1.4.0 Beta"
|
||||
|
||||
@@ -90,8 +89,6 @@ NODE_CLASS_MAPPINGS = {
|
||||
"Runware PhotoMaker V2": photoMaker,
|
||||
"Runware Image Upscaler": upscaler,
|
||||
"Runware Model Search": modelSearch,
|
||||
"Runware Kontext Inference": runwareKontext,
|
||||
"Runware Imagen Inference": runwareImagen,
|
||||
"Runware Multi Inference": multiInference,
|
||||
"Runware Lora Search": loraSearch,
|
||||
"Runware Embedding Search": embeddingSearch,
|
||||
@@ -156,6 +153,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"Runware Settings": RunwareSettings,
|
||||
"Runware Video Advanced Feature Inputs": videoAdvancedFeatureInputs,
|
||||
"Runware Wan Animate Advanced Feature Settings": RunwareWanAnimateAdvancedFeatureSettings,
|
||||
"Runware Save Image": RunwareSaveImage,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
|
||||
@@ -121,6 +121,7 @@ const RUNWARE_NODE_TYPES = {
|
||||
SETTINGS: "Runware Settings",
|
||||
VIDEOADVANCEDFEATUREINPUTS: "Runware Video Advanced Feature Inputs",
|
||||
WANANIMATEADVANCEDFEATURESETTINGS: "Runware Wan Animate Advanced Feature Settings",
|
||||
SAVEIMAGE: "Runware Save Image",
|
||||
};
|
||||
|
||||
const RUNWARE_NODE_PROPS = {
|
||||
@@ -420,6 +421,10 @@ const RUNWARE_NODE_PROPS = {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
colorModeOnly: true,
|
||||
},
|
||||
[RUNWARE_NODE_TYPES.SAVEIMAGE]: {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
colorModeOnly: true,
|
||||
},
|
||||
};
|
||||
|
||||
export {
|
||||
|
||||
@@ -80,7 +80,8 @@ class bgremoval:
|
||||
|
||||
DESCRIPTION = "Remove backgrounds from images effortlessly using Runware's low-cost image editing Inference."
|
||||
FUNCTION = "rembg"
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("Image",)
|
||||
CATEGORY = "Runware"
|
||||
|
||||
def rembg(self, **kwargs):
|
||||
@@ -112,7 +113,7 @@ class bgremoval:
|
||||
"model": modelAIR,
|
||||
"outputFormat": outputFormat,
|
||||
"outputQuality": rwUtils.OUTPUT_QUALITY,
|
||||
"outputType": "base64Data",
|
||||
"outputType": "URL",
|
||||
}
|
||||
|
||||
if includeExtraSettings:
|
||||
@@ -166,5 +167,5 @@ class bgremoval:
|
||||
print(f"[DEBUG] Error in Background Removal Request: {str(e)}", flush=True)
|
||||
raise
|
||||
|
||||
images = rwUtils.convertImageB64List(genResult)
|
||||
return (images, )
|
||||
images = rwUtils.extractImageURLs(genResult)
|
||||
return (images,)
|
||||
@@ -76,7 +76,7 @@ class controlNetPreprocessor:
|
||||
|
||||
DESCRIPTION = "ControlNet offers advanced capabilities for precise image processing through the use of guide images in specific formats, known as preprocessed images. This powerful tool enhances the control and customization of image generation, enabling users to achieve desired artistic styles and detailed adjustments effectively."
|
||||
FUNCTION = "controlNetPreProcess"
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("GUIDE IMAGE",)
|
||||
CATEGORY = "Runware"
|
||||
|
||||
@@ -98,7 +98,7 @@ class controlNetPreprocessor:
|
||||
"preProcessorType": preProcessorType,
|
||||
"outputFormat": rwUtils.OUTPUT_FORMAT,
|
||||
"outputQuality": rwUtils.OUTPUT_QUALITY,
|
||||
"outputType": "base64Data",
|
||||
"outputType": "URL",
|
||||
}
|
||||
]
|
||||
|
||||
@@ -115,5 +115,5 @@ class controlNetPreprocessor:
|
||||
genConfig[0]["highThresholdCanny"] = cannyHighThreshold
|
||||
|
||||
genResult = rwUtils.inferenecRequest(genConfig)
|
||||
images = rwUtils.convertImageB64List(genResult)
|
||||
return (images, )
|
||||
images = rwUtils.extractImageURLs(genResult)
|
||||
return (images,)
|
||||
+21
-11
@@ -1,5 +1,6 @@
|
||||
from .utils import runwareUtils as rwUtils
|
||||
import comfy.model_management
|
||||
import os
|
||||
|
||||
|
||||
class txt2img:
|
||||
@@ -129,9 +130,9 @@ class txt2img:
|
||||
"min": 32,
|
||||
"max": 128,
|
||||
}),
|
||||
"outputFormat": (["WEBP", "PNG", "JPEG", "SVG", "TIFF"], {
|
||||
"tooltip": "Choose the output image format.",
|
||||
"default": "WEBP",
|
||||
"outputFormat": (["WEBP", "PNG", "JPEG", "TIFF"], {
|
||||
"tooltip": "Choose the output image format. This will be used for the API request. The actual saved format will match the format returned by the API (visible in imageURL).",
|
||||
"default": rwUtils.getOutputFormat(),
|
||||
}),
|
||||
"batchSize": ("INT", {
|
||||
"tooltip": "The number of images to generate in a single request.",
|
||||
@@ -196,8 +197,8 @@ class txt2img:
|
||||
|
||||
DESCRIPTION = "Generates Images Lightning Fast With Runware Image Inference Sonic Engine."
|
||||
FUNCTION = "generateImage"
|
||||
RETURN_TYPES = ("IMAGE", "RUNWARETASK")
|
||||
RETURN_NAMES = ("IMAGE", "RW-Task")
|
||||
RETURN_TYPES = ("RUNWARETASK", "STRING")
|
||||
RETURN_NAMES = ("RW-Task", "Image")
|
||||
CATEGORY = "Runware"
|
||||
|
||||
@classmethod
|
||||
@@ -251,7 +252,7 @@ class txt2img:
|
||||
useSeed = kwargs.get("useSeed", True)
|
||||
useClipSkip = kwargs.get("useClipSkip", True)
|
||||
dimensions = kwargs.get("dimensions", "Square (512x512)")
|
||||
outputFormat = kwargs.get("outputFormat", "WEBP")
|
||||
outputFormat = kwargs.get("outputFormat", rwUtils.getOutputFormat())
|
||||
batchSize = kwargs.get("batchSize", 1)
|
||||
acceleration = kwargs.get("acceleration", "none")
|
||||
useResolution = kwargs.get("useResolution", False)
|
||||
@@ -454,11 +455,20 @@ class txt2img:
|
||||
status = image_data["status"]
|
||||
|
||||
if status == "success":
|
||||
# Check for image data (imageURL, imageBase64Data, mediaURL)
|
||||
if "imageURL" in image_data or "imageBase64Data" in image_data or "imageURL" in image_data:
|
||||
# Convert and return images
|
||||
images = rwUtils.convertImageB64List(pollResult)
|
||||
return (images, None)
|
||||
# Check for image data (imageURL, imageBase64Data)
|
||||
if "imageURL" in image_data or "imageBase64Data" in image_data:
|
||||
# Extract URLs from all results
|
||||
image_urls = []
|
||||
|
||||
for result in pollResult.get("data", []):
|
||||
imageURL = result.get("imageURL")
|
||||
if imageURL:
|
||||
image_urls.append(imageURL)
|
||||
|
||||
# Return URLs as comma-separated string (or single URL)
|
||||
image_url_str = ",".join(image_urls) if image_urls else ""
|
||||
|
||||
return (None, image_url_str)
|
||||
|
||||
# If status is "processing", continue polling
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ class imageMasking:
|
||||
DESCRIPTION = "Image Masking provides intelligent detection and mask generation for specific elements in images, particularly optimized for faces, hands, and people. Built on advanced detection models, this feature enhances the inpainting workflow by automatically creating precise masks around detected elements, enabling targeted enhancement and detailing."
|
||||
|
||||
FUNCTION = "imageMasking"
|
||||
RETURN_TYPES = ("IMAGE", "IMAGE", "MASK")
|
||||
RETURN_TYPES = ("IMAGE", "STRING", "STRING")
|
||||
RETURN_NAMES = ("Image", "Mask Preview", "Mask")
|
||||
CATEGORY = "Runware"
|
||||
|
||||
@@ -87,7 +87,7 @@ class imageMasking:
|
||||
"model": detectionModel,
|
||||
"confidence": confidence,
|
||||
"maxDetections": maxDetections,
|
||||
"outputType": "base64Data",
|
||||
"outputType": "URL",
|
||||
"outputFormat": rwUtils.OUTPUT_FORMAT,
|
||||
"outputQuality": rwUtils.OUTPUT_QUALITY,
|
||||
}
|
||||
@@ -97,5 +97,5 @@ class imageMasking:
|
||||
genConfig[0]["maskBlur"] = maskBlur
|
||||
|
||||
genResult = rwUtils.inferenecRequest(genConfig)
|
||||
images = rwUtils.convertImageB64List(genResult)
|
||||
return (image, images[0], images[0])
|
||||
image_url = rwUtils.extractImageURLs(genResult)
|
||||
return (image, image_url, image_url)
|
||||
|
||||
@@ -39,7 +39,7 @@ class multiInference:
|
||||
|
||||
DESCRIPTION = "Allows you to Run Multiple Inference Tasks in Parallel."
|
||||
FUNCTION = "multiInference"
|
||||
RETURN_TYPES = ("IMAGE", "IMAGE", "IMAGE", "IMAGE")
|
||||
RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING")
|
||||
RETURN_NAMES = ("Result 1", "Result 2", "Result 3", "Result 4")
|
||||
CATEGORY = "Runware"
|
||||
|
||||
@@ -60,8 +60,8 @@ class multiInference:
|
||||
result = await loop.run_in_executor(
|
||||
None, rwUtils.inferenecRequest, taskData
|
||||
)
|
||||
images = rwUtils.convertImageB64List(result)
|
||||
return images
|
||||
image_url = rwUtils.extractImageURLs(result)
|
||||
return image_url
|
||||
except Exception as e:
|
||||
print(f"\n---- Runware Multi Inference Task {oindex + 1} Failed ----")
|
||||
print(f"Task-Type: {taskData[0]['taskType']}")
|
||||
|
||||
@@ -112,7 +112,8 @@ class photoMaker:
|
||||
|
||||
DESCRIPTION = "Transform and style images using PhotoMaker's advanced personalization technology. Create consistent, high-quality image variations with precise subject fidelity and style control."
|
||||
FUNCTION = "photoMaker"
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("Image",)
|
||||
CATEGORY = "Runware"
|
||||
|
||||
@classmethod
|
||||
@@ -171,7 +172,7 @@ class photoMaker:
|
||||
"CFGScale": cfgScale,
|
||||
"clipSkip": clipSkip,
|
||||
"numberResults": batchSize,
|
||||
"outputType": "base64Data",
|
||||
"outputType": "URL",
|
||||
"outputFormat": rwUtils.OUTPUT_FORMAT,
|
||||
"outputQuality": rwUtils.OUTPUT_QUALITY,
|
||||
}
|
||||
@@ -186,5 +187,5 @@ class photoMaker:
|
||||
genConfig[0]["promptWeighting"] = "compel"
|
||||
|
||||
genResult = rwUtils.inferenecRequest(genConfig)
|
||||
images = rwUtils.convertImageB64List(genResult)
|
||||
return (images, )
|
||||
images = rwUtils.extractImageURLs(genResult)
|
||||
return (images,)
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
from .utils import runwareUtils as rwUtils
|
||||
|
||||
class runwareKontext:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"Model": ([
|
||||
"bfl:3@1 (FLUX.1 Kontext Pro)",
|
||||
"bfl:4@1 (FLUX.1 Kontext Max)",
|
||||
], {
|
||||
"tooltip": "Select The Model You Want For Image Generation or Image Editing.",
|
||||
"default": "bfl:3@1 (FLUX.1 Kontext Pro)",
|
||||
}),
|
||||
"positivePrompt": ("STRING", {
|
||||
"multiline": True,
|
||||
"placeholder": "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!",
|
||||
"tooltip": "Text instruction to guide the model on generating the image. You Can Also Press (Ctrl + Alt + E) To Enhance The Prompt!"
|
||||
}),
|
||||
"Prompt Upsampling": ("BOOLEAN", {
|
||||
"tooltip": "If Enabled it Will automatically modify the prompt at generation time for more creative results.",
|
||||
"default": False,
|
||||
"label_on": "Enabled",
|
||||
"label_off": "Disabled",
|
||||
}),
|
||||
"Aspect Ratio": ([
|
||||
"21:9 (Ultra-Wide / Landscape)",
|
||||
"16:9 (Wide / Landscape)",
|
||||
"4:3 (Standard / Landscape)",
|
||||
"3:2 (Classic Photo / Landscape)",
|
||||
"1:1 (Square)",
|
||||
"2:3 (Classic Photo / Portrait)",
|
||||
"3:4 (Standard / Portrait)",
|
||||
"9:16 (Vertical Video / Portrait)",
|
||||
"9:21 (Ultra-Tall / Portrait)"
|
||||
], {
|
||||
"default": "16:9 (Wide / Landscape)",
|
||||
"tooltip": "Adjust the dimensions of the generated image. This setting allows you to choose from various aspect ratios, such as 16:9 for wide images or 1:1 for square images. The width and height will be automatically adjusted based on the selected aspect ratio.",
|
||||
}),
|
||||
"seed": ("INT", {
|
||||
"tooltip": "A value used to randomize the image generation. If you want to make images reproducible (generate the same image multiple times), you can use the same seed value. Set to 0 to auto-generate a random seed.",
|
||||
"default": 0,
|
||||
"min": 0,
|
||||
"max": 4294967295,
|
||||
}),
|
||||
"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,
|
||||
"label_on": "Enabled",
|
||||
"label_off": "Disabled",
|
||||
}),
|
||||
"batchSize": ("INT", {
|
||||
"tooltip": "The number of images to generate in a single request.",
|
||||
"default": 1,
|
||||
"min": 1,
|
||||
"max": 10,
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"Reference Images": ("RUNWAREREFERENCEIMAGES", {
|
||||
"tooltip": "Connect a Runware Reference Images Node to use reference images for image editing or generation.",
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def VALIDATE_INPUTS(cls, positivePrompt):
|
||||
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!")
|
||||
return True
|
||||
|
||||
DESCRIPTION = "Generate And Edit Images Using Runware BFL Kontext Models."
|
||||
FUNCTION = "kontextInference"
|
||||
RETURN_TYPES = ("IMAGE", "RUNWARETASK")
|
||||
RETURN_NAMES = ("IMAGE", "RW-Task")
|
||||
CATEGORY = "Runware"
|
||||
|
||||
def kontextInference(self, **kwargs):
|
||||
runwareModel = kwargs.get("Model")
|
||||
runwareModel = runwareModel.split(" ")[0]
|
||||
positivePrompt = kwargs.get("positivePrompt")
|
||||
promptUpsampling = kwargs.get("Prompt Upsampling")
|
||||
aspectRatio = kwargs.get("Aspect Ratio")
|
||||
multiInferenceMode = kwargs.get("Multi Inference Mode", False)
|
||||
referenceImages = kwargs.get("Reference Images", None)
|
||||
seed = kwargs.get("seed")
|
||||
batchSize = kwargs.get("batchSize", 1)
|
||||
|
||||
aspectRatioMap = {
|
||||
"21:9": (1568, 672),
|
||||
"16:9": (1392, 752),
|
||||
"4:3": (1184, 880),
|
||||
"3:2": (1248, 832),
|
||||
"1:1": (1024, 1024),
|
||||
"2:3": (832, 1248),
|
||||
"3:4": (880, 1184),
|
||||
"9:16": (752, 1392),
|
||||
"9:21": (672, 1568),
|
||||
}
|
||||
|
||||
secAspectRatio = aspectRatio.split(" ")[0]
|
||||
width, height = aspectRatioMap.get(secAspectRatio, (1024, 1024))
|
||||
|
||||
genConfig = [
|
||||
{
|
||||
"taskType": "imageInference",
|
||||
"taskUUID": rwUtils.genRandUUID(),
|
||||
"positivePrompt": positivePrompt,
|
||||
"height": height,
|
||||
"width": width,
|
||||
"model": runwareModel,
|
||||
"seed": seed,
|
||||
"outputType": "base64Data",
|
||||
"outputFormat": rwUtils.OUTPUT_FORMAT,
|
||||
"outputQuality": rwUtils.OUTPUT_QUALITY,
|
||||
"numberResults": batchSize,
|
||||
}
|
||||
]
|
||||
|
||||
if (referenceImages is not None):
|
||||
genConfig[0]["referenceImages"] = referenceImages
|
||||
|
||||
if (promptUpsampling):
|
||||
genConfig[0]["providerSettings"] = {
|
||||
"bfl": {
|
||||
"promptUpsampling": True
|
||||
}
|
||||
}
|
||||
|
||||
# For Debugging Purposes Only
|
||||
print(f"[Debugging] Task UUID: {genConfig[0]['taskUUID']}")
|
||||
|
||||
if (multiInferenceMode):
|
||||
return (None, genConfig)
|
||||
else:
|
||||
genResult = rwUtils.inferenecRequest(genConfig)
|
||||
images = rwUtils.convertImageB64List(genResult)
|
||||
return (images, None)
|
||||
@@ -1,111 +0,0 @@
|
||||
from .utils import runwareUtils as rwUtils
|
||||
|
||||
class runwareImagen:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"Model": ([
|
||||
"google:1@1 (Imagen 3)",
|
||||
"google:1@2 (Imagen 3 Fast)",
|
||||
"google:2@1 (Imagen 4 Preview)",
|
||||
"google:2@2 (Imagen 4 Ultra)",
|
||||
"google:2@3 (Imagen 4 Fast)",
|
||||
], {
|
||||
"tooltip": "Select The Google Imagen Model For Image Generation.",
|
||||
"default": "google:1@1 (Imagen 3)",
|
||||
}),
|
||||
"positivePrompt": ("STRING", {
|
||||
"multiline": True,
|
||||
"placeholder": "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.",
|
||||
"tooltip": "Text instruction to guide the model on generating the image."
|
||||
}),
|
||||
"Aspect Ratio": ([
|
||||
"1:1 (Square)",
|
||||
"9:16 (Vertical)",
|
||||
"16:9 (Wide)",
|
||||
"3:4 (Portrait)",
|
||||
"4:3 (Landscape)"
|
||||
], {
|
||||
"default": "1:1 (Square)",
|
||||
"tooltip": "Adjust the dimensions of the generated image. The width and height will be automatically adjusted based on the selected aspect ratio.",
|
||||
}),
|
||||
"seed": ("INT", {
|
||||
"tooltip": "A value used to randomize the image generation. If you want to make images reproducible (generate the same image multiple times), you can use the same seed value.",
|
||||
"default": 0,
|
||||
"min": 1,
|
||||
"max": 9223372036854776000,
|
||||
}),
|
||||
"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,
|
||||
"label_on": "Enabled",
|
||||
"label_off": "Disabled",
|
||||
}),
|
||||
"batchSize": ("INT", {
|
||||
"tooltip": "The number of images to generate in a single request.",
|
||||
"default": 1,
|
||||
"min": 1,
|
||||
"max": 10,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def VALIDATE_INPUTS(cls, positivePrompt):
|
||||
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!")
|
||||
return True
|
||||
|
||||
DESCRIPTION = "Generate Images Using Google Imagen Models."
|
||||
FUNCTION = "imagenInference"
|
||||
RETURN_TYPES = ("IMAGE", "RUNWARETASK")
|
||||
RETURN_NAMES = ("IMAGE", "RW-Task")
|
||||
CATEGORY = "Runware"
|
||||
|
||||
def imagenInference(self, **kwargs):
|
||||
runwareModel = kwargs.get("Model")
|
||||
runwareModel = runwareModel.split(" ")[0]
|
||||
positivePrompt = kwargs.get("positivePrompt")
|
||||
aspectRatio = kwargs.get("Aspect Ratio")
|
||||
multiInferenceMode = kwargs.get("Multi Inference Mode", False)
|
||||
seed = kwargs.get("seed")
|
||||
batchSize = kwargs.get("batchSize", 1)
|
||||
|
||||
aspectRatioMap = {
|
||||
"1:1": (1024, 1024),
|
||||
"9:16": (768, 1408),
|
||||
"16:9": (1408, 768),
|
||||
"3:4": (896, 1280),
|
||||
"4:3": (1280, 896),
|
||||
}
|
||||
|
||||
secAspectRatio = aspectRatio.split(" ")[0]
|
||||
width, height = aspectRatioMap.get(secAspectRatio, (1024, 1024))
|
||||
|
||||
genConfig = [
|
||||
{
|
||||
"taskType": "imageInference",
|
||||
"taskUUID": rwUtils.genRandUUID(),
|
||||
"positivePrompt": positivePrompt,
|
||||
"height": height,
|
||||
"width": width,
|
||||
"model": runwareModel,
|
||||
"seed": seed,
|
||||
"outputType": "base64Data",
|
||||
"outputFormat": rwUtils.OUTPUT_FORMAT,
|
||||
"outputQuality": rwUtils.OUTPUT_QUALITY,
|
||||
"numberResults": batchSize
|
||||
}
|
||||
]
|
||||
|
||||
# For Debugging Purposes Only
|
||||
print(f"[Debugging] Task UUID: {genConfig[0]['taskUUID']}")
|
||||
|
||||
if (multiInferenceMode):
|
||||
return (None, genConfig)
|
||||
else:
|
||||
genResult = rwUtils.inferenecRequest(genConfig)
|
||||
images = rwUtils.convertImageB64List(genResult)
|
||||
return (images, None)
|
||||
@@ -0,0 +1,75 @@
|
||||
from .utils import runwareUtils as rwUtils
|
||||
import folder_paths
|
||||
import os
|
||||
import requests
|
||||
from datetime import datetime
|
||||
|
||||
class RunwareSaveImage:
|
||||
"""Runware Save Image node that respects format from imageURL"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"Images": ("STRING", {
|
||||
"tooltip": "Image URL(s) from Runware Image Inference node. For multiple images, URLs should be comma-separated."
|
||||
}),
|
||||
"filename_prefix": ("STRING", {
|
||||
"default": "ComfyUI",
|
||||
"tooltip": "Prefix for the filename."
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ()
|
||||
FUNCTION = "save_images"
|
||||
OUTPUT_NODE = True
|
||||
CATEGORY = "Runware"
|
||||
|
||||
def save_images(self, Images, filename_prefix="ComfyUI"):
|
||||
"""Save images using URL directly"""
|
||||
|
||||
# Get output directory
|
||||
output_dir = folder_paths.get_output_directory()
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
# Split URLs if comma-separated (for batch processing)
|
||||
image_urls = [url.strip() for url in Images.split(",") if url.strip()]
|
||||
|
||||
if not image_urls:
|
||||
raise Exception("No image URL provided. Please connect the 'Image URL' output from Runware Image Inference node.")
|
||||
|
||||
saved_files = []
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
|
||||
# Process each image
|
||||
for i, image_url in enumerate(image_urls):
|
||||
# Download original file directly from URL to preserve quality
|
||||
response = requests.get(image_url, timeout=30)
|
||||
response.raise_for_status()
|
||||
img_data = response.content
|
||||
|
||||
# Get extension from URL (handle query parameters)
|
||||
url_without_params = image_url.split('?')[0]
|
||||
extension = "." + url_without_params.split('.')[-1]
|
||||
|
||||
# Generate filename with timestamp
|
||||
if len(image_urls) > 1:
|
||||
filename = f"{filename_prefix}_{timestamp}_{i+1:03}.{extension.lstrip('.')}"
|
||||
else:
|
||||
filename = f"{filename_prefix}_{timestamp}.{extension.lstrip('.')}"
|
||||
filepath = os.path.join(output_dir, filename)
|
||||
|
||||
# Write the downloaded file
|
||||
with open(filepath, 'wb') as f:
|
||||
f.write(img_data)
|
||||
print(f"[Runware] Saved original file from URL: {filepath}")
|
||||
|
||||
# Add preview data for ComfyUI
|
||||
saved_files.append({
|
||||
"filename": filename,
|
||||
"subfolder": "",
|
||||
"type": "output"
|
||||
})
|
||||
|
||||
return {"ui": {"images": saved_files}}
|
||||
+18
-17
@@ -127,7 +127,8 @@ class upscaler:
|
||||
|
||||
DESCRIPTION = "Enhance the resolution and quality of your images using Runware's advanced upscaling API. Transform low-resolution images into sharp, high-definition visuals."
|
||||
FUNCTION = "upscale"
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("Image",)
|
||||
CATEGORY = "Runware"
|
||||
|
||||
def upscale(self, **kwargs):
|
||||
@@ -168,28 +169,28 @@ class upscaler:
|
||||
"upscaleFactor": int(upscaleFactor),
|
||||
"outputFormat": rwUtils.OUTPUT_FORMAT,
|
||||
"outputQuality": rwUtils.OUTPUT_QUALITY,
|
||||
"outputType": "base64Data",
|
||||
"outputType": "URL",
|
||||
}
|
||||
|
||||
# Add model if specified
|
||||
if model:
|
||||
genConfig["model"] = model
|
||||
|
||||
# Add common parameters at top level - only add if their toggle is enabled
|
||||
if useSteps:
|
||||
genConfig["steps"] = steps
|
||||
if useSeed:
|
||||
genConfig["seed"] = seed
|
||||
if useCFGScale:
|
||||
genConfig["CFGScale"] = CFGScale
|
||||
if usePrompts:
|
||||
if positivePrompt:
|
||||
genConfig["positivePrompt"] = positivePrompt
|
||||
if negativePrompt:
|
||||
genConfig["negativePrompt"] = negativePrompt
|
||||
|
||||
# Build settings object for advanced parameters
|
||||
settings = {}
|
||||
|
||||
# Common parameters - only add if their toggle is enabled
|
||||
if useSteps:
|
||||
settings["steps"] = steps
|
||||
if useSeed:
|
||||
settings["seed"] = seed
|
||||
if useCFGScale:
|
||||
settings["CFGScale"] = CFGScale
|
||||
if usePrompts:
|
||||
if positivePrompt:
|
||||
settings["positivePrompt"] = positivePrompt
|
||||
if negativePrompt:
|
||||
settings["negativePrompt"] = negativePrompt
|
||||
|
||||
# Clarity upscaler specific parameters
|
||||
if useClarityParams:
|
||||
@@ -249,5 +250,5 @@ class upscaler:
|
||||
print(f"[DEBUG] Error in Image Upscale Request: {str(e)}", flush=True)
|
||||
raise
|
||||
|
||||
images = rwUtils.convertImageB64List(genResult)
|
||||
return (images, )
|
||||
images = rwUtils.extractImageURLs(genResult)
|
||||
return (images,)
|
||||
@@ -552,6 +552,15 @@ def convertIMG2Tensor(b64img):
|
||||
tensorImage = torch.from_numpy(imageNP).squeeze()
|
||||
return tensorImage
|
||||
|
||||
def extractImageURLs(imageDataObject):
|
||||
"""Extract image URLs from API response and return as comma-separated string"""
|
||||
image_urls = []
|
||||
for result in imageDataObject.get("data", []):
|
||||
imageURL = result.get("imageURL")
|
||||
if imageURL:
|
||||
image_urls.append(imageURL)
|
||||
return ",".join(image_urls) if image_urls else ""
|
||||
|
||||
def convertImageB64List(imageDataObject):
|
||||
images = ()
|
||||
for result in imageDataObject["data"]:
|
||||
@@ -566,8 +575,8 @@ def convertImageB64List(imageDataObject):
|
||||
generatedImage = convertIMG2Tensor(generatedImage)
|
||||
images += (generatedImage,)
|
||||
else:
|
||||
# If no base64 data, try to get image URL (check both imageURL and mediaURL)
|
||||
imageURL = result.get("imageURL") or result.get("mediaURL")
|
||||
# If no base64 data, try to get image URL
|
||||
imageURL = result.get("imageURL")
|
||||
if imageURL:
|
||||
# Download image from URL and convert to tensor
|
||||
try:
|
||||
|
||||
+6
-32
@@ -1,14 +1,4 @@
|
||||
from .utils import runwareUtils as rwUtils
|
||||
import base64
|
||||
import io
|
||||
|
||||
|
||||
class SVGData:
|
||||
"""Wrapper class to match RecraftIO.SVG format for SaveSVGNode"""
|
||||
|
||||
def __init__(self, svgContent):
|
||||
svgBytes = io.BytesIO(svgContent.encode('utf-8'))
|
||||
self.data = [svgBytes]
|
||||
|
||||
|
||||
class vectorize:
|
||||
@@ -35,12 +25,12 @@ class vectorize:
|
||||
|
||||
DESCRIPTION = "Convert images to vector format using Runware's vectorization service."
|
||||
FUNCTION = "vectorizeImage"
|
||||
RETURN_TYPES = ("SVG",)
|
||||
RETURN_NAMES = ("SVG",)
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("Image",)
|
||||
CATEGORY = "Runware"
|
||||
|
||||
def vectorizeImage(self, **kwargs):
|
||||
"""Vectorize image and return SVG data"""
|
||||
"""Vectorize image and return SVG URL"""
|
||||
image = kwargs.get("Image")
|
||||
modelName = kwargs.get("Model", "recraft:1@1")
|
||||
|
||||
@@ -59,9 +49,9 @@ class vectorize:
|
||||
|
||||
self._validateResponse(genResult)
|
||||
|
||||
svgData = self._extractSvgData(genResult)
|
||||
svg_url = rwUtils.extractImageURLs(genResult)
|
||||
|
||||
return (svgData,)
|
||||
return (svg_url,)
|
||||
|
||||
def _buildGenConfig(self, model, imageUuid):
|
||||
"""Build generation configuration for API request"""
|
||||
@@ -72,7 +62,7 @@ class vectorize:
|
||||
"inputs": {
|
||||
"image": imageUuid
|
||||
},
|
||||
"outputType": "base64Data",
|
||||
"outputType": "URL",
|
||||
"outputFormat": "svg",
|
||||
}]
|
||||
|
||||
@@ -81,22 +71,6 @@ class vectorize:
|
||||
if "errors" in genResult:
|
||||
errorMessage = genResult["errors"][0]["message"]
|
||||
raise Exception(f"Vectorization failed: {errorMessage}")
|
||||
|
||||
if "data" not in genResult or len(genResult["data"]) == 0:
|
||||
raise Exception("No data returned from vectorization API")
|
||||
|
||||
def _extractSvgData(self, genResult):
|
||||
"""Extract SVG data from API response"""
|
||||
resultData = genResult["data"][0]
|
||||
|
||||
if "imageBase64Data" not in resultData:
|
||||
raise Exception("imageBase64Data not found in response")
|
||||
|
||||
base64Svg = resultData["imageBase64Data"]
|
||||
svgContent = base64.b64decode(base64Svg).decode('utf-8')
|
||||
svgData = SVGData(svgContent)
|
||||
|
||||
return svgData
|
||||
|
||||
|
||||
# Node class mappings
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
python-dotenv
|
||||
brotli
|
||||
zstd
|
||||
librosa
|
||||
librosa
|
||||
imageio
|
||||
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
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
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
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
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
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
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
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
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
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
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
{"id":"993f4b88-acb5-4f70-968f-e3a2c9c1c5b4","revision":0,"last_node_id":7,"last_link_id":8,"nodes":[{"id":6,"type":"SaveSVGNode","pos":[447.0441589355469,567.7747192382812],"size":[270,270],"flags":{},"order":2,"mode":0,"inputs":[{"localized_name":"svg","name":"svg","type":"SVG","link":8},{"localized_name":"filename_prefix","name":"filename_prefix","type":"STRING","widget":{"name":"filename_prefix"},"link":null}],"outputs":[],"properties":{"cnr_id":"comfy-core","ver":"0.3.46","Node name for S&R":"SaveSVGNode"},"widgets_values":["svg/ComfyUI"]},{"id":4,"type":"LoadImage","pos":[-162.27017211914062,506.9244384765625],"size":[274.080078125,314.0000305175781],"flags":{},"order":0,"mode":0,"inputs":[{"localized_name":"image","name":"image","type":"COMBO","widget":{"name":"image"},"link":null},{"localized_name":"choose file to upload","name":"upload","type":"IMAGEUPLOAD","widget":{"name":"upload"},"link":null}],"outputs":[{"localized_name":"IMAGE","name":"IMAGE","type":"IMAGE","links":[7]},{"localized_name":"MASK","name":"MASK","type":"MASK","links":null}],"properties":{"cnr_id":"comfy-core","ver":"0.3.46","Node name for S&R":"LoadImage"},"widgets_values":["bulldog_1.webp","image"]},{"id":7,"type":"Runware Vectorize","pos":[146.1571044921875,546.4293823242188],"size":[270,58],"flags":{},"order":1,"mode":0,"inputs":[{"localized_name":"Image","name":"Image","type":"IMAGE","link":7},{"localized_name":"Model","name":"Model","type":"COMBO","widget":{"name":"Model"},"link":null}],"outputs":[{"localized_name":"SVG","name":"SVG","type":"SVG","links":[8]}],"properties":{"cnr_id":"ComfyUI-Runware","ver":"8d75a6933feda430268dd0c555ecde9ccd99c7ef","Node name for S&R":"Runware Vectorize"},"widgets_values":["recraft:1@1"],"bgcolor":"#5345bf"}],"links":[[7,4,0,7,0,"IMAGE"],[8,7,0,6,0,"SVG"]],"groups":[],"config":{},"extra":{"ds":{"scale":1.02884607845781,"offset":[476.52544538804847,-345.82928772954745]}},"version":0.4}
|
||||
{"id":"993f4b88-acb5-4f70-968f-e3a2c9c1c5b4","revision":0,"last_node_id":10,"last_link_id":11,"nodes":[{"id":9,"type":"Runware Save Image","pos":[452.51519775390625,576.1388549804688],"size":[270,82],"flags":{},"order":2,"mode":0,"inputs":[{"localized_name":"Images","name":"Images","type":"STRING","widget":{"name":"Images"},"link":11},{"localized_name":"filename_prefix","name":"filename_prefix","type":"STRING","widget":{"name":"filename_prefix"},"link":null}],"outputs":[],"properties":{"cnr_id":"ComfyUI-Runware","ver":"91646ada844167e36188b535ff60063735d10ca3","Node name for S&R":"Runware Save Image"},"widgets_values":["","ComfyUI"],"bgcolor":"#5345bf"},{"id":4,"type":"LoadImage","pos":[-162.27017211914062,506.9244384765625],"size":[274.080078125,314.0000305175781],"flags":{},"order":0,"mode":0,"inputs":[{"localized_name":"image","name":"image","type":"COMBO","widget":{"name":"image"},"link":null},{"localized_name":"choose file to upload","name":"upload","type":"IMAGEUPLOAD","widget":{"name":"upload"},"link":null}],"outputs":[{"localized_name":"IMAGE","name":"IMAGE","type":"IMAGE","links":[10]},{"localized_name":"MASK","name":"MASK","type":"MASK","links":null}],"properties":{"cnr_id":"comfy-core","ver":"0.3.46","Node name for S&R":"LoadImage"},"widgets_values":["bulldog_1.webp","image"]},{"id":10,"type":"Runware Vectorize","pos":[162.64442443847656,533.0270385742188],"size":[270,58],"flags":{},"order":1,"mode":0,"inputs":[{"localized_name":"Image","name":"Image","type":"IMAGE","link":10},{"localized_name":"Model","name":"Model","type":"COMBO","widget":{"name":"Model"},"link":null}],"outputs":[{"localized_name":"Image","name":"Image","type":"STRING","links":[11]}],"properties":{"cnr_id":"ComfyUI-Runware","ver":"91646ada844167e36188b535ff60063735d10ca3","Node name for S&R":"Runware Vectorize"},"widgets_values":["recraft:1@1"],"bgcolor":"#5345bf"}],"links":[[10,4,0,10,0,"IMAGE"],[11,10,0,9,0,"STRING"]],"groups":[],"config":{},"extra":{"ds":{"scale":1.02884607845781,"offset":[476.67256864649244,-345.82928772954745]}},"version":0.4}
|
||||
Reference in New Issue
Block a user