Files
HappyOnigiri-ComfyUI-Meld/py/image_manager/nodes.py
T

187 lines
7.1 KiB
Python

import json
import os
import re
import time
from datetime import datetime
import folder_paths
import numpy as np
import server
from comfy.cli_args import args
from PIL import Image
from PIL.PngImagePlugin import PngInfo
from ..load_image_configs.metadata_helper import MetadataHelper
from .database import get_db_connection
# --- Custom Node Definition ---
class MeldNexus:
def __init__(self):
self.output_dir = folder_paths.get_output_directory()
self.type = "output"
self.prefix_append = ""
self.compress_level = 4
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"images": ("IMAGE", ),
"filename_prefix": ("STRING", {
"default": "MeldFlow",
"tooltip": (
"The prefix for the file to save. This may include formatting "
"information such as %date:yyyy-MM-dd% or %date:yyyy_MM_dd_HHmmss%.\n"
"yyyy: year, MM: month, dd: day, HH: hour, mm: minute, ss: second."
),
}),
},
"optional": {
"positive": ("STRING", {"forceInput": True, "multiline": True}),
"negative": ("STRING", {"forceInput": True, "multiline": True}),
"tags": ("STRING", {"multiline": False}),
},
"hidden": {
"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"
},
}
RETURN_TYPES = ()
FUNCTION = "save_images"
OUTPUT_NODE = True
CATEGORY = "MeldFlow/Nexus"
def save_images(
self,
images,
filename_prefix="MeldFlow",
positive=None,
negative=None,
tags=None,
prompt=None,
extra_pnginfo=None,
):
if filename_prefix is None:
filename_prefix = "MeldFlow"
filename_prefix += self.prefix_append
tokens = re.findall(r'%(.*?)%', filename_prefix)
for token in tokens:
if token.startswith('date:'):
format_str = token[5:]
# Simple mapper for common date formats
py_format = format_str.replace('yyyy', '%Y').replace('yy', '%y') \
.replace('MM', '%m').replace('dd', '%d') \
.replace('HH', '%H').replace('mm', '%M') \
.replace('ss', '%S')
try:
formatted_date = datetime.now().strftime(py_format)
filename_prefix = filename_prefix.replace(f'%{token}%', formatted_date)
except Exception:
pass
elif token == 'date':
formatted_date = datetime.now().strftime('%Y-%m-%d')
filename_prefix = filename_prefix.replace('%date%', formatted_date)
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(
filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0]
)
results = list()
conn = get_db_connection()
cursor = conn.cursor()
# Resolve Metadata
resolved_positive = positive
resolved_negative = negative
if not resolved_positive or not resolved_negative:
# Try to parse from workflow
logs: list[str] = []
wf_pos, wf_neg = "", ""
if extra_pnginfo and 'workflow' in extra_pnginfo:
wf_pos, wf_neg = MetadataHelper.parse_workflow_json(extra_pnginfo['workflow'], logs)
elif prompt:
wf_pos, wf_neg = MetadataHelper.parse_prompt_json(prompt, logs)
if not resolved_positive:
resolved_positive = wf_pos
if not resolved_negative:
resolved_negative = wf_neg
tag_list = []
if tags:
tag_list = [t.strip() for t in tags.split(',') if t.strip()]
# Split prompts into lists for separate registration
pos_list = [p.strip() for p in resolved_positive.split(',') if p.strip()] if resolved_positive else []
neg_list = [n.strip() for n in resolved_negative.split(',') if n.strip()] if resolved_negative else []
for (batch_number, image) in enumerate(images):
# Tensor [B, H, W, C] -> PIL
# ComfyUI images are [B, H, W, C] Tensors
i = 255. * image.cpu().numpy()
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
# Save metadata to PNG
metadata = None
if not args.disable_metadata:
metadata = PngInfo()
if prompt is not None:
metadata.add_text("prompt", json.dumps(prompt))
if extra_pnginfo is not None:
for x in extra_pnginfo:
metadata.add_text(x, json.dumps(extra_pnginfo[x]))
filename_with_batch_num = filename.replace("%batch_num%", str(batch_number))
file = f"{filename_with_batch_num}_{counter:05}.png"
img.save(os.path.join(full_output_folder, file), pnginfo=metadata, compress_level=self.compress_level)
timestamp = time.time()
# Insert Image
cursor.execute(
"INSERT INTO images (filename, subfolder, created_at, is_deleted) VALUES (?, ?, ?, 0)",
(file, subfolder, timestamp)
)
image_id = cursor.lastrowid
# Insert Prompts
for p in pos_list:
cursor.execute("INSERT OR IGNORE INTO positive_prompts (name) VALUES (?)", (p,))
cursor.execute("SELECT id FROM positive_prompts WHERE name = ?", (p,))
pp_id = cursor.fetchone()[0]
cursor.execute(
"INSERT INTO positive_prompt_image_relations (image_id, positive_prompt_id) VALUES (?, ?)",
(image_id, pp_id)
)
for n in neg_list:
cursor.execute("INSERT OR IGNORE INTO negative_prompts (name) VALUES (?)", (n,))
cursor.execute("SELECT id FROM negative_prompts WHERE name = ?", (n,))
np_id = cursor.fetchone()[0]
cursor.execute(
"INSERT INTO negative_prompt_image_relations (image_id, negative_prompt_id) VALUES (?, ?)",
(image_id, np_id)
)
# Insert Tags
for tag in tag_list:
cursor.execute("INSERT OR IGNORE INTO tags (name) VALUES (?)", (tag,))
cursor.execute("SELECT id FROM tags WHERE name = ?", (tag,))
tag_id = cursor.fetchone()[0]
cursor.execute("INSERT INTO tag_image_relations (image_id, tag_id) VALUES (?, ?)", (image_id, tag_id))
results.append({"filename": file, "subfolder": subfolder, "type": self.type})
counter += 1
conn.commit()
conn.close()
# Notify the frontend that images have been saved (Rule 2: Real-time update)
server.PromptServer.instance.send_sync("meld-nexus-image-saved", {"count": len(results)})
return {"ui": {"images": results}}