diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e1777d9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +__pycache__/ +*.py[cod] +*$py.class + + +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +cython_debug/ +.pyre/ diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..5a297e5 --- /dev/null +++ b/__init__.py @@ -0,0 +1,51 @@ +from .modules.imageInference import txt2img +from .modules.bgremoval import bgremoval +from .modules.photoMaker import photoMaker +from .modules.upscaler import upscaler +from .modules.modelSearch import modelSearch +from .modules.bridges import mainRoute +from .modules.controlNet import controlNet +from .modules.loraSearch import loraSearch +from .modules.loraCombine import loraCombine +from .modules.refiner import refiner +from .modules.imageMasking import imageMasking +from .modules.controlNetPreprocessor import controlNetPreprocessor +from .modules.apiManager import apiManager +from .modules.imageCaptioning import imageCaptioning +from .modules.controlNetCombine import controlNetCombine + +RUNWARE_COMFYUI_VERSION = "Alpha 0.8" + +RESET_COLOR = "\033[0m" +BLUE_COLOR = "\033[94m" +GREEN_COLOR = "\033[92m" +print(BLUE_COLOR + "##############################################################" + RESET_COLOR) +print(GREEN_COLOR + " Runware ComfyUI Inference Services Are Loaded Successfully" + RESET_COLOR) +print(GREEN_COLOR + " Version: " + RUNWARE_COMFYUI_VERSION + " | Maintained by: Runware Inc" + RESET_COLOR) +print(GREEN_COLOR + " Official Website: https://my.runware.ai" + RESET_COLOR) +print(BLUE_COLOR + "##############################################################" + RESET_COLOR) + +NODE_CLASS_MAPPINGS = { + "Runware Image Inference": txt2img, + "Runware Background Removal": bgremoval, + "Runware PhotoMaker V2": photoMaker, + "Runware Image Upscaler": upscaler, + "Runware Model Search": modelSearch, + "Runware Lora Search": loraSearch, + "Runware ControlNet": controlNet, + "Runware Lora Combine": loraCombine, + "Runware Refiner": refiner, + "Runware Image Masking": imageMasking, + "Runware ControlNet PreProcessor": controlNetPreprocessor, + "Runware API Manager": apiManager, + "Runware Image Caption": imageCaptioning, + "Runware ControlNet Combine": controlNetCombine, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "Runware Model Search": "Runware Model", + "Runware Lora Search": "Runware Lora", +} + +WEB_DIRECTORY = "./clientlibs" +__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"] \ No newline at end of file diff --git a/clientlibs/main.js b/clientlibs/main.js new file mode 100644 index 0000000..2fbb90e --- /dev/null +++ b/clientlibs/main.js @@ -0,0 +1,48 @@ +import { app } from "../../scripts/app.js"; +import { promptEnhanceHandler, syncDimensionsNodeHandler, searchNodeHandler, APIKeyHandler } from "./utils.js"; +import { RUNWARE_NODE_TYPES, RUNWARE_NODE_PROPS, SEARCH_TERMS } from "./types.js"; + +app.registerExtension({ + name: "runware.ai", + async setup() { + // pass + }, + + async nodeCreated(node) { + const nodeClass = node.comfyClass; + let crNodeProps = false; + if(typeof nodeClass === "string" && Object.values(RUNWARE_NODE_TYPES).includes(nodeClass)) { + crNodeProps = RUNWARE_NODE_PROPS[nodeClass]; + } else { + return; + } + + node.bgcolor = crNodeProps.bgColor; + if(nodeClass === RUNWARE_NODE_TYPES.APIMANAGER) { + APIKeyHandler(node); + } + + if(crNodeProps.colorModeOnly === true) return; + const nodeWidgets = node.widgets; + if(nodeWidgets.length <= 0) return; + + for(const nodeWidget of nodeWidgets) { + const widgetName = nodeWidget.name; + const widgetType = nodeWidget.type; + + if(crNodeProps.promptEnhancer === true && widgetType === "customtext") { + if (widgetName === "positivePrompt" || widgetName === "negativePrompt") { + nodeWidget.inputEl.addEventListener('keydown', promptEnhanceHandler); + } + } else if(crNodeProps.liveDimensions === true && widgetType === "combo" && widgetName === "dimensions") { + syncDimensionsNodeHandler(node, nodeWidget); + } + + if(crNodeProps.liveSearch === true) { + if(widgetType === "text" && SEARCH_TERMS.includes(widgetName)) { + searchNodeHandler(node, nodeWidget); + } + } + } + } +}) \ No newline at end of file diff --git a/clientlibs/types.js b/clientlibs/types.js new file mode 100644 index 0000000..cb89431 --- /dev/null +++ b/clientlibs/types.js @@ -0,0 +1,141 @@ +const DEFAULT_BGCOLOR = "#6c5ce7"; + +const DEFAULT_DIMENSIONS_LIST = { + "Square (512x512)": "512x512", + "Square HD (1024x1024)": "1024x1024", + "Portrait 3:4 (768x1024)": "768x1024", + "Portrait 9:16 (576x1024)": "576x1024", + "Landscape 4:3 (1024x768)": "1024x768", + "Landscape 16:9 (1024x576)": "1024x576" +}; + +const DEFAULT_MODELS_ARCH_LIST = { + "All": "all", + "FLUX.1-Schnell": "flux1s", + "FLUX.1-Dev": "flux1d", + "Pony": "pony", + "SD 1.5": "sd1x", + "SD 1.5 Hyper": "sdhyper", + "SD 1.5 LCM": "sd1xlcm", + "SD 3": "sd3", + "SDXL 1.0": "sdxl", + "SDXL 1.0 LCM": "sdxllcm", + "SDXL Distilled": "sdxldistilled", + "SDXL Hyper": "sdxlhyper", + "SDXL Lightning": "sdxllightning", + "SDXL Turbo": "sdxlturbo", +}; + +const DEFAULT_CONTROLNET_CONDITIONING_LIST = { + "All": "all", + "Canny": "canny", + "Depth": "depth", + "MLSD": "mlsd", + "Normal BAE": "normalbae", + "Open Pose": "openpose", + "Tile": "tile", + "Seg": "seg", + "Line Art": "lineart", + "Line Art Anime": "lineart_anime", + "Shuffle": "shuffle", + "Scribble": "scribble", + "Soft Edge": "softedge", +}; + +const SEARCH_TERMS = ["Model Search", "Lora Search", "ControlNet Search"]; +const MODEL_TYPES_TERMS = ["ModelType", "LoraType", "ControlNetType"]; +const MODEL_LIST_TERMS = ["ModelList", "LoraList", "ControlNetList"]; + +const RUNWARE_NODE_TYPES = { + IMAGEINFERENCE: "Runware Image Inference", + PHOTOMAKER: "Runware PhotoMaker V2", + MODELSEARCH: "Runware Model Search", + LORASEARCH: "Runware Lora Search", + CONTROLNET: "Runware ControlNet", + BGREMOVAL: "Runware Background Removal", + UPSCALER: "Runware Image Upscaler", + REFINER: "Runware Refiner", + LORACOMBINE: "Runware Lora Combine", + CONTROLNETCOMBINE: "Runware ControlNet Combine", + IMAGEMASKING: "Runware Image Masking", + CONTROLNETPREPROCESSING: "Runware ControlNet PreProcessor", + APIMANAGER: "Runware API Manager", + IMAGECAPTION: "Runware Image Caption", +}; + +const RUNWARE_NODE_PROPS = { + [RUNWARE_NODE_TYPES.IMAGEINFERENCE]: { + bgColor: DEFAULT_BGCOLOR, + liveDimensions: true, + promptEnhancer: true, + }, + [RUNWARE_NODE_TYPES.PHOTOMAKER]: { + bgColor: DEFAULT_BGCOLOR, + liveDimensions: true, + promptEnhancer: true, + }, + [RUNWARE_NODE_TYPES.MODELSEARCH]: { + bgColor: DEFAULT_BGCOLOR, + liveSearch: true, + }, + [RUNWARE_NODE_TYPES.LORASEARCH]: { + bgColor: DEFAULT_BGCOLOR, + liveSearch: true, + }, + [RUNWARE_NODE_TYPES.CONTROLNET]: { + bgColor: DEFAULT_BGCOLOR, + liveSearch: true, + }, + [RUNWARE_NODE_TYPES.CONTROLNETCONDITIONING]: { + bgColor: DEFAULT_BGCOLOR, + liveSearch: true, + }, + [RUNWARE_NODE_TYPES.BGREMOVAL]: { + bgColor: DEFAULT_BGCOLOR, + colorModeOnly: true, + }, + [RUNWARE_NODE_TYPES.UPSCALER]: { + bgColor: DEFAULT_BGCOLOR, + colorModeOnly: true, + }, + [RUNWARE_NODE_TYPES.REFINER]: { + bgColor: DEFAULT_BGCOLOR, + colorModeOnly: true, + }, + [RUNWARE_NODE_TYPES.LORACOMBINE]: { + bgColor: DEFAULT_BGCOLOR, + colorModeOnly: true, + }, + [RUNWARE_NODE_TYPES.CONTROLNETCOMBINE]: { + bgColor: DEFAULT_BGCOLOR, + colorModeOnly: true, + }, + [RUNWARE_NODE_TYPES.IMAGEMASKING]: { + bgColor: DEFAULT_BGCOLOR, + colorModeOnly: true, + }, + [RUNWARE_NODE_TYPES.CONTROLNETPREPROCESSING]: { + bgColor: DEFAULT_BGCOLOR, + liveDimensions: true, + }, + [RUNWARE_NODE_TYPES.APIMANAGER]: { + bgColor: DEFAULT_BGCOLOR, + colorModeOnly: true, + }, + [RUNWARE_NODE_TYPES.IMAGECAPTION]: { + bgColor: DEFAULT_BGCOLOR, + colorModeOnly: true, + }, +}; + +export { + DEFAULT_BGCOLOR, + DEFAULT_DIMENSIONS_LIST, + DEFAULT_MODELS_ARCH_LIST, + DEFAULT_CONTROLNET_CONDITIONING_LIST, + SEARCH_TERMS, + MODEL_TYPES_TERMS, + MODEL_LIST_TERMS, + RUNWARE_NODE_TYPES, + RUNWARE_NODE_PROPS +} \ No newline at end of file diff --git a/clientlibs/utils.js b/clientlibs/utils.js new file mode 100644 index 0000000..1d1c376 --- /dev/null +++ b/clientlibs/utils.js @@ -0,0 +1,249 @@ +import { app } from "../../scripts/app.js"; +import { api } from "../../scripts/api.js"; +import { DEFAULT_DIMENSIONS_LIST, DEFAULT_MODELS_ARCH_LIST, SEARCH_TERMS, + DEFAULT_CONTROLNET_CONDITIONING_LIST, RUNWARE_NODE_TYPES, MODEL_TYPES_TERMS, MODEL_LIST_TERMS +} from "./types.js"; + +async function enhancePrompt(userPrompt) { + const resp = await api.fetchApi('/promptEnhance', { + method: "POST", + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({ + userPrompt: userPrompt + }) + }); + const enhancePromptResults = await resp.json(); + return enhancePromptResults; +} + +async function setAPIKey(apiKey) { + const resp = await api.fetchApi('/setAPIKey', { + method: "POST", + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({ + apiKey: apiKey + }) + }); + const setAPIKeyResults = await resp.json(); + return setAPIKeyResults; +} + +async function modelSearch(modelQuery = "", modelArch = "all", modelType = "base", modelCat = "checkpoint", condtioning = "") { + const resp = await api.fetchApi('/modelSearch', { + method: "POST", + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({ + modelQuery: modelQuery, + modelArch: modelArch, + modelType: modelType, + modelCat: modelCat, + condtioning: condtioning + }) + }); + const modelSearchResults = await resp.json(); + return modelSearchResults; +} + +function notifyUser(message, type="info", title = "Runware", life = 4.5) { + app.extensionManager.toast.add({ + severity: type, // 'info', 'success', 'warn', 'error' \\ + summary: title, + detail: message, + life: Math.floor(life * 1000) + }); +} + +async function promptEnhanceHandler(e) { + if(e.altKey && e.shiftKey && e.key === 'E') { + const userPrompt = e.target.value.trim(); + if(userPrompt.length <= 1) { + notifyUser("Prompt Is Too Short!", "error", "Runware Prompt Enhancer"); + return; + } else if(userPrompt.length > 300) { + notifyUser("Prompt Must Not Exceed 300 Characters!", "error", "Runware Prompt Enhancer"); + return; + } + const enhanceResults = await enhancePrompt(userPrompt); + if(enhanceResults.success) { + e.target.value = enhanceResults.enhancedPrompt; + } else { + notifyUser(enhanceResults.error, "error", "Runware Prompt Enhancer"); + } + } +} + +function remNode(node) { + app.graph.remove(node); + app.graph.setDirtyCanvas(true,true); +} + +async function APIKeyHandler(apiManagerNode) { + const widgets = apiManagerNode.widgets; + for(const widget of widgets) { + if(widget.name === "API Key") { + appendWidgetCB(widget, async function(...args) { + const apiKey = args[0].trim(); + if(apiKey.length < 30) { + notifyUser("Invalid API Key Set, Please Try Again!", "error", "Runware API Manager"); + return; + } else { + const setAPIKeyResults = await setAPIKey(apiKey); + if(setAPIKeyResults.success) { + remNode(apiManagerNode); + notifyUser("API Key Set Successfully!", "success", "Runware API Manager"); + } else { + notifyUser(setAPIKeyResults.error, "error", "Runware API Manager"); + } + } + }); + break; + } + } +} + +function appendWidgetCB(node, newCB) { + const oldNodeCB = node.callback; + if(typeof oldNodeCB === "function") { + node.callback = function(...args) { + newCB?.apply(this, args); + oldNodeCB?.apply(this, args); + } + } else { + node.callback = newCB; + } +} + +async function syncDimensionsNodeHandler(node, dimensionsWidget) { + const nodeWidgets = node.widgets; + let widthWidget = false, heightWidget = false; + for(const nodeWidget of nodeWidgets) { + const widgetName = nodeWidget.name; + const widgetType = nodeWidget.type; + if(widgetName === "width" && widgetType === "number") widthWidget = nodeWidget; + if(widgetName === "height" && widgetType === "number") heightWidget = nodeWidget; + } + + appendWidgetCB(dimensionsWidget, function(...args) { + const chosenDimension = args[0]; + if(chosenDimension === "Custom") return; + const dimensionValue = DEFAULT_DIMENSIONS_LIST[chosenDimension]; + const [width, height] = dimensionValue.split("x"); + widthWidget.callback(width, "customSetOperation"); + heightWidget.callback(height, "customSetOperation"); + }); + + if(widthWidget !== false) { + appendWidgetCB(widthWidget, function(...args) { + if(args[1] === "customSetOperation") return; + dimensionsWidget.value = "Custom"; + }); + } + + if(heightWidget !== false) { + appendWidgetCB(heightWidget, function(...args) { + if(args[1] === "customSetOperation") return; + dimensionsWidget.value = "Custom"; + }); + } +} + +async function searchNodeHandler(searchNode, searchInputWidget) { + const searchNodeWidgets = searchNode.widgets; + let modelArchWidget = false, modelTypeWidget = false, modelListWidget = false, defaultWidgetValues = {}; + let isLora = false, isControlNet = false, modelTypeValue; + if(searchNode.comfyClass === RUNWARE_NODE_TYPES.LORASEARCH) isLora = true; + if(searchNode.comfyClass === RUNWARE_NODE_TYPES.CONTROLNET) isControlNet = true; + for(const searchWidget of searchNodeWidgets) { + const widgetName = searchWidget.name; + const widgetType = searchWidget.type; + if(widgetName === "Model Architecture" && widgetType === "combo") { + modelArchWidget = searchWidget; + defaultWidgetValues["modelArch"] = searchWidget.options.values; + } + if(MODEL_TYPES_TERMS.includes(widgetName) && widgetType === "combo") { + modelTypeWidget = searchWidget; + defaultWidgetValues["modelType"] = searchWidget.options.values; + } + if(MODEL_LIST_TERMS.includes(widgetName) && widgetType === "combo") { + modelListWidget = searchWidget; + defaultWidgetValues["modelList"] = searchWidget.options.values; + } + } + + function resetValues() { + if(modelArchWidget) { + modelArchWidget.options.values = defaultWidgetValues["modelArch"]; + modelArchWidget.value = defaultWidgetValues["modelArch"][0]; + } + if(modelTypeWidget) { + modelTypeWidget.options.values = defaultWidgetValues["modelType"]; + modelTypeWidget.value = defaultWidgetValues["modelType"][0]; + } + if(modelListWidget) { + modelListWidget.options.values = defaultWidgetValues["modelList"]; + modelListWidget.value = defaultWidgetValues["modelList"][0]; + } + } + + async function searchModels() { + const searchQuery = searchInputWidget.value.trim(); + const modelArchValue = DEFAULT_MODELS_ARCH_LIST[modelArchWidget.value]; + if(isControlNet) { + modelTypeValue = DEFAULT_CONTROLNET_CONDITIONING_LIST[modelTypeWidget.value]; + } else { + modelTypeValue = modelTypeWidget.value.toLowerCase().replace("model", "").trim(); + } + + let modelSearchResults = null; + if(isControlNet) { + modelSearchResults = await modelSearch(searchQuery, modelArchValue, "", "controlnet", modelTypeValue); + } else if(isLora) { + modelSearchResults = await modelSearch(searchQuery, modelArchValue, "", modelTypeValue); + } else { + modelSearchResults = await modelSearch(searchQuery, modelArchValue, modelTypeValue); + } + + if(modelSearchResults.success) { + const modelListArray = []; + modelSearchResults.modelList.forEach(modelObj => { + modelListArray.push(`${modelObj.air} (${modelObj.name} ${modelObj.version})`); + }); + modelListWidget.options.values = modelListArray; + modelListWidget.value = modelListArray[0]; + } else { + notifyUser(modelSearchResults.error, "error", "Runware Search"); + } + } + + appendWidgetCB(searchInputWidget, async function(...args) { + const searchQuery = args[0].trim(); + if(searchQuery.length === 0) { + resetValues(); + } else if(searchQuery.length < 2 || searchQuery.length > 32) { + notifyUser("Invalid Search Value, Search Query must be between 2 and 32 characters!", "error", "Runware Search"); + return; + } else { + await searchModels(); + } + }); + + appendWidgetCB(modelArchWidget, async function(...args) { + await searchModels(); + }); + + if(!isLora){ + appendWidgetCB(modelTypeWidget, async function(...args) { + await searchModels(); + }); + } +} + +export { + enhancePrompt, + modelSearch, + notifyUser, + promptEnhanceHandler, + syncDimensionsNodeHandler, + searchNodeHandler, + APIKeyHandler, +}; \ No newline at end of file diff --git a/modules/apiManager.py b/modules/apiManager.py new file mode 100644 index 0000000..c772278 --- /dev/null +++ b/modules/apiManager.py @@ -0,0 +1,15 @@ +class apiManager: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "API Key": ("STRING", { + "tooltip": "Set Your Runware API Key To Start Using Runware Image Inference Services.\n\n(eg: BcVr1JVQM8FGzrRwb3GsbCq5ww1QwabV)\n\nIf You Don't Have One, You Can Get It From: https://my.runware.ai/keys", + }), + }, + } + + DESCRIPTION = "API Managers is a Runware Utility That helps you set your API Key From The ComfyUI Interface Without having to adjust the env file locally." + CATEGORY = "Runware" + RETURN_TYPES = () + OUTPUT_NODE = True \ No newline at end of file diff --git a/modules/bgremoval.py b/modules/bgremoval.py new file mode 100644 index 0000000..5bdc1fb --- /dev/null +++ b/modules/bgremoval.py @@ -0,0 +1,86 @@ +from .utils import runwareUtils as rwUtils + +class bgremoval: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "Image": ("IMAGE", { + "tooltip": "Specifies the input image to be processed." + }), + }, + "optional": { + "Post Process Mask": ("BOOLEAN", { + "tooltip": "Controls whether the mask should undergo additional post-processing. This step can improve the accuracy and quality of the background removal mask.", + "default": False, + "label_on": "Enabled", + "label_off": "Disabled", + }), + "Return Only Mask": ("BOOLEAN", { + "tooltip": "Whether to return only the mask. The mask is the opposite of the image background removal.", + "default": False, + "label_on": "Enabled", + "label_off": "Disabled", + }), + "Alpha Matting": ("BOOLEAN", { + "tooltip": "Alpha matting is a post-processing technique that enhances the quality of the output by refining the edges of the foreground object.", + "default": False, + "label_on": "Enabled", + "label_off": "Disabled", + }), + "Alpha Matting Foreground Threshold": ("INT", { + "tooltip": "Threshold value used in alpha matting to distinguish the foreground from the background. Adjusting this parameter affects the sharpness and accuracy of the foreground object edges.", + "default": 240, + "min": 1, + "max": 255, + }), + "Alpha Matting Background Threshold": ("INT", { + "tooltip": "Threshold value used in alpha matting to refine the background areas. It influences how aggressively the algorithm removes the background while preserving image details. The higher the value, the more computation is needed and therefore the more expensive the operation is.", + "default": 10, + "min": 1, + "max": 255, + }), + "Alpha Matting Erode Size": ("INT", { + "tooltip": "Specifies the size of the erosion operation used in alpha matting. Erosion helps in smoothing the edges of the foreground object for a cleaner removal of the background.", + "default": 10, + "min": 1, + "max": 255, + }), + } + } + + DESCRIPTION = "Remove backgrounds from images effortlessly using Runware's low-cost image editing Inference." + FUNCTION = "rembg" + RETURN_TYPES = ("IMAGE",) + CATEGORY = "Runware" + + def rembg(self, **kwargs): + image = kwargs.get("Image") + postProcessMask = kwargs.get("Post Process Mask", False) + returnOnlyMask = kwargs.get("Return Only Mask", False) + alphaMatting = kwargs.get("Alpha Matting", False) + alphaMattingForegroundThreshold = kwargs.get("Alpha Matting Foreground Threshold", 240) + alphaMattingBackgroundThreshold = kwargs.get("Alpha Matting Background Threshold", 10) + alphaMattingErodeSize = kwargs.get("Alpha Matting Erode Size", 10) + + genConfig = [ + { + "taskType": "imageBackgroundRemoval", + "taskUUID": rwUtils.genRandUUID(), + "inputImage": rwUtils.convertTensor2IMG(image), + "postProcessMask": postProcessMask, + "returnOnlyMask": returnOnlyMask, + "alphaMatting": alphaMatting, + "outputFormat": "WEBP", + "outputType": "base64Data", + } + ] + + if(alphaMatting): + genConfig[0]["alphaMattingForegroundThreshold"] = alphaMattingForegroundThreshold + genConfig[0]["alphaMattingBackgroundThreshold"] = alphaMattingBackgroundThreshold + genConfig[0]["alphaMattingErodeSize"] = alphaMattingErodeSize + + genResult = rwUtils.inferenecRequest(genConfig) + images = rwUtils.convertImageB64List(genResult) + return images \ No newline at end of file diff --git a/modules/bridges/mainRoute.py b/modules/bridges/mainRoute.py new file mode 100644 index 0000000..9805672 --- /dev/null +++ b/modules/bridges/mainRoute.py @@ -0,0 +1,73 @@ +from ..utils import runwareUtils as rwUtils +from server import PromptServer +from aiohttp import web + +routes = PromptServer.instance.routes + +@routes.post('/setAPIKey') +async def setAPIKey(reqPayload): + reqData = await reqPayload.json() + apiKey = reqData.get('apiKey', None) + if(apiKey is None or apiKey == "" or len(apiKey) < 30): + return web.json_response({'success': False, 'error': 'API Key Not Provided!'}) + try: + apiKey = apiKey.strip() + rwUtils.setAPIKey(apiKey) + except Exception as e: + return web.json_response({'success': False, 'error': str(e)}) + return web.json_response({'success': True}) + +@routes.post('/promptEnhance') +async def promptEnhance(reqPayload): + reqData = await reqPayload.json() + userPrompt = reqData.get('userPrompt') + utilityConfig = [{ + "taskType": "promptEnhance", + "taskUUID": rwUtils.genRandUUID(), + "prompt": userPrompt, + "promptMaxLength": 300, + "promptVersions": 1, + }] + + try: + utilityResults = rwUtils.inferenecRequest(utilityConfig) + except Exception as e: + return web.json_response({'success': False, 'error': str(e)}) + enhancedPrompt = utilityResults["data"][0]["text"] + return web.json_response({'success': True, 'enhancedPrompt': enhancedPrompt}) + +@routes.post('/modelSearch') +async def modelSearch(reqPayload): + reqData = await reqPayload.json() + modelQuery = reqData.get('modelQuery', "") + modelArch = reqData.get('modelArch', "all") + modelCategory = reqData.get('modelCat', "checkpoint") + modelType = reqData.get('modelType', "base") + controlNetConditioning = reqData.get('condtioning', "all") + + utilityConfig = [{ + "taskType": "modelSearch", + "taskUUID": rwUtils.genRandUUID(), + "category": modelCategory, + "limit": 15, + }] + + if(modelCategory != "controlnet" and modelCategory != "lora"): + utilityConfig[0]["type"] = modelType + elif(modelCategory == "controlnet" and controlNetConditioning != "all"): + utilityConfig[0]["conditioning"] = controlNetConditioning + + if(modelArch != "all"): + utilityConfig[0]["architecture"] = modelArch + if(modelQuery != ""): + utilityConfig[0]["search"] = modelQuery + + try: + utilityResults = rwUtils.inferenecRequest(utilityConfig) + except Exception as e: + return web.json_response({'success': False, 'error': str(e)}) + totalResults = utilityResults["data"][0]["totalResults"] + if(totalResults < 1): + return web.json_response({'success': False, 'error': 'No Results Found!'}) + modelList = utilityResults["data"][0]["results"] + return web.json_response({'success': True, 'modelList': modelList}) \ No newline at end of file diff --git a/modules/controlNet.py b/modules/controlNet.py new file mode 100644 index 0000000..68be5cc --- /dev/null +++ b/modules/controlNet.py @@ -0,0 +1,155 @@ +from .utils import runwareUtils as rwUtils + +class controlNet: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "Guide Image": ("IMAGE", { + "tooltip": "Specifies the preprocessed image to be used as guide to control the image generation process." + }), + "ControlNet Search": ("STRING", { + "tooltip": "Searchg For Specific ControlNet By Name Or Civit AIR Code (eg: Canny).", + }), + "Model Architecture": ([ + "All", + "FLUX.1-Schnell", + "FLUX.1-Dev", + "Pony", + "SD 1.5", + "SD 1.5 Hyper", + "SD 1.5 LCM", + "SD 3", + "SDXL 1.0", + "SDXL 1.0 LCM", + "SDXL Distilled", + "SDXL Hyper", + "SDXL Lightning", + "SDXL Turbo", + ], { + "tooltip": "Choose ControlNet Model Architecture To Filter Results.", + "default": "All", + }), + "ControlNetType": ([ + "All", + "Canny", + "Depth", + "MLSD", + "Normal BAE", + "Open Pose", + "Tile", + "Seg", + "Line Art", + "Line Art Anime", + "Shuffle", + "Scribble", + "Soft Edge", + ], { + "tooltip": "Choose ControlNet Type To Filter Results.", + "default": "All", + }), + "ControlNetList": ([ + "civitai:38784@44716 (SD1.5 Canny)", + "civitai:38784@44876 (SD1.5 Inpaint)", + "civitai:38784@44877 (SD1.5 Lineart)", + "civitai:38784@44795 (SD1.5 MLSD)", + "civitai:38784@44774 (SD1.5 NormalBAE)", + "runware:20@1 (SDXL Canny)", + "runware:25@1 (Flux Dev Canny)", + "runware:26@1 (Flux Dev Tile)", + "runware:28@1 (Flux Dev Blur)", + "runware:29@1 (Flux Dev OpenPose)", + "runware:30@1 (Flux Dev Gray)", + "runware:31@1 (Flux Dev Low Quality)", + ], { + "tooltip": "ControlNet Results Will Show UP Here So You Could Choose From.", + "default": "civitai:38784@44716 (SD1.5 Canny)", + }), + "startStep": ("INT", { + "tooltip": "Represents the step number at which the ControlNet model starts to control the inference process. (Enter -1 To Disable)", + "min": -1, + "max": 99, + "default": -1, + }), + "startStepPercentage": ("INT", { + "tooltip": "Represents the percentage of steps at which the ControlNet model starts to control the inference process. (Enter -1 To Disable)", + "min": -1, + "max": 99, + "default": 0, + }), + "endStep": ("INT", { + "tooltip": "Represents the step number at which the ControlNet preprocessor ends to control the inference process. (Enter -1 To Disable)", + "min": -1, + "max": 100, + "default": -1, + }), + "endStepPercentage": ("INT", { + "tooltip": "Represents the percentage of steps at which the ControlNet model ends to control the inference process. (Enter -1 To Disable)", + "min": -1, + "max": 100, + "default": 80, + }), + "Control Mode": (["prompt", "controlnet", "balanced"], { + "tooltip": "Choose Control Mode To Control The Inference Process.", + "default": "balanced", + }), + "weight": ("FLOAT", { + "tooltip": "Represents the weight (strength) of the ControlNet model in the image.", + "default": 1.0, + "min": 0, + "max": 1.0, + "step": 0.1, + }), + }, + } + + DESCRIPTION = "Directly Search and Configure ControlNet Guidance to Connect It With Runware Image Inference Nodes In ComfyUI." + FUNCTION = "controlNet" + RETURN_TYPES = ("RUNWARECONTROLNET",) + RETURN_NAMES = ("Runware ControlNet",) + CATEGORY = "Runware" + + @classmethod + def VALIDATE_INPUTS(cls, ControlNetList, startStep, startStepPercentage, endStep, endStepPercentage): + if((startStep == -1 and startStepPercentage == -1) or (startStep != -1 and startStepPercentage != -1)): + raise Exception("Error: Please Provide Either Start Step or Start Step Percentage!") + if((endStep == -1 and endStepPercentage == -1) or (endStep != -1 and endStepPercentage != -1)): + raise Exception("Error: Please Provide Either End Step or End Step Percentage!") + if((startStep != -1 and endStep != -1) and (startStep > endStep)): + raise Exception("Error: Start Step Cannot Be Greater Than End Step!") + elif((startStep != -1 and endStep != -1) and (startStep == endStep)): + raise Exception("Error: Start Step Cannot Be Equal To End Step!") + if((startStepPercentage != -1 and endStepPercentage != -1) and (startStepPercentage > endStepPercentage)): + raise Exception("Error: Start Step Percentage Cannot Be Greater Than End Step Percentage!") + elif((startStepPercentage != -1 and endStepPercentage != -1) and (startStepPercentage == endStepPercentage)): + raise Exception("Error: Start Step Percentage Cannot Be Equal To End Step Percentage!") + return True + + def controlNet(self, **kwargs): + guideImage = kwargs.get("Guide Image") + currentModel = kwargs.get("ControlNetList") + controlNetAIRCode = currentModel.split(" ")[0] + startStep = kwargs.get("startStep") + startStepPercentage = kwargs.get("startStepPercentage") + endStep = kwargs.get("endStep") + endStepPercentage = kwargs.get("endStepPercentage") + controlMode = kwargs.get("Control Mode") + weight = kwargs.get("weight") + + controlNetGuideOBJ = [{ + "model": controlNetAIRCode, + "guideImage": rwUtils.convertTensor2IMG(guideImage), + "weight": round(weight, 2), + "controlMode": controlMode, + }] + + if(startStep != -1): + controlNetGuideOBJ[0]["startStep"] = startStep + elif(startStepPercentage != -1): + controlNetGuideOBJ[0]["startStepPercentage"] = startStepPercentage + if(endStep != -1): + controlNetGuideOBJ[0]["endStep"] = endStep + elif(endStepPercentage != -1): + controlNetGuideOBJ[0]["endStepPercentage"] = endStepPercentage + + return (controlNetGuideOBJ,) \ No newline at end of file diff --git a/modules/controlNetCombine.py b/modules/controlNetCombine.py new file mode 100644 index 0000000..2fd2adb --- /dev/null +++ b/modules/controlNetCombine.py @@ -0,0 +1,37 @@ +class controlNetCombine: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "ControlNet 1": ("RUNWARECONTROLNET", { + "tooltip": "Connect a Runware ControlNet Node.", + }), + }, + "optional": { + "ControlNet 2": ("RUNWARECONTROLNET", { + "tooltip": "Connect a Runware ControlNet Node.", + }), + "ControlNet 3": ("RUNWARECONTROLNET", { + "tooltip": "Connect a Runware ControlNet Node.", + }), + }, + } + + DESCRIPTION = "Combine One or More ControlNet's To Guide Image Generation Process in Runware Image Inference." + FUNCTION = "controlNetCombine" + RETURN_TYPES = ("RUNWARECONTROLNET",) + RETURN_NAMES = ("Runware ControlNet's",) + CATEGORY = "Runware" + + def controlNetCombine(self, **kwargs): + controlNet1 = kwargs.get("ControlNet 1") + controlNet2 = kwargs.get("ControlNet 2", None) + controlNet3 = kwargs.get("ControlNet 3", None) + + controlNetObjectArray = controlNet1 + if(controlNet2 is not None): + controlNetObjectArray = controlNetObjectArray + controlNet2 + if(controlNet3 is not None): + controlNetObjectArray = controlNetObjectArray + controlNet3 + print(controlNetObjectArray) + return (controlNetObjectArray,) \ No newline at end of file diff --git a/modules/controlNetPreprocessor.py b/modules/controlNetPreprocessor.py new file mode 100644 index 0000000..4b61601 --- /dev/null +++ b/modules/controlNetPreprocessor.py @@ -0,0 +1,118 @@ +from .utils import runwareUtils as rwUtils + +class controlNetPreprocessor: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "Image": ("IMAGE", { + "tooltip": "Specifies the input image to be preprocessed to generate a guide image. This guide image will be used as a reference for image generation processes, guiding the AI to generate images that are more aligned with the input image." + }), + "PreProcessor Type": ([ + "Canny", + "Depth", + "MLSD", + "NormalBAE", + "OpenPose", + "Tile", + "Seg", + "LineArt", + "LineArtAnime", + "Shuffle", + "Scribble", + "SoftEdge", + ], { + "tooltip": "Choose Preprocessor Type To Generate The Guide Image.", + "default": "Canny", + }), + "Include Hands And Faces [ OpenPose ]": ("BOOLEAN", { + "default": False, + "tooltip": "Include the hands and face in the pose outline when using the OpenPose preprocessor.", + "label_on": "Enabled", + "label_off": "Disabled", + }), + "Canny Low Threshold": ("INT", { + "tooltip": "Defines the lower threshold when using the Canny edge detection preprocessor.", + "default": 100, + "min": 0, + "max": 255, + }), + "Canny High Threshold": ("INT", { + "tooltip": "Defines the high threshold when using the Canny edge detection preprocessor.", + "default": 200, + "min": 0, + "max": 255, + }), + "Image Resizing": ("BOOLEAN", { + "default": False, + "tooltip": "It Allows Image Resizing if the dimension of the image is higher than a specific value it will be resized.", + "label_on": "Enabled", + "label_off": "Disabled", + }), + "dimensions": ([ + "Square (512x512)", "Square HD (1024x1024)", "Portrait 3:4 (768x1024)", + "Portrait 9:16 (576x1024)", "Landscape 4:3 (1024x768)", + "Landscape 16:9 (1024x576)", "Custom" + ], { + "default": "Square HD (1024x1024)", + "tooltip": "Resize the dimensions of the generated Guide image by specifying its width and height in pixels, or select from the predefined options. Image dimensions must be multiples of 64 (e.g., 512x512, 1024x768).", + }), + "width": ("INT", { + "tooltip": "If the Image height dimension is larger than this value, the output image will be resized to the specified height.", + "default": 1024, + "min": 512, + "max": 2048, + "step": 64, + }), + "height": ("INT", { + "tooltip": "If the Image width dimension is larger than this value, the output image will be resized to the specified width.", + "default": 1024, + "min": 512, + "max": 2048, + "step": 64, + }), + }, + } + + 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_NAMES = ("GUIDE IMAGE",) + CATEGORY = "Runware" + + def controlNetPreProcess(self, **kwargs): + image = kwargs.get("Image") + preProcessorType = kwargs.get("PreProcessor Type") + includeHandsAndFaces = kwargs.get("Include Hands And Faces [ OpenPose ]") + cannyLowThreshold = kwargs.get("Canny Low Threshold") + cannyHighThreshold = kwargs.get("Canny High Threshold") + imageResizing = kwargs.get("Image Resizing") + width = kwargs.get("width") + height = kwargs.get("height") + + genConfig = [ + { + "taskType": "imageControlNetPreProcess", + "taskUUID": rwUtils.genRandUUID(), + "inputImage": rwUtils.convertTensor2IMG(image), + "preProcessorType": preProcessorType, + "outputFormat": "WEBP", + "outputType": "base64Data", + } + ] + + # For Debugging Purposes Only + print(f"[Debugging] Task UUID: {genConfig[0]['taskUUID']}") + + if(imageResizing): + genConfig[0]["width"] = width + genConfig[0]["height"] = height + if(preProcessorType == "OpenPose" and includeHandsAndFaces): + genConfig[0]["includeHandsAndFaceOpenPose"] = includeHandsAndFaces + elif(preProcessorType == "Canny"): + genConfig[0]["lowThresholdCanny"] = cannyLowThreshold + genConfig[0]["highThresholdCanny"] = cannyHighThreshold + + genResult = rwUtils.inferenecRequest(genConfig) + images = rwUtils.convertImageB64List(genResult) + return images \ No newline at end of file diff --git a/modules/imageCaptioning.py b/modules/imageCaptioning.py new file mode 100644 index 0000000..12e74ac --- /dev/null +++ b/modules/imageCaptioning.py @@ -0,0 +1,42 @@ +from .utils import runwareUtils as rwUtils + +class imageCaptioning: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "Image": ("IMAGE", { + "tooltip": "Specifies the input image to be processed." + }), + }, + "optional": { + "Image Description": ("STRING", { + "multiline": True, + "placeholder": "Don't Write Anything Here, The Image Description Will Be Generated Automatically.", + }), + }, + "hidden": { "node_id": "UNIQUE_ID" } + } + + DESCRIPTION = "Image to text, also known as image captioning, allows you to obtain descriptive text prompts based on uploaded or previously generated images. This process is instrumental in generating textual descriptions that can be used to create additional images or provide detailed insights into visual content." + FUNCTION = "imageCaptioning" + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("IMAGE PROMPT",) + CATEGORY = "Runware" + OUTPUT_NODE = True + + def imageCaptioning(self, **kwargs): + print(kwargs) + image = kwargs.get("Image") + kwargs.set("Image Description", "Don't Write Anything Here, The Image Description Will Be Generated Automatically.") + genConfig = [ + { + "taskType": "imageCaption", + "taskUUID": rwUtils.genRandUUID(), + "inputImage": rwUtils.convertTensor2IMG(image) + } + ] + + genResult = rwUtils.inferenecRequest(genConfig) + genText = genResult["data"][0]["text"] + return (genText, ) \ No newline at end of file diff --git a/modules/imageInference.py b/modules/imageInference.py new file mode 100644 index 0000000..c373883 --- /dev/null +++ b/modules/imageInference.py @@ -0,0 +1,211 @@ +from .utils import runwareUtils as rwUtils + +class txt2img: + @classmethod + 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!", + "tooltip": "Positive Prompt: a text instruction to guide the model on generating the image. You Can Also Press (Ctrl + Alt + E) To Enhance The Prompt!" + }), + "negativePrompt": ("STRING", { + "multiline": True, + "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." + }), + "Prompt Weighting": ("BOOLEAN", { + "default": False, + "tooltip": "Prompt weighting allows you to adjust how strongly different parts of your prompt influence the generated image.", + "label_on": "Enabled", + "label_off": "Disabled", + }), + "dimensions": ([ + "Square (512x512)", "Square HD (1024x1024)", "Portrait 3:4 (768x1024)", + "Portrait 9:16 (576x1024)", "Landscape 4:3 (1024x768)", + "Landscape 16:9 (1024x576)", "Custom" + ], { + "default": "Square (512x512)", + "tooltip": "Adjust the dimensions of the generated image by specifying its width and height in pixels, or select from the predefined options. Image dimensions must be multiples of 64 (e.g., 512x512, 1024x768).", + }), + "width": ("INT", { + "tooltip": "The Width of the image in pixels.", + "default": 512, + "min": 512, + "max": 2048, + "step": 64, + }), + "height": ("INT", { + "tooltip": "The Height of the image in pixels.", + "default": 512, + "min": 512, + "max": 2048, + "step": 64, + }), + "steps": ("INT", { + "tooltip": "Guidance scale represents how closely the images will resemble the prompt or how much freedom the AI model has. Higher values are closer to the prompt. Low values may reduce the quality of the results.", + "default": 4, + "min": 1, + "max": 100, + }), + "scheduler": ([ + "Default", "DDIMScheduler", "DDIMInverseScheduler", "DDPMScheduler", "DEISMultistepScheduler", + "DPMSolverSinglestepScheduler", "DPMSolverMultistepScheduler", "DPMSolverMultistepInverse", "DPMSolverSDEScheduler", "HeunDiscreteScheduler", "KDPM2DiscreteScheduler", "KDPM2AncestralDiscreteScheduler", "KarrasVeScheduler", "LMSDiscreteScheduler", "PNDMScheduler", "ScoreSdeVeScheduler", "ScoreSdeVpScheduler", "IPNDMScheduler", "EulerDiscreteScheduler", "EulerAncestralDiscreteScheduler", "EDMEulerScheduler", "FlowMatchEulerDiscreteScheduler", "VQDiffusionScheduler", "UniPCMultistepScheduler", "RePaintScheduler", "DPM++ 2M Karras", "DPM++ 2M SDE Karras", "DPM++ 2M SDE", "DPM++ SDE Karras", "DPM++ SDE", "LCMScheduler", "EDMDPMSolverMultistepScheduler", "TCDScheduler" + ], { + "tooltip": "An scheduler is a component that manages the inference process. Different schedulers can be used to achieve different results like more detailed images, faster inference, or more accurate results.", + "default": "Default", + }), + "cfgScale": ("FLOAT", { + "tooltip": "The scale of the image generation. The higher the scale, the more detailed the image will be.", + "default": 6.5, + "min": 1.0, + "max": 30.0, + "step": 0.5, + }), + "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": rwUtils.genRandSeed(), + "min": 1, + "max": 9223372036854776000, + }), + "clipSkip": ("INT", { + "tooltip": "Enables skipping layers of the CLIP embedding process, leading to quicker and more varied image generation.", + "default": 0, + "min": 0, + "max": 2, + }), + "strength": ("FLOAT", { + "tooltip": "When doing Image-to-Image or Inpainting, this parameter is used to determine the influence of the seedImage image in the generated output. A lower value results in more influence from the original image, while a higher value allows more creative deviation.", + "default": 0.80, + "min": 0.0, + "max": 1.0, + "step": 0.05, + }), + "Mask Margin": ("BOOLEAN", { + "tooltip": "Enables Or Disables Mask Margin Feature.", + "default": False, + "label_on": "Enabled", + "label_off": "Disabled", + }), + "maskMargin": ("INT", { + "tooltip": "Adds extra context pixels around the masked region during inpainting. When this parameter is present, the model will zoom into the masked area, considering these additional pixels to create more coherent and well-integrated details.", + "default": 32, + "min": 32, + "max": 128, + }), + "batchSize": ("INT", { + "tooltip": "The number of images to generate in a single request.", + "default": 1, + "min": 1, + "max": 10, + }), + }, + "optional": { + "Lora": ("RUNWARELORA", { + "tooltip": "Connect a Runware Lora From Lora Search Node Or Lora Combine For Multiple Lora's Together.", + }), + "ControlNet": ("RUNWARECONTROLNET", { + "tooltip": "Connect a Runware ControlNet Guidance Node to help the model generate images that align with the desired structure.", + }), + "Refiner": ("RUNWAREREFINER", { + "tooltip": "Connect a Runware Refiner Node to help create higher quality image outputs by incorporating specialized models designed to enhance image details and overall coherence.", + }), + "seedImage": ("IMAGE", { + "tooltip": "Specifies the seed image to be used for the diffusion process, when doing Image-to-Image, Inpainting or Outpainting, this parameter is required.", + }), + "maskImage": ("MASK", { + "tooltip": "Specifies the mask image to be used for the inpainting process, when doing Inpainting, this parameter is required.", + }), + } + } + + @classmethod + def VALIDATE_INPUTS(cls, positivePrompt, negativePrompt): + if (positivePrompt is None 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",) + CATEGORY = "Runware" + + def generateImage(self, **kwargs): + runwareModel = kwargs.get("Model") + positivePrompt = kwargs.get("positivePrompt") + negativePrompt = kwargs.get("negativePrompt", None) + promptWeighting = kwargs.get("Prompt Weighting", False) + runwareControlNet = kwargs.get("ControlNet", None) + runwareLora = kwargs.get("Lora", None) + runwareRefiner = kwargs.get("Refiner", None) + seedImage = kwargs.get("seedImage", None) + seedImageStrength = kwargs.get("strength", 0.8) + maskImage = kwargs.get("maskImage", None) + enableMaskMargin = kwargs.get("Mask Margin", False) + maskImageMargin = kwargs.get("maskMargin", 32) + clipSkip = kwargs.get("clipSkip", 0) + height = kwargs.get("height", 512) + width = kwargs.get("width", 512) + steps = kwargs.get("steps", 4) + scheduler = kwargs.get("scheduler", "Default") + cfgScale = kwargs.get("cfgScale", 6.5) + seed = kwargs.get("seed") + batchSize = kwargs.get("batchSize", 1) + + if(maskImage is not None and seedImage is None): + raise Exception("Mask Image Requires Seed Image To Be Provided!") + + genConfig = [ + { + "taskType": "imageInference", + "taskUUID": rwUtils.genRandUUID(), + "positivePrompt": positivePrompt, + "height": height, + "width": width, + "model": runwareModel, + "steps": steps, + "CFGScale": cfgScale, + "scheduler": scheduler, + "clipSkip": clipSkip, + "seed": seed, + "outputFormat": "WEBP", + "outputType": "base64Data", + "numberResults": batchSize, + } + ] + + # For Debugging Purposes Only + print(f"[Debugging] Task UUID: {genConfig[0]['taskUUID']}") + + if(negativePrompt is not None and negativePrompt != ""): + genConfig[0]["negativePrompt"] = negativePrompt + if(promptWeighting): + genConfig[0]["promptWeighting"] = "sdEmbeds" + if(runwareLora is not None): + if(isinstance(runwareLora, list)): + genConfig[0]["lora"] = runwareLora + elif(isinstance(runwareLora, dict)): + genConfig[0]["lora"] = [runwareLora] + if(runwareControlNet is not None): + genConfig[0]["controlNet"] = runwareControlNet + if(runwareRefiner is not None): + genConfig[0]["refiner"] = runwareRefiner + if(seedImage is not None): + seedImage = rwUtils.convertTensor2IMG(seedImage) + genConfig[0]["seedImage"] = seedImage + genConfig[0]["strength"] = seedImageStrength + if(maskImage is not None): + maskImage = rwUtils.convertTensor2IMG(maskImage) + genConfig[0]["maskImage"] = maskImage + if(enableMaskMargin): + genConfig[0]["maskMargin"] = maskImageMargin + + genResult = rwUtils.inferenecRequest(genConfig) + images = rwUtils.convertImageB64List(genResult) + return images \ No newline at end of file diff --git a/modules/imageMasking.py b/modules/imageMasking.py new file mode 100644 index 0000000..400768f --- /dev/null +++ b/modules/imageMasking.py @@ -0,0 +1,79 @@ +from .utils import runwareUtils as rwUtils + +class imageMasking: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "Image": ("IMAGE", { + "tooltip": "Specifies the input image to be processed for mask generation." + }), + "Detection Model": ([ + "face_yolov8n", "face_yolov8s", "hand_yolov8n", "person_yolov8n-seg", "person_yolov8s-seg", + "mediapipe_face_full", "mediapipe_face_short", "mediapipe_face_mesh" + ], { + "tooltip": "Specifies the specialized detection model to use for mask generation.", + "default": "face_yolov8n", + }), + "Confidence": ("FLOAT", { + "tooltip": "Confidence threshold for detections. Only detections with confidence scores above this threshold will be included in the mask.", + "default": 0.25, + "min": 0, + "max": 1, + "step": 0.01, + }), + "Max Detections": ("INT", { + "tooltip": "Limits the maximum number of elements (faces, hands, or people) that will be detected and masked in the image. If there are more elements than this value, only the ones with highest confidence scores will be included.", + "default": 6, + "min": 1, + "max": 20, + }), + "Mask Padding": ("INT", { + "tooltip": "Extends or reduces the detected mask area by the specified number of pixels. Positive values create a larger masked region (useful when you want to ensure complete coverage of the element), while negative values shrink the mask (useful for tighter, more focused areas).", + "default": 4, + "min": -40, + "max": 40, + }), + "Mask Blur": ("INT", { + "tooltip": "Extends the mask by the specified number of pixels with a gradual fade-out effect, creating smooth transitions between masked and unmasked regions in the final result.", + "default": 4, + "min": 0, + "max": 20, + }), + }, + } + + 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_NAMES = ("Image", "Mask Preview", "Mask") + CATEGORY = "Runware" + + def imageMasking(self, **kwargs): + image = kwargs.get("Image") + detectionModel = kwargs.get("Detection Model") + confidence = kwargs.get("Confidence") + maxDetections = kwargs.get("Max Detections") + maskPadding = kwargs.get("Mask Padding") + maskBlur = kwargs.get("Mask Blur") + + genConfig = [ + { + "taskType": "imageMasking", + "taskUUID": rwUtils.genRandUUID(), + "inputImage": rwUtils.convertTensor2IMG(image), + "model": detectionModel, + "confidence": confidence, + "maxDetections": maxDetections, + "outputFormat": "WEBP", + "outputType": "base64Data", + } + ] + + genConfig[0]["maskPadding"] = maskPadding + genConfig[0]["maskBlur"] = maskBlur + + genResult = rwUtils.inferenecRequest(genConfig) + images = rwUtils.convertImageB64List(genResult) + return (image, images[0], images[0]) \ No newline at end of file diff --git a/modules/loraCombine.py b/modules/loraCombine.py new file mode 100644 index 0000000..584375d --- /dev/null +++ b/modules/loraCombine.py @@ -0,0 +1,37 @@ +class loraCombine: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "Lora 1": ("RUNWARELORA", { + "tooltip": "Connect a Runware Lora From Lora Search Node.", + }), + }, + "optional": { + "Lora 2": ("RUNWARELORA", { + "tooltip": "Connect a Runware Lora From Lora Search Node.", + }), + "Lora 3": ("RUNWARELORA", { + "tooltip": "Connect a Runware Lora From Lora Search Node.", + }), + }, + } + + DESCRIPTION = "Combine One or More Lora's To Connect It With Runware Image Inference." + FUNCTION = "loraCombine" + RETURN_TYPES = ("RUNWARELORA",) + RETURN_NAMES = ("Runware Lora's",) + CATEGORY = "Runware" + + def loraCombine(self, **kwargs): + runwareLora1 = kwargs.get("Lora 1") + runwareLora2 = kwargs.get("Lora 2", None) + runwareLora3 = kwargs.get("Lora 3", None) + + loraObjectArray = [] + loraObjectArray.append(runwareLora1) + if(runwareLora2): + loraObjectArray.append(runwareLora2) + if(runwareLora3): + loraObjectArray.append(runwareLora3) + return (loraObjectArray,) \ No newline at end of file diff --git a/modules/loraSearch.py b/modules/loraSearch.py new file mode 100644 index 0000000..d47581c --- /dev/null +++ b/modules/loraSearch.py @@ -0,0 +1,72 @@ +class loraSearch: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "Lora Search": ("STRING", { + "tooltip": "Searchg For Specific Lora By Name Or Civit AIR Code (eg: Cyberpunk).", + }), + "Model Architecture": ([ + "All", + "FLUX.1-Schnell", + "FLUX.1-Dev", + "Pony", + "SD 1.5", + "SD 1.5 Hyper", + "SD 1.5 LCM", + "SD 3", + "SDXL 1.0", + "SDXL 1.0 LCM", + "SDXL Distilled", + "SDXL Hyper", + "SDXL Lightning", + "SDXL Turbo", + ], { + "tooltip": "Choose Lora Model Architecture To Filter Results.", + "default": "All", + }), + "LoraType": ([ + "Lora", + ], { + "tooltip": "Choose Lora Type To Filter Results.", + "default": "Lora", + }), + "LoraList": ([ + "civitai:58390@62833 (Detail Tweaker LoRA (细节调整LoRA))", + "civitai:82098@87153 (Add More Details - Detail Enhancer / Tweaker (细节调整) LoRA)", + "civitai:122359@135867 (Detail Tweaker XL)", + "civitai:14171@16677 (Cute_girl_mix4)", + "civitai:13941@16576 (epi_noiseoffset)", + "civitai:25995@32988 (blindbox/大概是盲盒)", + ], { + "tooltip": "Lora Results Will Show UP Here So You Could Choose From.", + "default": "civitai:58390@62833 (Detail Tweaker LoRA (细节调整LoRA))", + }), + "weight": ("FLOAT", { + "tooltip": "Defines the strength or influence of the LoRA model in the generation process.", + "default": 1.0, + "min": -4.0, + "max": 4.0, + "step": 0.1, + }), + }, + } + + DESCRIPTION = "Directly Search and Connect Lora's to Runware Inference Nodes In ComfyUI." + FUNCTION = "loraSearch" + RETURN_TYPES = ("RUNWARELORA",) + RETURN_NAMES = ("Runware Lora",) + CATEGORY = "Runware" + + @classmethod + def VALIDATE_INPUTS(cls, LoraList): + return True + + def loraSearch(self, **kwargs): + currentModel = kwargs.get("LoraList") + loraWeight = kwargs.get("weight") + loraAIRCode = currentModel.split(" ")[0] + return ({ + "model": loraAIRCode, + "weight": round(loraWeight, 2), + },) \ No newline at end of file diff --git a/modules/modelSearch.py b/modules/modelSearch.py new file mode 100644 index 0000000..fc3b79a --- /dev/null +++ b/modules/modelSearch.py @@ -0,0 +1,63 @@ +class modelSearch: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "Model Search": ("STRING", { + "tooltip": "Searchg For Specific Model By Name Or Civit AIR Code (eg: Juggernaut).", + }), + "Model Architecture": ([ + "All", + "FLUX.1-Schnell", + "FLUX.1-Dev", + "Pony", + "SD 1.5", + "SD 1.5 Hyper", + "SD 1.5 LCM", + "SD 3", + "SDXL 1.0", + "SDXL 1.0 LCM", + "SDXL Distilled", + "SDXL Hyper", + "SDXL Lightning", + "SDXL Turbo", + ], { + "tooltip": "Choose Model Architecture To Filter Results.", + "default": "All", + }), + "ModelType": ([ + "Base Model", + "Inpainting Model", + ], { + "tooltip": "Choose Model Type To Filter Results.", + "default": "Base Model", + }), + "ModelList": ([ + "runware:100@1 (Flux Schnell)", + "runware:101@1 (Flux Dev)", + "runware:5@1 (SD3)", + "civitai:4384@128713 (SDXL 1.5 DreamShaper)", + "civitai:43331@176425 (SD 1.5 majicMIX realistic 麦橘写实)", + "civitai:101055@128078 (SDXL v1.0 VAE fix)", + "civitai:133005@288982 (SDXL Juggernaut XL V8)", + ], { + "tooltip": "Model Results Will Show UP Here So You Could Choose From. If You didn't Search For Anything this will show featured Model List.", + "default": "runware:100@1 (Flux Schnell)", + }), + }, + } + + DESCRIPTION = "Directly Search and Connect Model Checkpoints to Runware Inference Nodes In ComfyUI." + FUNCTION = "modelSearch" + RETURN_TYPES = ("RUNWAREMODEL",) + RETURN_NAMES = ("Runware Model",) + CATEGORY = "Runware" + + @classmethod + def VALIDATE_INPUTS(cls, ModelList): + return True + + def modelSearch(self, **kwargs): + currentModel = kwargs.get("ModelList") + modelAIRCode = currentModel.split(" ")[0] + return (modelAIRCode,) \ No newline at end of file diff --git a/modules/photoMaker.py b/modules/photoMaker.py new file mode 100644 index 0000000..bd5a3dc --- /dev/null +++ b/modules/photoMaker.py @@ -0,0 +1,179 @@ +from .utils import runwareUtils as rwUtils + +class photoMaker: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "Model": ("RUNWAREMODEL", { + "tooltip": "Connect a Runware Model From Runware Model Node.", + }), + "Image 1": ("IMAGE", { + "tooltip": "Specifies Input Image 1 that will be used as reference for the subject. These reference images help the AI to maintain identity consistency during the generation process." + }), + "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\nThe PhotoMaker positive prompt must follow a specific format: the class word (like \"man\", \"woman\", \"girl\") must be followed by the trigger word \"img\".\nExample: man img, wearing a suit", + "tooltip": "Positive Prompt: a text instruction to guide the model on generating the image.\nDo not Forget To Add trigger word \"img\" after the class word (like \"man\", \"woman\", \"girl\")", + }), + "negativePrompt": ("STRING", { + "multiline": True, + "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." + }), + "style": ([ + "No style", "Cinematic", "Disney Character", "Digital Art", "Photographic", "Fantasy art", + "Neonpunk", "Enhance", "Comic book", "Lowpoly", "Line art" + ], { + "default": "No style", + "tooltip": "Specifies the artistic style to be applied to the generated images." + }), + "dimensions": ([ + "Square (512x512)", "Square HD (1024x1024)", "Portrait 3:4 (768x1024)", + "Portrait 9:16 (576x1024)", "Landscape 4:3 (1024x768)", + "Landscape 16:9 (1024x576)", "Custom" + ], { + "default": "Square HD (1024x1024)", + "tooltip": "Adjust the dimensions of the generated image by specifying its width and height in pixels, or select from the predefined options. Image dimensions must be multiples of 64 (e.g., 512x512, 1024x768).", + }), + "width": ("INT", { + "tooltip": "The Width of the image in pixels.", + "default": 1024, + "min": 512, + "max": 2048, + "step": 64, + }), + "height": ("INT", { + "tooltip": "The Height of the image in pixels.", + "default": 1024, + "min": 512, + "max": 2048, + "step": 64, + }), + "steps": ("INT", { + "tooltip": "Guidance scale represents how closely the images will resemble the prompt or how much freedom the AI model has. Higher values are closer to the prompt. Low values may reduce the quality of the results.", + "default": 25, + "min": 1, + "max": 100, + }), + "scheduler": ([ + "Default", "DDIMScheduler", "DDIMInverseScheduler", "DDPMScheduler", "DEISMultistepScheduler", + "DPMSolverSinglestepScheduler", "DPMSolverMultistepScheduler", "DPMSolverMultistepInverse", "DPMSolverSDEScheduler", "HeunDiscreteScheduler", "KDPM2DiscreteScheduler", "KDPM2AncestralDiscreteScheduler", "KarrasVeScheduler", "LMSDiscreteScheduler", "PNDMScheduler", "ScoreSdeVeScheduler", "ScoreSdeVpScheduler", "IPNDMScheduler", "EulerDiscreteScheduler", "EulerAncestralDiscreteScheduler", "EDMEulerScheduler", "FlowMatchEulerDiscreteScheduler", "VQDiffusionScheduler", "UniPCMultistepScheduler", "RePaintScheduler", "DPM++ 2M Karras", "DPM++ 2M SDE Karras", "DPM++ 2M SDE", "DPM++ SDE Karras", "DPM++ SDE", "LCMScheduler", "EDMDPMSolverMultistepScheduler", "TCDScheduler" + ], { + "tooltip": "An scheduler is a component that manages the inference process. Different schedulers can be used to achieve different results like more detailed images, faster inference, or more accurate results.", + "default": "Default", + }), + "cfgScale": ("FLOAT", { + "tooltip": "The scale of the image generation. The higher the scale, the more detailed the image will be.", + "default": 6.5, + "min": 1.0, + "max": 30.0, + "step": 0.5, + }), + "strength": ("INT", { + "default": 15, + "min": 15, + "max": 50, + "tooltip": "Controls the balance between preserving the subject's original features and the creative transformation specified in the prompt.\n- Lower values provide stronger subject fidelity.\n- Higher values allow more creative freedom in the transformation." + }), + "clipSkip": ("INT", { + "tooltip": "Enables skipping layers of the CLIP embedding process, leading to quicker and more varied image generation.", + "default": 0, + "min": 0, + "max": 2, + }), + "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": rwUtils.genRandSeed(), + "min": 1, + "max": 9223372036854776000, + }), + "batchSize": ("INT", { + "tooltip": "The number of images to generate in a single request.", + "default": 1, + "min": 1, + "max": 10, + }), + }, + "optional": { + "Image 2": ("IMAGE", { + "tooltip": "Specifies Input Image 2 that will be used as reference for the subject. These reference images help the AI to maintain identity consistency during the generation process." + }), + "Image 3": ("IMAGE", { + "tooltip": "Specifies Input Image 3 that will be used as reference for the subject. These reference images help the AI to maintain identity consistency during the generation process." + }), + "Image 4": ("IMAGE", { + "tooltip": "Specifies Input Image 4 that will be used as reference for the subject. These reference images help the AI to maintain identity consistency during the generation process." + }), + } + } + + 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",) + CATEGORY = "Runware" + + @classmethod + def VALIDATE_INPUTS(cls, positivePrompt, negativePrompt): + if (positivePrompt is None 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 + + def photoMaker(self, **kwargs): + runwareModel = kwargs.get("Model") + image1 = kwargs.get("Image 1") + image2 = kwargs.get("Image 2", None) + image3 = kwargs.get("Image 3", None) + image4 = kwargs.get("Image 4", None) + positivePrompt = kwargs.get("positivePrompt") + negativePrompt = kwargs.get("negativePrompt", None) + style = kwargs.get("style", "No style") + dimensions = kwargs.get("dimensions", "Square HD (1024x1024)") + width = kwargs.get("width", 1024) + height = kwargs.get("height", 1024) + steps = kwargs.get("steps", 25) + scheduler = kwargs.get("scheduler", "Default") + cfgScale = kwargs.get("cfgScale", 6.5) + strength = kwargs.get("strength", 15) + clipSkip = kwargs.get("clipSkip", 0) + seed = kwargs.get("seed") + batchSize = kwargs.get("batchSize", 1) + + imageList = [rwUtils.convertTensor2IMG(image1)] + if(image2 is not None): + imageList.append(rwUtils.convertTensor2IMG(image2)) + if(image3 is not None): + imageList.append(rwUtils.convertTensor2IMG(image3)) + if(image4 is not None): + imageList.append(rwUtils.convertTensor2IMG(image4)) + + genConfig = [ + { + "taskType": "photoMaker", + "taskUUID": rwUtils.genRandUUID(), + "model": runwareModel, + "inputImages": imageList, + "positivePrompt": positivePrompt, + "style": style, + "strength": strength, + "height": height, + "width": width, + "steps": steps, + "scheduler": scheduler, + "seed": seed, + "CFGScale": cfgScale, + "clipSkip": clipSkip, + "numberResults": batchSize, + "outputFormat": "WEBP", + "outputType": "base64Data", + } + ] + + if(negativePrompt is not None and negativePrompt != ""): + genConfig[0]["negativePrompt"] = negativePrompt + + genResult = rwUtils.inferenecRequest(genConfig) + images = rwUtils.convertImageB64List(genResult) + return images \ No newline at end of file diff --git a/modules/refiner.py b/modules/refiner.py new file mode 100644 index 0000000..45303f3 --- /dev/null +++ b/modules/refiner.py @@ -0,0 +1,49 @@ +class refiner: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "Model": ("RUNWAREMODEL", { + "tooltip": "Connect a Runware Model From Model Search Node.", + }), + "startStep": ("INT", { + "tooltip": "Represents the step number at which the refinement process begins. The initial model will generate the image up to this step, after which the refiner model takes over to enhance the result.", + "min": 0, + "max": 99, + "default": 0, + }), + "startStepPercentage": ("INT", { + "tooltip": "Represents the percentage of total steps at which the refinement process begins. The initial model will generate the image up to this percentage of steps before the refiner takes over.", + "min": 0, + "max": 99, + "default": 0 + }), + }, + } + + DESCRIPTION = "Refiner models help create higher quality image outputs by incorporating specialized models designed to enhance image details and overall coherence. This can be particularly useful when you need results with superior quality, photorealism, or specific aesthetic refinements. (Note that refiner models are only SDXL based)" + FUNCTION = "refiner" + RETURN_TYPES = ("RUNWAREREFINER",) + CATEGORY = "Runware" + + @classmethod + def VALIDATE_INPUTS(cls, startStep, startStepPercentage): + if(startStep != 0 and startStepPercentage != 0): + raise Exception("Please provide either startStep or startStepPercentage, not both.") + elif(startStep == 0 and startStepPercentage == 0): + raise Exception("Please provide either startStep or startStepPercentage.") + else: + return True + + def refiner(self, **kwargs): + runwareModel = kwargs.get("Model") + startStep = kwargs.get("startStep") + startStepPercentage = kwargs.get("startStepPercentage") + refinerResult = { + "model": runwareModel, + } + if(startStep != 0): + refinerResult["startStep"] = startStep + else: + refinerResult["startStepPercentage"] = startStepPercentage + return (refinerResult,) \ No newline at end of file diff --git a/modules/upscaler.py b/modules/upscaler.py new file mode 100644 index 0000000..967a894 --- /dev/null +++ b/modules/upscaler.py @@ -0,0 +1,42 @@ +from .utils import runwareUtils as rwUtils + +class upscaler: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "Image": ("IMAGE", { + "tooltip": "Specifies the input image to be upscaled." + }), + "upscaleFactor": ("INT", { + "tooltip": "Each level will increase the size of the image by the corresponding factor.", + "default": 2, + "min": 2, + "max": 4, + }), + }, + } + + 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",) + CATEGORY = "Runware" + + def upscale(self, **kwargs): + image = kwargs.get("Image") + upscaleFactor = kwargs.get("upscaleFactor", 2) + + genConfig = [ + { + "taskType": "imageUpscale", + "taskUUID": rwUtils.genRandUUID(), + "inputImage": rwUtils.convertTensor2IMG(image), + "upscaleFactor": upscaleFactor, + "outputFormat": "WEBP", + "outputType": "base64Data", + } + ] + + genResult = rwUtils.inferenecRequest(genConfig) + images = rwUtils.convertImageB64List(genResult) + return images \ No newline at end of file diff --git a/modules/utils/runwareUtils.py b/modules/utils/runwareUtils.py new file mode 100644 index 0000000..917e60c --- /dev/null +++ b/modules/utils/runwareUtils.py @@ -0,0 +1,111 @@ +from requests.adapters import HTTPAdapter +from dotenv import load_dotenv +from pathlib import Path +from PIL import Image +import numpy as np +import requests +import random +import base64 +import torch +import uuid +import os +import io + +load_dotenv() + +SESSION_TIMEOUT = 60 +RUNWARE_API_BASE_URL = "https://api.runware.ai/v1" +session = requests.Session() +adapter = HTTPAdapter(pool_connections=10, pool_maxsize=10) +session.mount("http://", adapter) +session.mount("https://", adapter) + +def getAPIKey(): + apiKey = os.getenv("RUNWARE_API_KEY") + if apiKey and isinstance(apiKey, str) and len(apiKey) > 24: + return apiKey + return False + +RUNWARE_API_KEY = getAPIKey() + +def setAPIKey(apiKey: str): + global RUNWARE_API_KEY + comfyNodeRoot = Path(__file__).parent.parent.parent + envFilePath = comfyNodeRoot / '.env' + if not envFilePath.exists(): + envFilePath.touch() + with open(envFilePath, 'r') as f: + lines = f.readlines() + key_exists = False + new_lines = [] + for line in lines: + if line.startswith('RUNWARE_API_KEY='): + key_exists = True + new_lines.append(f'RUNWARE_API_KEY={apiKey}\n') + else: + new_lines.append(line) + if not key_exists: + new_lines.append(f'RUNWARE_API_KEY={apiKey}\n') + with open(envFilePath, 'w') as f: + f.writelines(new_lines) + RUNWARE_API_KEY = apiKey + os.environ["RUNWARE_API_KEY"] = apiKey + return True + +def genRandSeed(minSeed = 1000, maxSeed = 9223372036854776000): + return random.randint(minSeed, maxSeed) + +def genRandUUID(): + return str(uuid.uuid4()) + +def inferenecRequest(genConfig): + global RUNWARE_API_KEY + RUNWARE_API_KEY = os.getenv("RUNWARE_API_KEY") + headers = { + "Authorization": f"Bearer {RUNWARE_API_KEY}", + "Content-Type": "application/json", + "Accept-Encoding": "gzip, deflate, br, zstd", + } + + try: + genResult = session.post(RUNWARE_API_BASE_URL, headers=headers, json=genConfig, timeout=SESSION_TIMEOUT, allow_redirects=False, stream=True) + genResult = genResult.json() + if("errors" in genResult): + raise Exception(genResult["errors"][0]["message"]) + else: + return genResult + except requests.exceptions.Timeout: + raise Exception(f"Error: Request Timed Out After {SESSION_TIMEOUT} Seconds - Please Try Again!") + except requests.exceptions.RequestException: + raise Exception("Error: Runware Request Failed!") + except Exception as e: + if "invalid api key" in str(e).lower(): + raise Exception("Invalid API Key - Please Make Sure you set your correctly API Key Either in the env file or through the API Manager Node!\n\nIf you don't have an API Key, you can get it from: https://my.runware.ai/keys") + else: + raise Exception(f"Error: {e}") + return False + +def convertTensor2IMG(tensorImage): + imageNP = (tensorImage.squeeze().numpy() * 255).astype(np.uint8) + image = Image.fromarray(imageNP) + with io.BytesIO() as buffer: + image.save(buffer, format="webp") + imgb64 = base64.b64encode(buffer.getvalue()).decode('utf-8') + imgDataURI = f'data:image/webp;base64,{imgb64}' + return imgDataURI + +def convertIMG2Tensor(b64img): + imgbytes = base64.b64decode(b64img) + image = Image.open(io.BytesIO(imgbytes)) + imageNP = np.array(image).astype(np.float32) / 255.0 + tensorImage = torch.from_numpy(imageNP).squeeze() + return tensorImage + +def convertImageB64List(imageDataObject): + images = () + for result in imageDataObject["data"]: + generatedImage = result.get("imageBase64Data", result.get("maskImageBase64Data", result.get("guideImageBase64Data", False))) + generatedImage = convertIMG2Tensor(generatedImage) + images += (generatedImage,) + images = torch.stack(images, dim=0) + return (images,) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..3e338bf --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +python-dotenv \ No newline at end of file diff --git a/workflows/Runware_Adetailer.json b/workflows/Runware_Adetailer.json new file mode 100644 index 0000000..a3b83f6 --- /dev/null +++ b/workflows/Runware_Adetailer.json @@ -0,0 +1 @@ +{"last_node_id":7,"last_link_id":8,"nodes":[{"id":4,"type":"Runware Image Masking","pos":[351.7330627441406,132.90028381347656],"size":[315,194],"flags":{},"order":4,"mode":0,"inputs":[{"name":"Image","type":"IMAGE","link":3}],"outputs":[{"name":"Image","type":"IMAGE","links":[4],"slot_index":0},{"name":"Mask Preview","type":"IMAGE","links":null},{"name":"Mask","type":"MASK","links":[5],"slot_index":2}],"properties":{"Node name for S&R":"Runware Image Masking"},"widgets_values":["face_yolov8n",0.25,6,4,4],"bgcolor":"#6c5ce7"},{"id":2,"type":"Runware Model Search","pos":[-75.49443817138672,-288.33795166015625],"size":[315,130],"flags":{"collapsed":true},"order":0,"mode":0,"inputs":[],"outputs":[{"name":"Runware Model","type":"RUNWAREMODEL","links":[2],"slot_index":0}],"properties":{"Node name for S&R":"Runware Model Search"},"widgets_values":["","All","Base Model","civitai:101055@128078 (SDXL v1.0 VAE fix)"],"bgcolor":"#6c5ce7"},{"id":6,"type":"PreviewImage","pos":[1151.2889404296875,-264.380859375],"size":[552.3806762695312,564.0267944335938],"flags":{},"order":6,"mode":0,"inputs":[{"name":"images","type":"IMAGE","link":7}],"outputs":[],"properties":{"Node name for S&R":"PreviewImage"}},{"id":1,"type":"PreviewImage","pos":[357.7220764160156,-258.3918762207031],"size":[307.82305908203125,331.447509765625],"flags":{},"order":3,"mode":0,"inputs":[{"name":"images","type":"IMAGE","link":1}],"outputs":[],"properties":{"Node name for S&R":"PreviewImage"}},{"id":7,"type":"Runware Model Search","pos":[691.1177978515625,-322.2765808105469],"size":[315,130],"flags":{"collapsed":true},"order":1,"mode":0,"inputs":[],"outputs":[{"name":"Runware Model","type":"RUNWAREMODEL","links":[8],"slot_index":0}],"properties":{"Node name for S&R":"Runware Model Search"},"widgets_values":["","All","Base Model","runware:101@1 (Flux Dev)"],"bgcolor":"#6c5ce7"},{"id":3,"type":"Runware Image Inference","pos":[-77.49113464355469,-239.42645263671875],"size":[400,562],"flags":{},"order":2,"mode":0,"inputs":[{"name":"Model","type":"RUNWAREMODEL","link":2},{"name":"Lora","type":"RUNWARELORA","link":null,"shape":7},{"name":"ControlNet","type":"RUNWARECONTROLNET","link":null,"shape":7},{"name":"Refiner","type":"RUNWAREREFINER","link":null,"shape":7},{"name":"seedImage","type":"IMAGE","link":null,"shape":7},{"name":"maskImage","type":"MASK","link":null,"shape":7}],"outputs":[{"name":"IMAGE","type":"IMAGE","links":[1,3],"slot_index":0}],"properties":{"Node name for S&R":"Runware Image Inference"},"widgets_values":["a man standing in the woods and looking serious, wide screen, zoomed out, realistic, HD, details","",false,"Square HD (1024x1024)",1024,1024,20,"Default",4.5,502840077979554,"randomize",0,0.8,false,32,1],"bgcolor":"#6c5ce7"},{"id":5,"type":"Runware Image Inference","pos":[700.1026611328125,-276.35955810546875],"size":[421.96026611328125,591.9458618164062],"flags":{},"order":5,"mode":0,"inputs":[{"name":"Model","type":"RUNWAREMODEL","link":8},{"name":"Lora","type":"RUNWARELORA","link":null,"shape":7},{"name":"ControlNet","type":"RUNWARECONTROLNET","link":null,"shape":7},{"name":"Refiner","type":"RUNWAREREFINER","link":null,"shape":7},{"name":"seedImage","type":"IMAGE","link":4,"shape":7},{"name":"maskImage","type":"MASK","link":5,"shape":7}],"outputs":[{"name":"IMAGE","type":"IMAGE","links":[7],"slot_index":0}],"properties":{"Node name for S&R":"Runware Image Inference"},"widgets_values":["a man standing in the woods and looking serious, wide screen, zoomed out, realistic, HD, details","",false,"Square HD (1024x1024)",1024,1024,30,"Default",2,1108798832542204,"randomize",0,0.8,true,32,1],"bgcolor":"#6c5ce7"}],"links":[[1,3,0,1,0,"IMAGE"],[2,2,0,3,0,"RUNWAREMODEL"],[3,3,0,4,0,"IMAGE"],[4,4,0,5,4,"IMAGE"],[5,4,2,5,5,"MASK"],[7,5,0,6,0,"IMAGE"],[8,7,0,5,0,"RUNWAREMODEL"]],"groups":[],"config":{},"extra":{"ds":{"scale":1.001808834538306,"offset":[117.41863536835024,390.59035778045654]}},"version":0.4} \ No newline at end of file diff --git a/workflows/Runware_Basic_Background_Removal.json b/workflows/Runware_Basic_Background_Removal.json new file mode 100644 index 0000000..81bcb83 --- /dev/null +++ b/workflows/Runware_Basic_Background_Removal.json @@ -0,0 +1 @@ +{"last_node_id":5,"last_link_id":6,"nodes":[{"id":5,"type":"Runware Background Removal","pos":[481.94317626953125,-300.1940612792969],"size":[333.4461364746094,178],"flags":{},"order":1,"mode":0,"inputs":[{"name":"Image","type":"IMAGE","link":5}],"outputs":[{"name":"IMAGE","type":"IMAGE","links":[6],"slot_index":0}],"properties":{"Node name for S&R":"Runware Background Removal"},"widgets_values":[false,false,false,240,10,10],"bgcolor":"#6c5ce7"},{"id":2,"type":"PreviewImage","pos":[479.01971435546875,-74.14266204833984],"size":[343.48712158203125,246],"flags":{},"order":2,"mode":0,"inputs":[{"name":"images","type":"IMAGE","link":6}],"outputs":[],"properties":{"Node name for S&R":"PreviewImage"}},{"id":1,"type":"LoadImage","pos":[91.22550201416016,-285.5789794921875],"size":[363.7179260253906,459.1794128417969],"flags":{},"order":0,"mode":0,"inputs":[],"outputs":[{"name":"IMAGE","type":"IMAGE","links":[5],"slot_index":0},{"name":"MASK","type":"MASK","links":null}],"properties":{"Node name for S&R":"LoadImage"},"widgets_values":["r2rs1PrivateFLux.png","image"]}],"links":[[5,1,0,5,0,"IMAGE"],[6,5,0,2,0,"IMAGE"]],"groups":[],"config":{},"extra":{"ds":{"scale":1.0263162364614136,"offset":[464.15888590882696,406.82531520554556]}},"version":0.4} \ No newline at end of file diff --git a/workflows/Runware_Basic_Image_Inference.json b/workflows/Runware_Basic_Image_Inference.json new file mode 100644 index 0000000..91bd028 --- /dev/null +++ b/workflows/Runware_Basic_Image_Inference.json @@ -0,0 +1 @@ +{"last_node_id":79,"last_link_id":52,"nodes":[{"id":76,"type":"PreviewImage","pos":[890.173095703125,-194.4432373046875],"size":[442.2727355957031,469.6363525390625],"flags":{},"order":3,"mode":0,"inputs":[{"name":"images","type":"IMAGE","link":51}],"outputs":[],"properties":{"Node name for S&R":"PreviewImage"}},{"id":77,"type":"Runware Image Inference","pos":[401.4457092285156,-337.6249694824219],"size":[420,608],"flags":{},"order":2,"mode":0,"inputs":[{"name":"Model","type":"RUNWAREMODEL","link":52},{"name":"Lora","type":"RUNWARELORA","link":null,"shape":7},{"name":"ControlNet","type":"RUNWARECONTROLNET","link":null,"shape":7},{"name":"Refiner","type":"RUNWAREREFINER","link":null,"shape":7},{"name":"seedImage","type":"IMAGE","link":null,"shape":7},{"name":"maskImage","type":"MASK","link":null,"shape":7}],"outputs":[{"name":"IMAGE","type":"IMAGE","links":[51],"slot_index":0}],"properties":{"Node name for S&R":"Runware Image Inference"},"widgets_values":["A rebellious model with sharp features adorned with gold make-up and black eyeliner with lavander bob cut. A black off-shoulder top with a pink cats print pattern and choker. She posing in a vibrant, vivid color palette setting with ambient lighting, her expression calm and composed. The scene is painted in Patrick Nagel's painting style, with rich, smooth brushstrokes and a soft, warm color grading. The lighting is soft and diffused, creating a gentle glow around her. ArcaneFGTNR, Niji Stan Katayama Style, cyb3rp0p","",false,"Square (512x512)",512,512,4,"Default",6.5,697387220709399,"randomize",2,0.8,false,32,1],"bgcolor":"#6c5ce7"},{"id":79,"type":"Note","pos":[-46.554290771484375,-124.62498474121094],"size":[418,327],"flags":{},"order":1,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["Hello there! 🌟\n\nWelcome to the Runware Image Inference workflow. 🎉\n\nHere’s what you need to know to get started: \n\n- You can explore and search from a variety of models. 🧠✨\n \n- Remember to adjust the settings appropriately to achieve the desired results. ⚙️✅\n\n- Don’t forget to tweak the **\"steps\"** for optimal performance. 🛠️📈\n\nThe best part? We support a wide range of models, so dive in and have fun experimenting! 🚀❤️\n\nEnjoy your creative journey! 💡😊\n\n\ntip: Press \"Ctrl + Alt + E\" To Automatically Enhance Your Prompt\n\ntip: You can reset the model, lora search by clearing the search input and everything will get back to defaults."],"color":"#432","bgcolor":"#653"},{"id":78,"type":"Runware Model Search","pos":[5.445709228515625,-324.625],"size":[315,130],"flags":{"collapsed":false},"order":0,"mode":0,"inputs":[],"outputs":[{"name":"Runware Model","type":"RUNWAREMODEL","links":[52],"slot_index":0}],"properties":{"Node name for S&R":"Runware Model Search"},"widgets_values":["","All","Base Model","runware:100@1 (Flux Schnell)"],"bgcolor":"#6c5ce7"}],"links":[[51,77,0,76,0,"IMAGE"],[52,78,0,77,0,"RUNWAREMODEL"]],"groups":[],"config":{},"extra":{"ds":{"scale":1,"offset":[260.5542915265911,467.0624812864706]}},"version":0.4} \ No newline at end of file diff --git a/workflows/Runware_Basic_Image_Inference_Lora.json b/workflows/Runware_Basic_Image_Inference_Lora.json new file mode 100644 index 0000000..b5f194d --- /dev/null +++ b/workflows/Runware_Basic_Image_Inference_Lora.json @@ -0,0 +1 @@ +{"last_node_id":4,"last_link_id":3,"nodes":[{"id":3,"type":"Runware Model Search","pos":[-170.87696838378906,-336.2454528808594],"size":[315,130],"flags":{},"order":0,"mode":0,"inputs":[],"outputs":[{"name":"Runware Model","type":"RUNWAREMODEL","links":[2],"slot_index":0}],"properties":{"Node name for S&R":"Runware Model Search"},"widgets_values":["","All","Base Model","runware:100@1 (Flux Schnell)"],"bgcolor":"#6c5ce7"},{"id":2,"type":"Runware Image Inference","pos":[191.5845489501953,-370.3477478027344],"size":[410.71795654296875,598.05126953125],"flags":{},"order":2,"mode":0,"inputs":[{"name":"Model","type":"RUNWAREMODEL","link":2},{"name":"Lora","type":"RUNWARELORA","link":3,"shape":7},{"name":"ControlNet","type":"RUNWARECONTROLNET","link":null,"shape":7},{"name":"Refiner","type":"RUNWAREREFINER","link":null,"shape":7},{"name":"seedImage","type":"IMAGE","link":null,"shape":7},{"name":"maskImage","type":"MASK","link":null,"shape":7}],"outputs":[{"name":"IMAGE","type":"IMAGE","links":[1],"slot_index":0}],"properties":{"Node name for S&R":"Runware Image Inference"},"widgets_values":["a guy wearing red glasses, anime, pixiv top monthly, trending on artstation, cinematic, danbooru, zerochan art, kyoto animation, niji","",false,"Square (512x512)",512,512,4,"Default",6.5,258656211183721,"randomize",0,0.8,false,32,1],"bgcolor":"#6c5ce7"},{"id":4,"type":"Runware Lora Search","pos":[-162.10772705078125,-143.322265625],"size":[315,154],"flags":{},"order":1,"mode":0,"inputs":[],"outputs":[{"name":"Runware Lora","type":"RUNWARELORA","links":[3],"slot_index":0}],"properties":{"Node name for S&R":"Runware Lora Search"},"widgets_values":["niji","FLUX.1-Schnell","Lora","civitai:768440@859463 (Flux-Better-Art-With-Niji Schnell)",1],"bgcolor":"#6c5ce7"},{"id":1,"type":"PreviewImage","pos":[627.12255859375,-365.4764404296875],"size":[458.46142578125,484.9228515625],"flags":{},"order":3,"mode":0,"inputs":[{"name":"images","type":"IMAGE","link":1}],"outputs":[],"properties":{"Node name for S&R":"PreviewImage"}}],"links":[[1,2,0,1,0,"IMAGE"],[2,3,0,2,0,"RUNWAREMODEL"],[3,4,0,2,1,"RUNWARELORA"]],"groups":[],"config":{},"extra":{"ds":{"scale":1.0263162364614136,"offset":[606.4152342088262,463.3381111055449]}},"version":0.4} \ No newline at end of file diff --git a/workflows/Runware_Basic_Image_Upscaling.json b/workflows/Runware_Basic_Image_Upscaling.json new file mode 100644 index 0000000..edc4f0d --- /dev/null +++ b/workflows/Runware_Basic_Image_Upscaling.json @@ -0,0 +1 @@ +{"last_node_id":5,"last_link_id":3,"nodes":[{"id":4,"type":"Runware Image Upscaler","pos":[139.44570922851562,-279.625],"size":[315,58],"flags":{},"order":1,"mode":0,"inputs":[{"name":"Image","type":"IMAGE","link":3}],"outputs":[{"name":"IMAGE","type":"IMAGE","links":[2],"slot_index":0}],"properties":{"Node name for S&R":"Runware Image Upscaler"},"widgets_values":[4],"bgcolor":"#6c5ce7"},{"id":1,"type":"PreviewImage","pos":[497.6885986328125,-288.6935729980469],"size":[593.5640869140625,614.8204345703125],"flags":{},"order":2,"mode":0,"inputs":[{"name":"images","type":"IMAGE","link":2}],"outputs":[],"properties":{"Node name for S&R":"PreviewImage"}},{"id":5,"type":"LoadImage","pos":[155.82870483398438,-114.90699005126953],"size":[320.84613037109375,411.43585205078125],"flags":{},"order":0,"mode":0,"inputs":[],"outputs":[{"name":"IMAGE","type":"IMAGE","links":[3],"slot_index":0},{"name":"MASK","type":"MASK","links":null}],"properties":{"Node name for S&R":"LoadImage"},"widgets_values":["qwd2s.jpg","image"]}],"links":[[2,4,0,1,0,"IMAGE"],[3,5,0,4,0,"IMAGE"]],"groups":[],"config":{},"extra":{"ds":{"scale":1.0263162364614136,"offset":[452.4665833088276,458.4663183555447]}},"version":0.4} \ No newline at end of file diff --git a/workflows/Runware_ControlNet.json b/workflows/Runware_ControlNet.json new file mode 100644 index 0000000..99618f8 --- /dev/null +++ b/workflows/Runware_ControlNet.json @@ -0,0 +1 @@ +{"last_node_id":6,"last_link_id":6,"nodes":[{"id":4,"type":"LoadImage","pos":[0.36829668283462524,-416.1068420410156],"size":[401.6533203125,200.8212432861328],"flags":{},"order":0,"mode":0,"inputs":[],"outputs":[{"name":"IMAGE","type":"IMAGE","links":[4],"slot_index":0},{"name":"MASK","type":"MASK","links":null}],"properties":{"Node name for S&R":"LoadImage"},"widgets_values":["qwd2qdasf.jpg","image"]},{"id":6,"type":"Runware Model Search","pos":[426.5971374511719,-462.02398681640625],"size":[315,130],"flags":{},"order":1,"mode":0,"inputs":[],"outputs":[{"name":"Runware Model","type":"RUNWAREMODEL","links":[5],"slot_index":0}],"properties":{"Node name for S&R":"Runware Model Search"},"widgets_values":["","All","Base Model","runware:101@1 (Flux Dev)"],"bgcolor":"#6c5ce7"},{"id":5,"type":"Runware ControlNet PreProcessor","pos":[-3.6244616508483887,-163.56333923339844],"size":[390.5999755859375,226],"flags":{},"order":2,"mode":0,"inputs":[{"name":"Image","type":"IMAGE","link":4}],"outputs":[{"name":"GUIDE IMAGE","type":"IMAGE","links":[6],"slot_index":0}],"properties":{"Node name for S&R":"Runware ControlNet PreProcessor"},"widgets_values":["OpenPose",true,100,200,false,"Square HD (1024x1024)",1024,1024],"bgcolor":"#6c5ce7"},{"id":1,"type":"PreviewImage","pos":[843.8425903320312,-253.40106201171875],"size":[593.306640625,620.9238891601562],"flags":{},"order":5,"mode":0,"inputs":[{"name":"images","type":"IMAGE","link":1}],"outputs":[],"properties":{"Node name for S&R":"PreviewImage"}},{"id":2,"type":"Runware Image Inference","pos":[415.61724853515625,-281.35052490234375],"size":[413.9747009277344,664.8140258789062],"flags":{},"order":4,"mode":0,"inputs":[{"name":"Model","type":"RUNWAREMODEL","link":5},{"name":"Lora","type":"RUNWARELORA","link":null,"shape":7},{"name":"ControlNet","type":"RUNWARECONTROLNET","link":2,"shape":7},{"name":"Refiner","type":"RUNWAREREFINER","link":null,"shape":7},{"name":"seedImage","type":"IMAGE","link":null,"shape":7},{"name":"maskImage","type":"MASK","link":null,"shape":7}],"outputs":[{"name":"IMAGE","type":"IMAGE","links":[1],"slot_index":0}],"properties":{"Node name for S&R":"Runware Image Inference"},"widgets_values":["a girl wearing a red dress, with curly hair, very anime!!! fine - face, pretty face, realistic shaded perfect face, fine details by stanley artgerm lau, wlop, rossdraws, james jean, andrei riabovitchev, marc simonetti, and sakimichan, trending on artstation","",false,"Square HD (1024x1024)",1024,1024,30,"Default",2.5,164154394413164,"randomize",0,0.8,false,32,1],"bgcolor":"#6c5ce7"},{"id":3,"type":"Runware ControlNet","pos":[4.361094951629639,107.9453125],"size":[380.4000244140625,274],"flags":{},"order":3,"mode":0,"inputs":[{"name":"Guide Image","type":"IMAGE","link":6}],"outputs":[{"name":"Runware ControlNet","type":"RUNWARECONTROLNET","links":[2],"slot_index":0}],"properties":{"Node name for S&R":"Runware ControlNet"},"widgets_values":["","FLUX.1-Dev","Open Pose","runware:29@1 (ControlNetFlux (CNFlux) instantx-union)",-1,0,-1,65,"balanced",0.8],"bgcolor":"#6c5ce7"}],"links":[[1,2,0,1,0,"IMAGE"],[2,3,0,2,2,"RUNWARECONTROLNET"],[4,4,0,5,0,"IMAGE"],[5,6,0,2,0,"RUNWAREMODEL"],[6,5,0,3,0,"IMAGE"]],"groups":[],"config":{},"extra":{"ds":{"scale":1.001808834538306,"offset":[369.9618265217751,378.612024603221]}},"version":0.4} \ No newline at end of file diff --git a/workflows/Runware_Image_Inference_Refiner.json b/workflows/Runware_Image_Inference_Refiner.json new file mode 100644 index 0000000..20c63b8 --- /dev/null +++ b/workflows/Runware_Image_Inference_Refiner.json @@ -0,0 +1 @@ +{"last_node_id":6,"last_link_id":6,"nodes":[{"id":1,"type":"Runware Model Search","pos":[-180.6204376220703,-331.3739318847656],"size":[315,130],"flags":{},"order":0,"mode":0,"inputs":[],"outputs":[{"name":"Runware Model","type":"RUNWAREMODEL","links":[1],"slot_index":0}],"properties":{"Node name for S&R":"Runware Model Search"},"widgets_values":["","All","Base Model","civitai:101055@128078 (SDXL v1.0 VAE fix)"],"bgcolor":"#6c5ce7"},{"id":4,"type":"PreviewImage","pos":[616.4048461914062,-342.0920104980469],"size":[572.4613647460938,603.7946166992188],"flags":{},"order":4,"mode":0,"inputs":[{"name":"images","type":"IMAGE","link":3}],"outputs":[],"properties":{"Node name for S&R":"PreviewImage"}},{"id":6,"type":"Runware Model Search","pos":[-169.90292358398438,-124.80946350097656],"size":[315,130],"flags":{"collapsed":false},"order":1,"mode":0,"inputs":[],"outputs":[{"name":"Runware Model","type":"RUNWAREMODEL","links":[6],"slot_index":0}],"properties":{"Node name for S&R":"Runware Model Search"},"widgets_values":["","All","Base Model","civitai:133005@288982 (SDXL Juggernaut XL V8)"],"bgcolor":"#6c5ce7"},{"id":2,"type":"Runware Image Inference","pos":[183.7898406982422,-340.14276123046875],"size":[410.71795654296875,598.05126953125],"flags":{},"order":3,"mode":0,"inputs":[{"name":"Model","type":"RUNWAREMODEL","link":1},{"name":"Lora","type":"RUNWARELORA","link":null,"shape":7},{"name":"ControlNet","type":"RUNWARECONTROLNET","link":null,"shape":7},{"name":"Refiner","type":"RUNWAREREFINER","link":4,"shape":7},{"name":"seedImage","type":"IMAGE","link":null,"shape":7},{"name":"maskImage","type":"MASK","link":null,"shape":7}],"outputs":[{"name":"IMAGE","type":"IMAGE","links":[3],"slot_index":0}],"properties":{"Node name for S&R":"Runware Image Inference"},"widgets_values":["A juicy burger on the lunar surface, an astronaut in a sleek white spacesuit gestures towards it, with the vibrant blue Earth rising behind him, soft golden light illuminating the burger's crispy bun and melted cheese, casting a warm glow on the desolate moon rocks.","",false,"Square HD (1024x1024)",1024,1024,50,"Default",6.5,362638305241772,"randomize",0,0.8,false,32,1],"bgcolor":"#6c5ce7"},{"id":5,"type":"Runware Refiner","pos":[-175.74874877929688,74.93402099609375],"size":[315,82],"flags":{},"order":2,"mode":0,"inputs":[{"name":"Model","type":"RUNWAREMODEL","link":6}],"outputs":[{"name":"RUNWAREREFINER","type":"RUNWAREREFINER","links":[4],"slot_index":0}],"properties":{"Node name for S&R":"Runware Refiner"},"widgets_values":[0,60],"bgcolor":"#6c5ce7"}],"links":[[1,1,0,2,0,"RUNWAREMODEL"],[3,2,0,4,0,"IMAGE"],[4,5,0,2,3,"RUNWAREREFINER"],[6,6,0,5,0,"RUNWAREMODEL"]],"groups":[],"config":{},"extra":{"ds":{"scale":1.0263162364614136,"offset":[568.4152507588258,429.23556185554514]}},"version":0.4} \ No newline at end of file diff --git a/workflows/Runware_Inpainting.json b/workflows/Runware_Inpainting.json new file mode 100644 index 0000000..d290f39 --- /dev/null +++ b/workflows/Runware_Inpainting.json @@ -0,0 +1 @@ +{"last_node_id":30,"last_link_id":36,"nodes":[{"id":26,"type":"LoadImage","pos":[-440.0780029296875,-294.6051025390625],"size":[371,462],"flags":{},"order":0,"mode":0,"inputs":[],"outputs":[{"name":"IMAGE","type":"IMAGE","links":[31],"slot_index":0},{"name":"MASK","type":"MASK","links":[32],"slot_index":1}],"properties":{"Node name for S&R":"LoadImage"},"widgets_values":["clipspace/clipspace-mask-6099630.100000024.png [input]","image"]},{"id":25,"type":"Runware Model Search","pos":[-427.0780029296875,-472.6051025390625],"size":[315,130],"flags":{"collapsed":false},"order":1,"mode":0,"inputs":[],"outputs":[{"name":"Runware Model","type":"RUNWAREMODEL","links":[30],"slot_index":0}],"properties":{"Node name for S&R":"Runware Model Search"},"widgets_values":["","SDXL Lightning","Base Model","civitai:133005@357609 (Juggernaut XL from RunDiffusion V9+RDPhoto2-Lightning_4S)"],"bgcolor":"#6c5ce7"},{"id":23,"type":"PreviewImage","pos":[423.9219970703125,-451.6051025390625],"size":[553,581],"flags":{},"order":3,"mode":0,"inputs":[{"name":"images","type":"IMAGE","link":29}],"outputs":[],"properties":{"Node name for S&R":"PreviewImage"},"widgets_values":[]},{"id":24,"type":"Runware Image Inference","pos":[-42.0780029296875,-489.6051025390625],"size":[443,656],"flags":{},"order":2,"mode":0,"inputs":[{"name":"Model","type":"RUNWAREMODEL","link":30},{"name":"Lora","type":"RUNWARELORA","link":null,"shape":7},{"name":"ControlNet","type":"RUNWARECONTROLNET","link":null,"shape":7},{"name":"Refiner","type":"RUNWAREREFINER","link":null,"shape":7},{"name":"seedImage","type":"IMAGE","link":31,"shape":7},{"name":"maskImage","type":"MASK","link":32,"shape":7}],"outputs":[{"name":"IMAGE","type":"IMAGE","links":[29],"slot_index":0}],"properties":{"Node name for S&R":"Runware Image Inference"},"widgets_values":["a cat","",false,"Square HD (1024x1024)",1024,1024,8,"Default",2.5,696000855492372,"randomize",0,0.79,true,50,1],"bgcolor":"#6c5ce7"}],"links":[[29,24,0,23,0,"IMAGE"],[30,25,0,24,0,"RUNWAREMODEL"],[31,26,0,24,4,"IMAGE"],[32,26,1,24,5,"MASK"]],"groups":[],"config":{},"extra":{"ds":{"scale":1,"offset":[634.0779979367994,575.0425882124623]}},"version":0.4} \ No newline at end of file diff --git a/workflows/Runware_PhotoMaker_v2.json b/workflows/Runware_PhotoMaker_v2.json new file mode 100644 index 0000000..a14adee --- /dev/null +++ b/workflows/Runware_PhotoMaker_v2.json @@ -0,0 +1 @@ +{"last_node_id":13,"last_link_id":13,"nodes":[{"id":10,"type":"LoadImage","pos":[-11.610055923461914,-225.45167541503906],"size":[324.9819641113281,419.80859375],"flags":{},"order":0,"mode":0,"inputs":[],"outputs":[{"name":"IMAGE","type":"IMAGE","links":[11],"slot_index":0},{"name":"MASK","type":"MASK","links":null}],"properties":{"Node name for S&R":"LoadImage"},"widgets_values":["ggasdw.jpg","image"]},{"id":11,"type":"LoadImage","pos":[28.317718505859375,-268.37384033203125],"size":[315,314],"flags":{"collapsed":true},"order":1,"mode":0,"inputs":[],"outputs":[{"name":"IMAGE","type":"IMAGE","links":[12],"slot_index":0},{"name":"MASK","type":"MASK","links":null}],"properties":{"Node name for S&R":"LoadImage"},"widgets_values":["qwd2qdasf.jpg","image"]},{"id":12,"type":"LoadImage","pos":[172.05775451660156,-269.3723449707031],"size":[315,314],"flags":{"collapsed":true},"order":2,"mode":0,"inputs":[],"outputs":[{"name":"IMAGE","type":"IMAGE","links":[9]},{"name":"MASK","type":"MASK","links":null}],"properties":{"Node name for S&R":"LoadImage"},"widgets_values":["qwd2s.jpg","image"]},{"id":13,"type":"Runware Model Search","pos":[108.17327117919922,-327.2672424316406],"size":[315,130],"flags":{"collapsed":true},"order":3,"mode":0,"inputs":[],"outputs":[{"name":"Runware Model","type":"RUNWAREMODEL","links":[13],"slot_index":0}],"properties":{"Node name for S&R":"Runware Model Search"},"widgets_values":["","All","Base Model","civitai:133005@288982 (SDXL Juggernaut XL V8)"],"bgcolor":"#6c5ce7"},{"id":9,"type":"Runware PhotoMaker V2","pos":[333.765380859375,-334.2550964355469],"size":[411.97833251953125,527.9025268554688],"flags":{},"order":4,"mode":0,"inputs":[{"name":"Model","type":"RUNWAREMODEL","link":13},{"name":"Image 1","type":"IMAGE","link":9,"shape":7},{"name":"Image 2","type":"IMAGE","link":12,"shape":7},{"name":"Image 3","type":"IMAGE","link":11,"shape":7},{"name":"Image 4","type":"IMAGE","link":null,"shape":7}],"outputs":[{"name":"IMAGE","type":"IMAGE","links":[5],"slot_index":0}],"properties":{"Node name for S&R":"Runware PhotoMaker V2"},"widgets_values":["a girl wearing a red dress, ilya kuvshinov, anime, pixiv top monthly, trending on artstation, cinematic, danbooru, zerochan art, kyoto animation","nsfw, bad quality, deformed, out of frame, watermark, text","Fantasy art","Square HD (1024x1024)",1024,1024,25,"Default",6.5,15,0,484392277852135,"randomize",1],"bgcolor":"#6c5ce7"},{"id":7,"type":"PreviewImage","pos":[772.9705200195312,-347.2317810058594],"size":[528.4240112304688,550.0520629882812],"flags":{},"order":5,"mode":0,"inputs":[{"name":"images","type":"IMAGE","link":5}],"outputs":[],"properties":{"Node name for S&R":"PreviewImage"},"widgets_values":[]}],"links":[[5,9,0,7,0,"IMAGE"],[9,12,0,9,1,"IMAGE"],[11,10,0,9,3,"IMAGE"],[12,11,0,9,2,"IMAGE"],[13,13,0,9,0,"RUNWAREMODEL"]],"groups":[],"config":{},"extra":{"ds":{"scale":1.001808834538306,"offset":[394.91668730768725,442.4964682151535]}},"version":0.4} \ No newline at end of file