From 360138c5e14ec50c10a0780bec2769e7c4884a20 Mon Sep 17 00:00:00 2001 From: Raykosan <71210053+Raykosan@users.noreply.github.com> Date: Mon, 3 Aug 2026 20:38:32 +0300 Subject: [PATCH] Adding an RS Image-Prompt node --- Rayko_Image_Prompt.py | 66 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 60 insertions(+), 6 deletions(-) diff --git a/Rayko_Image_Prompt.py b/Rayko_Image_Prompt.py index 9c64752..82a2aee 100644 --- a/Rayko_Image_Prompt.py +++ b/Rayko_Image_Prompt.py @@ -22,8 +22,9 @@ from aiohttp import web WEB_DIRECTORY = "./web" -KNOWN_PROMPT_NODES = {"CLIPTextEncode", "RSPrompts", "RS_ImagePrompt"} -TEXT_KEYS = ["text", "string", "parameters", "Comment", "Description", "Title"] +KNOWN_PROMPT_NODES = {"CLIPTextEncode", "RSPrompts", "RS_ImagePrompt", "PromptStashPassthrough"} +TEXT_KEYS = ["prompt_text", "text", "string", "parameters", "Comment", "Description", "Title"] + def get_image_files(): input_dir = folder_paths.get_input_directory() @@ -34,6 +35,11 @@ def get_image_files(): files.sort() return files + +# ========================================================= +# Граф ComfyUI, чанк "prompt" (API-формат) +# ========================================================= + def _get_text_from_node(prompt_data, node_id, visited): node_id = str(node_id) if node_id in visited or node_id not in prompt_data: @@ -44,6 +50,7 @@ def _get_text_from_node(prompt_data, node_id, visited): inputs = node.get("inputs", {}) class_type = node.get("class_type", "") + # RS Prompts: если включён и подключён text_input — идём вверх по нему if inputs.get("enable_text_input"): ti = inputs.get("text_input") if isinstance(ti, list) and len(ti) >= 1: @@ -51,15 +58,25 @@ def _get_text_from_node(prompt_data, node_id, visited): if upstream: return upstream + # Прямой виджет "text" (только если это строка, а не ссылка) text = inputs.get("text") if isinstance(text, str) and text: return text + # RS_ImagePrompt: промпт в виджете "prompt_preview" if class_type == "RS_ImagePrompt": prompt_preview = inputs.get("prompt_preview") if isinstance(prompt_preview, str) and prompt_preview: return prompt_preview + # PromptStashPassthrough: если use_input_text=false — берём prompt_text + if class_type == "PromptStashPassthrough": + if not inputs.get("use_input_text", False): + prompt_text = inputs.get("prompt_text") + if isinstance(prompt_text, str) and prompt_text: + return prompt_text + + # Идём глубже по типичным входам (только если это ссылки, а не строки) for key in ("conditioning", "conditioning_1", "conditioning_2", "clip", "text_input", "text"): ref = inputs.get(key) if isinstance(ref, list) and len(ref) >= 1: @@ -68,6 +85,7 @@ def _get_text_from_node(prompt_data, node_id, visited): return result return "" + def _extract_prompt_from_dict(prompt_data): if not isinstance(prompt_data, dict): return "" @@ -80,6 +98,7 @@ def _extract_prompt_from_dict(prompt_data): if isinstance(ref, list) and len(ref) >= 1: negative_refs.add((str(ref[0]), ref[1] if len(ref) > 1 else 0)) + # Проход 1: от сэмплера по positive-связи for node_id, node_data in prompt_data.items(): if not isinstance(node_data, dict): continue @@ -91,21 +110,32 @@ def _extract_prompt_from_dict(prompt_data): if text: return text + # Проход 2 (фолбэк): известные промпт-ноды, кроме негативных for node_id, node_data in prompt_data.items(): if not isinstance(node_data, dict): continue if node_data.get("class_type", "") in KNOWN_PROMPT_NODES: if (str(node_id), 0) in negative_refs: continue - text = node_data.get("inputs", {}).get("text") + inputs = node_data.get("inputs", {}) + + text = inputs.get("text") if isinstance(text, str) and text: return text - prompt_preview = node_data.get("inputs", {}).get("prompt_preview") + + prompt_preview = inputs.get("prompt_preview") if isinstance(prompt_preview, str) and prompt_preview: return prompt_preview + if node_data.get("class_type", "") == "PromptStashPassthrough": + if not inputs.get("use_input_text", False): + prompt_text = inputs.get("prompt_text") + if isinstance(prompt_text, str) and prompt_text: + return prompt_text + return "" + def _extract_prompt(prompt_json_str): try: data = json.loads(prompt_json_str) @@ -113,6 +143,11 @@ def _extract_prompt(prompt_json_str): return "" return _extract_prompt_from_dict(data) + +# ========================================================= +# Граф ComfyUI, чанк "workflow" (UI-формат) — запасной +# ========================================================= + def _extract_prompt_from_workflow(workflow_json_str): try: wf = json.loads(workflow_json_str) @@ -180,6 +215,11 @@ def _extract_prompt_from_workflow(workflow_json_str): return t return "" + +# ========================================================= +# Сырые текстовые ключи (text / string / parameters / ...) +# ========================================================= + def _looks_like_settings(line): keys = ("Steps:", "Sampler:", "CFG scale:", "Seed:", "Size:", "Model hash:", "Model:", "Denoising strength:", "Clip skip:", "ENSAM:", "TI_hashes:") @@ -210,6 +250,11 @@ def _clean_text_prompt(text): lines.pop() return "\n".join(lines).strip() + +# ========================================================= +# EXIF UserComment (A1111 JPG / WebP) +# ========================================================= + def _decode_user_comment(data): if isinstance(data, str): return data @@ -247,6 +292,11 @@ def _extract_a1111_from_exif(pil_img): print(f"[RS Image-Prompt] EXIF error: {e}") return "" + +# ========================================================= +# Главная точка входа +# ========================================================= + def extract_prompt_from_file(input_path): try: with Image.open(input_path) as pil_img: @@ -256,18 +306,21 @@ def extract_prompt_from_file(input_path): print(f"[RS Image-Prompt] Cannot open image: {e}") return "" + # 1) ComfyUI API-граф prompt_json = info.get("prompt", "") if prompt_json: result = _extract_prompt(prompt_json) if result: return result + # 2) ComfyUI UI-граф workflow_json = info.get("workflow", "") if workflow_json: result = _extract_prompt_from_workflow(workflow_json) if result: return result + # 3) Сырые текстовые ключи for key in TEXT_KEYS: value = info.get(key) if isinstance(value, str) and value.strip(): @@ -275,6 +328,7 @@ def extract_prompt_from_file(input_path): if result: return result + # 4) EXIF UserComment (A1111 JPG / WebP) if exif_text: result = _clean_text_prompt(exif_text) if result: @@ -283,6 +337,7 @@ def extract_prompt_from_file(input_path): return "" +# ---------- API-роут для мгновенного чтения в UI ---------- @PromptServer.instance.routes.get("/rayko/get_prompt") async def rayko_get_prompt(request): filename = request.rel_url.query.get("filename", "") @@ -298,7 +353,6 @@ async def rayko_get_prompt(request): class RS_ImagePrompt: @classmethod def INPUT_TYPES(cls): - files = get_image_files() return { "required": { "image": ("STRING", {"default": ""}), @@ -314,7 +368,7 @@ class RS_ImagePrompt: RETURN_NAMES = ("prompt",) FUNCTION = "process" CATEGORY = "🦊 RaykoStudio" - DESCRIPTION = "Extracts the positive prompt from image metadata (ComfyUI, RS Prompts, RS Image-Prompt, A1111 PNG/JPG/WebP)." + DESCRIPTION = "Extracts the positive prompt from image metadata (ComfyUI, RS Prompts, RS Image-Prompt, PromptStash, A1111 PNG/JPG/WebP)." def process(self, image, prompt_preview=""): try: