Added Load LoRA Tag

This commit is contained in:
Level Pixel Dev
2024-11-13 03:35:29 +06:00
parent 35f0c255e0
commit c6edbb21b6
6 changed files with 189 additions and 5 deletions
+18 -2
View File
@@ -38,7 +38,7 @@ It will attempt to use symlinks and junctions to prevent having to copy files an
All nodes Level Pixel:
<img width="1173" alt="level-pixel-nodes" src="https://github.com/user-attachments/assets/60623f84-b02a-4749-9e2c-4ab60431b383">
<img width="1171" alt="level-pixel-nodes_2" src="https://github.com/user-attachments/assets/63b42605-1720-4a10-a54d-d2950d3d013f">
## LLM nodes
@@ -60,6 +60,14 @@ The core functionality is taken from [ComfyUI_VLM_nodes](https://github.com/goka
A more improved version of rembg nodes for ComfyUI with an extended list of models.
To use on GPU, at least CUDA 12.4 (Pytorch cu124) is required, so I recommend upgrading to newer versions of ComfyUI and Pytorch.
If GPU still doesn't work, run:
```
pip uninstall rembg
pip install rembg[gpu]
```
The core functionality is taken from [RemBG nodes for ComfyUI](https://github.com/Loewen-Hob/rembg-comfyui-node-better) and belongs to its authors.
## Autotagger
@@ -78,6 +86,12 @@ Nodes are very convenient because you can use them to remove unnecessary tags by
The core functionality is taken from [comfyui_tag_fillter](https://github.com/sugarkwork/comfyui_tag_fillter) and belongs to its authors.
## Load LoRA Tag
LoRA loader from text in the style of Automatic1111 and Forge WebUI. For this version of loader, text output for errors when loading LoRA has been added as widget on node.
The core functionality is taken from [comfyui_lora_tag_loader](https://github.com/badjeff/comfyui_lora_tag_loader) and belongs to its authors.
## Model Unloader nodes
A node that automatically unloads all checkpoints from memory. It must be added to a sequential chain of nodes in the workflow. There are three versions of this node: Hard (complete unloading of all checkpoints from memory, except for GGUF (not supported yet)), Middle (the same as Hard, but in the future I plan to add widgets with the ability to select a mode), Soft (without unloading checkpoints from memory, just soft cleaning of memory from garbage).
@@ -121,12 +135,14 @@ VLM nodes for ComfyUI/[ComfyUI_VLM_nodes](https://github.com/gokayfem/ComfyUI_VL
Tag Filter nodes for ComfyUI/[comfyui_tag_fillter](https://github.com/sugarkwork/comfyui_tag_fillter) - Best tag filter by category nodes for ComfyUI.
Load LoRA Tag node for ComfyUI/[comfyui_lora_tag_loader](https://github.com/badjeff/comfyui_lora_tag_loader) - Thanks to the author for this great node for LoRAs!
RemBG nodes for ComfyUI/[rembg-comfyui-node](https://github.com/Loewen-Hob/rembg-comfyui-node-better) - RemBG nodes for ComfyUI.
RemBG software package/[rembg](https://github.com/danielgatis/rembg) - Best software to remove background for any object in the picture.
# License
Copyright (c) 2024-present Level Pixel
Copyright (c) 2024-present [Level Pixel](https://github.com/LevelPixel)
Licensed under Apache License
+1
View File
@@ -39,6 +39,7 @@ node_list = [
"io.image_loaders_LP",
"io.image_outputs_LP",
"io.text_outputs_LP",
"io.lora_tag_loader_LP",
"llm.llm_LP",
"tags.tags_utils_LP",
"text.text_utils_LP",
+101
View File
@@ -0,0 +1,101 @@
from pathlib import Path
import folder_paths
import re
# Import ComfyUI files
import comfy.sd
import comfy.utils
class LoraTagLoader:
def __init__(self):
self.loaded_lora = None
self.tag_pattern = r"\<[0-9a-zA-Z\:\_\-\.\s\/\(\)\\\\]+\>"
@classmethod
def INPUT_TYPES(s):
return {"required": { "model": ("MODEL",),
"clip": ("CLIP", ),
"text": ("STRING", {"multiline": True}),
}}
RETURN_TYPES = ("MODEL", "CLIP", "STRING")
RETURN_NAMES = ("MODEL", "CLIP", "STRING")
FUNCTION = "load_lora"
OUTPUT_NODE = False
CATEGORY = "LevelPixel/IO"
def load_lora(self, model, clip, text):
# print(f"\nLoraTagLoader input text: { text }")
founds = re.findall(self.tag_pattern, text)
# print(f"\nfoound lora tags: { founds }")
if len(founds) < 1:
return (model, clip, text)
model_lora = model
clip_lora = clip
log = []
log.append("")
lora_files = folder_paths.get_filename_list("loras")
for f in founds:
tag = f[1:-1]
pak = tag.split(":")
type = pak[0]
if type != 'lora':
continue
name = None
if len(pak) > 1 and len(pak[1]) > 0:
name = pak[1]
else:
continue
wModel = wClip = 0
try:
if len(pak) > 2 and len(pak[2]) > 0:
wModel = float(pak[2])
wClip = wModel
if len(pak) > 3 and len(pak[3]) > 0:
wClip = float(pak[3])
except ValueError:
continue
if name == None:
continue
lora_name = None
for lora_file in lora_files:
if Path(lora_file).name.startswith(name) or lora_file.startswith(name):
lora_name = lora_file
break
if lora_name == None:
log[0] = log[0] + f"NOT found LoRA '{name}' \n"
print(f"bypassed lora tag: { (type, name, wModel, wClip) } >> { lora_name }")
continue
print(f"detected lora tag: { (type, name, wModel, wClip) } >> { lora_name }")
lora_path = folder_paths.get_full_path("loras", lora_name)
lora = None
if self.loaded_lora is not None:
if self.loaded_lora[0] == lora_path:
lora = self.loaded_lora[1]
else:
temp = self.loaded_lora
self.loaded_lora = None
del temp
if lora is None:
lora = comfy.utils.load_torch_file(lora_path, safe_load=True)
self.loaded_lora = (lora_path, lora)
model_lora, clip_lora = comfy.sd.load_lora_for_models(model_lora, clip_lora, lora, wModel, wClip)
plain_prompt = re.sub(self.tag_pattern, "", text)
return {"ui": {"log": log}, "result": (model_lora, clip_lora, plain_prompt)}
NODE_CLASS_MAPPINGS = {
"LoraTagLoader|LP": LoraTagLoader,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"LoraTagLoader|LP": "Load LoRA Tag [LP]",
}
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "comfyui_level_pixel"
description = "Various nodes of the Level Pixel company. Includes convenient advanced nodes for working with images from folders; counting files in a folder; cleaning memory; tag filters. Model Unloader, LLM Unloader (GGUF unloaders), Free memory, Tag Filters, Tag Category Filters, Tag Choice Parser, File counter, Image Loader From Path (with counters), Image Remove Background based on RemBG, Autotagger."
version = "1.0.7"
version = "1.0.8"
license = { file = "LICENSE" }
dependencies = ["torch>=2.0.1", "torchvision>=0.15.2", "numpy", "matplotlib", "scikit-build-core>=0.10.7", "rembg>=2.0.59", "onnxruntime-gpu>=1.18.0", "onnxruntime>=1.20.0"]
+1 -1
View File
@@ -5,5 +5,5 @@ numpy
matplotlib
scikit-build-core>=0.10.7
rembg>=2.0.59
onnxruntime-gpu>=1.18.0
onnxruntime-gpu>=1.20.0
onnxruntime>=1.20.0
+67 -1
View File
@@ -80,7 +80,7 @@ app.registerExtension({
async beforeRegisterNodeDef(nodeType, nodeData, app) {
levelpixel.addStatusTagHandler(nodeType);
if (nodeData.name === "Autotagger|LevelPixel") {
if (nodeData.name === "Autotagger|LP") {
const onExecuted = nodeType.prototype.onExecuted;
nodeType.prototype.onExecuted = function (message) {
const r = onExecuted?.apply?.(this, arguments);
@@ -139,3 +139,69 @@ app.registerExtension({
}
},
});
app.registerExtension({
name: "levelpixel.LoraTagLoader",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
levelpixel.addStatusTagHandler(nodeType);
if (nodeData.name === "LoraTagLoader|LP") {
const onExecuted = nodeType.prototype.onExecuted;
nodeType.prototype.onExecuted = function (message) {
const r = onExecuted?.apply?.(this, arguments);
const pos = this.widgets.findIndex((w) => w.name === "log");
if (pos !== -1) {
for (let i = pos; i < this.widgets.length; i++) {
this.widgets[i].onRemove?.();
}
this.widgets.length = pos;
}
for (const list of message.log) {
const w = ComfyWidgets["STRING"](this, "log", ["STRING", { multiline: true }], app).widget;
w.inputEl.readOnly = true;
w.inputEl.style.opacity = 0.6;
w.value = list;
}
this.onResize?.(this.size);
return r;
};
} else {
const getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
nodeType.prototype.getExtraMenuOptions = function (_, options) {
const r = getExtraMenuOptions?.apply?.(this, arguments);
let img;
if (this.imageIndex != null) {
// An image is selected so select that
img = this.imgs[this.imageIndex];
} else if (this.overIndex != null) {
// No image is selected but one is hovered
img = this.imgs[this.overIndex];
}
if (img) {
let pos = options.findIndex((o) => o.content === "Save Image");
if (pos === -1) {
pos = 0;
} else {
pos++;
}
options.splice(pos, 0, {
content: "LoraTagLoader",
callback: async () => {
let src = img.src;
src = src.replace("/view?", `/levelpixel/loratagloader/tag?node=${this.id}&clientId=${api.clientId}&`);
const res = await (await fetch(src)).json();
alert(res);
},
});
}
return r;
};
}
},
});