diff --git a/.gitignore b/.gitignore index ce9d3be..2919e7a 100644 --- a/.gitignore +++ b/.gitignore @@ -77,4 +77,7 @@ htmlcov/ # mypy .mypy_cache/ .dmypy.json -dmypy.json \ No newline at end of file +dmypy.json + +# AI +.claude \ No newline at end of file diff --git a/__init__.py b/__init__.py index 5967780..c907ba0 100644 --- a/__init__.py +++ b/__init__.py @@ -26,7 +26,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { __all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] -__version__ = "2.0.2" +__version__ = "2.0.3" __author__ = "Bawk Nodes" __description__ = "A complete collection of FLUX-optimized ComfyUI nodes for enhanced workflows" @@ -38,4 +38,4 @@ print(f"\033[94m • 🚀 Diffusion Model Loader (Advanced) - FLUX-optimized print(f"\033[95m • 🎲 FLUX Wildcard Encoder - Text encoding with wildcard support and 6 LoRA slots\033[0m") print(f"\033[92m • 💾 FLUX Image Saver - Organized image saving with metadata and prompt files\033[0m") print(f"\033[91m • 🐓 Bawk Sampler (All-in-One) - Combined latent optimizer and sampler\033[0m") -print(f"\033[3m\033[96m • 📦 Modular architecture for easy maintenance and debugging\033[0m") \ No newline at end of file +print(f"\033[3m\033[96m • 📦 Modular architecture for easy maintenance and debugging\033[0m") \ No newline at end of file diff --git a/modules/__init__.py b/modules/__init__.py index 79fd655..31f20d9 100644 --- a/modules/__init__.py +++ b/modules/__init__.py @@ -16,5 +16,5 @@ __all__ = [ ] # Version info -__version__ = "2.0.2" +__version__ = "2.0.3" __author__ = "judd" \ No newline at end of file diff --git a/nodes/flux_image_saver.py b/nodes/flux_image_saver.py index 9f0c09c..aadac53 100644 --- a/nodes/flux_image_saver.py +++ b/nodes/flux_image_saver.py @@ -8,8 +8,11 @@ import json import re import numpy as np import folder_paths +import requests +import io +import base64 from datetime import datetime -from typing import Dict, Any +from typing import Dict, Any, List class FluxImageSaver: @@ -29,6 +32,8 @@ class FluxImageSaver: "quality": ("INT", {"default": 95, "min": 1, "max": 100}), "save_metadata": ("BOOLEAN", {"default": True}), "save_prompt": ("BOOLEAN", {"default": True, "tooltip": "Save the processed prompt as a separate text file"}), + "send_to_discord": ("BOOLEAN", {"default": False, "tooltip": "Send images to Discord via webhook"}), + "discord_webhook_url": ("STRING", {"default": "", "tooltip": "Discord webhook URL (only used when send_to_discord is True)"}), }, "hidden": { "prompt_hidden": "PROMPT", @@ -41,14 +46,15 @@ class FluxImageSaver: FUNCTION = "save_images" CATEGORY = "BawkNodes/image" OUTPUT_NODE = True - DESCRIPTION = "Save FLUX-generated images with organized folder structure" + DESCRIPTION = "Save FLUX-generated images with organized folder structure and optional Discord webhook integration" def __init__(self): self.output_dir = folder_paths.get_output_directory() self.type = "output" - def save_images(self, images, model_string, processed_prompt, prefix="flux_image", + def save_images(self, images, model_string, processed_prompt, prefix="flux_image", format="png", quality=95, save_metadata=True, save_prompt=True, + send_to_discord=False, discord_webhook_url="", prompt_hidden=None, extra_pnginfo=None): """ Save images with FLUX-optimized organization and metadata @@ -133,7 +139,14 @@ class FluxImageSaver: model_string, format, quality, processed_prompt) print(f"[FluxImageSaver] Returning {len(results)} images for preview") - + + # Send to Discord if enabled + if send_to_discord and discord_webhook_url.strip(): + try: + self._send_to_discord(saved_paths, discord_webhook_url, processed_prompt, model_string) + except Exception as e: + print(f"[FluxImageSaver] Discord webhook failed: {str(e)}") + return {"ui": {"images": results}} except Exception as e: @@ -200,4 +213,91 @@ class FluxImageSaver: print(f"[FluxImageSaver] Metadata saved: {metadata_path}") except Exception as e: - print(f"[FluxImageSaver] Failed to save metadata: {str(e)}") \ No newline at end of file + print(f"[FluxImageSaver] Failed to save metadata: {str(e)}") + + def _send_to_discord(self, image_paths: List[str], webhook_url: str, prompt: str, model: str): + """Send images to Discord via webhook with batch support""" + if not webhook_url or not image_paths: + return + + print(f"[FluxImageSaver] Sending {len(image_paths)} images to Discord") + + # Prepare the embed with generation info + embed = { + "title": "🎨 New AI Generation", + "description": f"**Model:** {model}\n**Prompt:** {prompt[:1000]}{'...' if len(prompt) > 1000 else ''}", + "color": 0x5865F2, # Discord blurple + "timestamp": datetime.now().isoformat(), + "footer": {"text": "Generated with ComfyUI BawkNodes"} + } + + # Discord has a limit of 10 files per message, so we'll batch them + max_files_per_message = 10 + for i in range(0, len(image_paths), max_files_per_message): + batch_paths = image_paths[i:i + max_files_per_message] + self._send_discord_batch(webhook_url, batch_paths, embed if i == 0 else None, i // max_files_per_message + 1) + + def _send_discord_batch(self, webhook_url: str, image_paths: List[str], embed: dict = None, batch_num: int = 1): + """Send a batch of images to Discord""" + try: + files = {} + payload = {} + + # Add embed only to first batch + if embed: + payload["embeds"] = [embed] + + # Add batch info if multiple batches + if batch_num > 1: + payload["content"] = f"📎 Batch {batch_num} of images" + + # Prepare files for upload + for idx, image_path in enumerate(image_paths): + if not os.path.exists(image_path): + print(f"[FluxImageSaver] Warning: Image file not found: {image_path}") + continue + + try: + with open(image_path, 'rb') as f: + file_data = f.read() + + filename = os.path.basename(image_path) + files[f'file{idx}'] = (filename, file_data, self._get_content_type(image_path)) + + except Exception as e: + print(f"[FluxImageSaver] Failed to read image {image_path}: {str(e)}") + continue + + if not files: + print(f"[FluxImageSaver] No valid images to send in batch {batch_num}") + return + + # Send to Discord + response = requests.post( + webhook_url, + data={"payload_json": json.dumps(payload)} if payload else None, + files=files, + timeout=30 + ) + + if response.status_code == 200: + print(f"[FluxImageSaver] Successfully sent batch {batch_num} with {len(files)} images to Discord") + else: + print(f"[FluxImageSaver] Discord webhook failed for batch {batch_num}: {response.status_code} - {response.text}") + + except requests.exceptions.RequestException as e: + print(f"[FluxImageSaver] Network error sending to Discord: {str(e)}") + except Exception as e: + print(f"[FluxImageSaver] Unexpected error sending to Discord: {str(e)}") + + def _get_content_type(self, image_path: str) -> str: + """Get the appropriate content type for the image""" + ext = os.path.splitext(image_path)[1].lower() + content_types = { + '.png': 'image/png', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.webp': 'image/webp', + '.gif': 'image/gif' + } + return content_types.get(ext, 'image/png') \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 90411f4..8ea7797 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "comfyui-bawknodes" -version = "2.0.2" +version = "2.0.3" description = "A complete collection of FLUX-optimized ComfyUI nodes for enhanced AI image generation workflows." readme = "README.md" license = { file = "LICENSE" }