diff --git a/MangoModelData.py b/MangoModelData.py new file mode 100644 index 0000000..08c8f28 --- /dev/null +++ b/MangoModelData.py @@ -0,0 +1,58 @@ +import os +import json + +class MangoModelData: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + # Checkpoint name passed from KSamplerMango (may include subfolder) + "ckpt_name": ("STRING", {"tooltip": "Checkpoint name"}), + } + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "export_model_data" + CATEGORY = "Mango Node Pack/Metadata" + + def export_model_data(self, ckpt_name): + # Determine path to JSON file containing manual model usage tips + data_path = os.path.join(os.path.dirname(__file__), 'model_data.json') + + # Check for existence + if not os.path.isfile(data_path): + return (f"[ModelData] ERROR: file not found: {data_path}",) + + # Load JSON + try: + with open(data_path, 'r', encoding='utf-8') as f: + db = json.load(f) + except Exception as e: + return (f"[ModelData] ERROR: failed to load JSON ({type(e).__name__}): {e}",) + + # Validate JSON structure + if not isinstance(db, dict) or not db: + return ("[ModelData] ERROR: model_data.json is empty or not a JSON object",) + + # Extract the filename portion + base_name = os.path.basename(ckpt_name) + + # Lookup strategy: full key, basename, then case-insensitive basename + model_entry = db.get(ckpt_name) + if model_entry is None: + model_entry = db.get(base_name) + if model_entry is None: + lower_base = base_name.lower() + for key, val in db.items(): + if os.path.basename(key).lower() == lower_base: + model_entry = val + break + + # If still not found, list available keys in the error + if model_entry is None: + available = ", ".join(db.keys()) + return (f"[ModelData] no entry for '{base_name}'. Available keys: {available}",) + + # Format and return the JSON snippet using the base filename + snippet = json.dumps({base_name: model_entry}, indent=2) + return (snippet,) diff --git a/ModelPreviewMango.py b/ModelPreviewMango.py new file mode 100644 index 0000000..cf3a843 --- /dev/null +++ b/ModelPreviewMango.py @@ -0,0 +1,85 @@ +import os +import math +import yaml +from PIL import Image +import folder_paths + +def collect_dirs(key_name: str): + """ + key_name: "checkpoints" or "loras" + Returns a list of absolute dirs: + 1) ComfyUI/models/ + 2) For each section in extra_model_paths.yaml: + section.base_path + '/' + section[key_name] + """ + ROOT = os.getcwd() + dirs = [os.path.join(ROOT, "models", key_name)] + yaml_file = os.path.join(ROOT, "extra_model_paths.yaml") + if os.path.exists(yaml_file): + with open(yaml_file, "r") as f: + cfg = yaml.safe_load(f) or {} + # Each section is a dict with base_path and relative subdirs + for section in cfg.values(): + base = section.get("base_path", ROOT) + rel = section.get(key_name) + if rel: + dirs.append(os.path.abspath(os.path.join(base, rel))) + return dirs + +class PreviewImageNode: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "ckpt_name": ("STRING",), + "lora_stack": ("LORA_STACK",), + } + } + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("preview",) + FUNCTION = "preview_images" + CATEGORY = "Mango Node Pack/Loaders" + + def preview_images(self, ckpt_name, lora_stack): + # strip off extensions so "foo.ckpt" β†’ "foo" + base_name = os.path.splitext(ckpt_name)[0] + + exts = [".png", ".jpg", ".jpeg", ".webp", ".bmp", ".tiff"] + + def find_image_in(dirs, name): + for d in dirs: + if not os.path.isdir(d): + continue + for ext in exts: + candidate = os.path.join(d, name + ext) + if os.path.exists(candidate): + print(f"[PreviewImage] βœ” Found {name+ext} in {d}") + return candidate + print(f"[PreviewImage] ✘ No preview for '{name}' in {dirs}") + return None + + # build our search lists + model_dirs = collect_dirs("checkpoints") + lora_dirs = collect_dirs("loras") + + imgs = [] + # 1) model preview + mp = find_image_in(model_dirs, base_name) + imgs.append(Image.open(mp).convert("RGB") if mp else Image.new("RGB",(512,512),(40,40,40))) + + # 2) up to 5 LoRAs + for name, *_ in lora_stack[:5]: + if not name: + continue + lp = find_image_in(lora_dirs, name) + imgs.append(Image.open(lp).convert("RGB") if lp else Image.new("RGB",(512,512),(80,80,80))) + + # 3) composite 2Γ—3 grid + cols = 3 + rows = math.ceil(len(imgs)/cols) + w, h = imgs[0].size + grid = Image.new("RGB", (cols*w, rows*h), (20,20,20)) + for i, im in enumerate(imgs): + grid.paste(im, ((i%cols)*w, (i//cols)*h)) + + return (grid,) diff --git a/README.md b/README.md deleted file mode 100644 index 96bd622..0000000 --- a/README.md +++ /dev/null @@ -1,149 +0,0 @@ -# ComfyUI MangoNodePack - -*A collection of enhanced nodes for ComfyUI with metadata handling for Civitai and streamlined model loading.* - ---- - -## **UPDATE** - -Added Image Loader for batch load images for Img2Img creations. To use it: -1. Create a new folder in the output folder -2. Insert a batch of images to the created folder -3. Browse and choose output's subfolders from a dropdown list of the node -4. All images in the folder will be processed in Img2Img creations - ---- - -## πŸš€ **Features** - -πŸ‘‰ **LoRA Metadata Exporter** – Extracts trigger words from LoRA metadata (local & online).\ -πŸ‘‰ **Prompt Formatters** – Organizes quality tags, descriptions, and prompt structure.\ -πŸ‘‰ **Smart Model Loader** – Loads models and LoRAs with automatic metadata extraction.\ -πŸ‘‰ **Enhanced KSampler** – Improved metadata tracking with LoRA and checkpoint integration.\ -πŸ‘‰ **Diffusion Model Loader** – Like SDXL/PONY version, it loads model along with clips and LoRAs, extracting metadata and and passing them as string.\ -πŸ‘‰ **FLUX Sampler** – Metadata tracking with LoRA and checkpoint integration.\ -πŸ‘‰ **FLUX Guidance** – The very same FLUX Guidance as ComfyUI original, but with guidance extraction to save as metadata.\ -πŸ‘‰ **Advanced Image Saver** – Saves images with embedded metadata, filenames, and auto-numbering.\ -πŸ‘‰ **Custom Latent Generator** – Predefined resolutions for common aspect ratios.\ -πŸ‘‰ **Prompt Saver** – Don't lose your prompts ever again, by saving their copy in the output folder (or specified subfolder).\ -πŸ‘‰ **Prompt Loader** – Load saved prompts easily from the dropdown list, and connect to the Positive/Negative nodes. - -Known issue: Occasionally, not every LoRA will be visible on Civitai as the hash will not be recognized correctly. It's most likely caused by models' updates on Civitai, and re-downloading the model solves the issue. - ---- - -## πŸ’½ **Installation** - -### **Requirements** - -- **ComfyUI** installed and running. -- Python dependencies (install via terminal): - ```sh - pip install safetensors - ``` - -### **Installation Steps** - -Find Mango Node Pack in the ComfyUI Manager! - -OR - -1. Clone the repository: - ```sh - git clone https://github.com/mang01010/MangoNodePack.git - ``` -2. Move the `MangoNodePack` folder into ComfyUI’s `custom_nodes` directory. -3. Restart ComfyUI to load the new nodes. - ---- - -## πŸ“ **Node Overview** - -### **1️⃣ Trigger Exporter (Mango)** - -πŸ’‘ **Category:** Metadata - -- The pack started with this node. It extracts trigger words from LoRAs and concatenates them as comma delimited string that can be pushed further, so you won't have to think about these. Don't fail using downloaded LoRA ever again! - -![Overlay Preview](Screenshots/TriggerExporter.png) - -### **2️⃣ Prompt (Mango)** - -πŸ’‘ **Category:** Metadata - -- This node organizes prompts into three separate text boxes, making them easier to read and edit. It also supports direct integration with the Trigger Exporter. - -![Overlay Preview](Screenshots/Prompt.png) - -### **3️⃣ Prompt /w Embedding (Mango)** - -πŸ’‘ **Category:** Metadata - -- Does the same thing as Prompt (Mango), but divides prompt further to add embeddings in seperate field. - -### **4️⃣ Loader (Mango)** - -πŸ’‘ **Category:** Loaders - -- Loads Stable Diffusion models and up to 5 LoRAs. -- Computes model hashes and applies LoRAs with weight control. - -### **5️⃣ KSampler (Mango)** - -πŸ’‘ **Category:** Sampling - -- This version of the KSampler node features eight inputs. The standard inputsβ€”model, latent image, positive, and negativeβ€”are used for image generation. Additionally, it includes a LoRA stack, checkpoint name, and separate positive and negative text fields as a metadata workaround for: - -### **6️⃣ Image Saver (Mango)** - -πŸ’‘ **Category:** Metadata - -- Saves images with embedded metadata (prompt, seed, CFG, steps, sampler, checkpoint and LoRAs info), ensuring compatibility with Civitai for direct metadata reading. -- Supports auto-numbering and custom subdirectories. - -![Overlay Preview](Screenshots/Metadata.png) - -### **7️⃣ Latent Image (Mango)** - -πŸ’‘ **Category:** Loaders - -- Generates latent tensors in predefined aspect ratios (e.g., 1:1, 16:9, etc.). - -### **8️⃣ Prompt Saver and Loader** - -πŸ’‘ **Category:** Metadata - -- Save a copy of prompts in the default output folder (or specified subfolder). -- Load saved prompts easily from the dropdown list, and connect to the Positive/Negative nodes. - -![Overlay Preview](Screenshots/LoadPrompt.png) - ---- - -## πŸ›  **Usage Guide** - -Once installed, find the **MangoNodePack** nodes inside ComfyUI. - -**Example Workflow:** - -![Overlay Preview](Screenshots/Workflow.png) - ---- - - -## πŸ“ **License** - -This project is licensed under the MIT License – see the [LICENSE](LICENSE) file for details. - - ---- -### **πŸ“’ Check out these awesome node packs!** -- [ComfyUI](https://github.com/comfyanonymous/ComfyUI) -- [rgthree-comfy](https://github.com/rgthree/rgthree-comfy) -- [Efficiency-Nodes](https://github.com/jags111/efficiency-nodes-comfyui) -- [ComfyUI Comfyroll Custom Nodes](https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes) -- [ComfyUI Custom Scripts](https://github.com/pythongosssss/ComfyUI-Custom-Scripts) -- [WAS Node Suite](https://github.com/WASasquatch/was-node-suite-comfyui) -- [Easy-Use](https://github.com/yolain/ComfyUI-Easy-Use) -- [PRNodes](https://github.com/pikenrover/ComfyUI_PRNodes) - diff --git a/__init__.py b/__init__.py index c4a34d2..b4ae13e 100644 --- a/__init__.py +++ b/__init__.py @@ -13,6 +13,7 @@ from .MangoPromptLoad import MangoPromptLoad from .LoraStackMango import LoraStackMango from .MangoImageLoader import MangoImageLoader from .MangoLoader10Loras import MangoLoader10Loras +from .MangoModelData import MangoModelData NODE_CLASS_MAPPINGS = { "MangoTriggerExporter": MangoTriggerExporter, @@ -30,6 +31,7 @@ NODE_CLASS_MAPPINGS = { "LoraStackMango": LoraStackMango, "MangoImageLoader": MangoImageLoader, "MangoLoader10Loras": MangoLoader10Loras, + "MangoModelData": MangoModelData, } NODE_DISPLAY_NAME_MAPPINGS = { @@ -48,4 +50,5 @@ NODE_DISPLAY_NAME_MAPPINGS = { "LoraStackMango": "LoRA Stack (Mango)", "MangoImageLoader": "Image Loader (Mango)", "MangoLoader10Loras": "Loader (Mango + 10 Loras)", + "MangoModelData": "Model Data (Mango)", } diff --git a/model_data.json b/model_data.json new file mode 100644 index 0000000..7da94c8 --- /dev/null +++ b/model_data.json @@ -0,0 +1,72 @@ +{ + "animij_v5.safetensors": { + "clip_skip": "2", + "steps": "30+", + "cfg": "4-7", + "sampler_name": "Euler a", + "scheduler": "N/A", + + "positive": "N/A", + "negative": "worst_quality, bad_quality, poorly_detailed", + + "resolution": "1024" + }, + "realDream_sdxlPony15.safetensors": { + "clip_skip": "2", + "steps": "20-30", + "cfg": "5-9", + "sampler_name": "Euler a / DPM++ SDE", + "scheduler": "Karras", + + "positive": "score_9, score_8_up, score_7_up, (Masterpiece, Best Quality, Highres:1.2), 35mm lens, 35mm RAW photo, photorealistic, realistic, extremely detailed, film grain, front light", + "negative": "score_6, score_5, score_4, score_2, score_1", + + "resolution": "512 / 768", + "High-Res Fix tip": "Upscale by 1.5 and denoise at 0.6", + + "Recommended Lora 1": "amateur_style_v1_pony.safetensors @ 2.5", + "Recommended Lora 2": "Dramatic Lighting Slider.safetensors @ 1.5" + }, + "ponyDiffusionV6XL_v6StartWithThisOne.safetensors": { + "clip_skip": "2", + "steps": "25-30", + "cfg": "5-9", + "sampler_name": "Euler a", + "scheduler": "N/A", + + "positive": "score_9, score_8_up, score_7_up, score_6_up, score_5_up, score_4_up, just describe what you want, tag1, tag2", + "negative": "N/A", + + "resolution": "1024" + }, + "ultrarealFineTune_v4.safetensors": { + "steps": "30-50", + "cfg": "0.9-1", + "sampler_name": "DPM++ 2M", + "scheduler": "Beta" + }, + "8StepsCreartHyperFlux_creartUltimate.safetensors": { + "steps": "8-10", + "cfg": "3.5", + "sampler_name": "Euler", + "scheduler": "Beta" + }, + "c4pacitor_xV1.safetensors": { + "steps": "16-32", + "cfg": "3-6", + "sampler_name": "DEIS", + "scheduler": "Beta/Beta57/Bong Tangent" + }, + "77FluxAnime_i.safetensors": { + "steps": "25-30", + "cfg": "3.5-7.5", + "sampler_name": "euler", + "scheduler": "normal" + }, + "illustriousNova_V10.safetensors": { + "steps": "30-50", + "cfg": "5-7", + "sampler_name": "DPM++ 2M SDE", + "scheduler": "Karras" + } +}