Add Prompt Line Chooser node
Reads a .txt file holding one prompt per line and outputs a single line, picked by an index widget with control_after_generate so queued runs can increment through the file or pick lines at random. - Blank and '#' comment lines are skipped by default so files can hold notes - IS_CHANGED folds in the file mtime, so editing the list re-runs the node - Shows the chosen line as a readout at the bottom of the node - Ships prompt_line_chooser/prompts.txt as the default example list Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@ utility nodes for resolutions, loading/saving images with extra metadata, and lo
|
||||
- [📃 Brekel Prompt Enhancer (LLM)](#-brekel-prompt-enhancer-llm)
|
||||
- [📃 Brekel Auto Prompt Generator](#-brekel-auto-prompt-generator)
|
||||
- [📃 Brekel Prompt Chooser](#-brekel-prompt-chooser)
|
||||
- [📃 Brekel Prompt Line Chooser](#-brekel-prompt-line-chooser)
|
||||
- [📐 Brekel Resolution Selector](#-brekel-resolution-selector)
|
||||
- [🖼️ Brekel Load Image (with Filename & Caption)](#️-brekel-load-image-with-filename--caption)
|
||||
- [💾 Brekel Save Image (PNG/JPG)](#-brekel-save-image-pngjpg)
|
||||
@@ -167,6 +168,41 @@ It's perfect for workflows where you want to iterate through a predefined list p
|
||||
<br>
|
||||
|
||||
|
||||
### 📃 Brekel Prompt Line Chooser
|
||||
|
||||
Reads a single `.txt` file that holds **one prompt per line** and outputs one of those lines.
|
||||
Because the line number is a number widget it gets the `fixed / increment / decrement / randomize`
|
||||
control, so a batch of queued runs can walk through the file or pick lines at random.
|
||||
|
||||
#### How to Use
|
||||
|
||||
1. Set `file_path` to your text file (it defaults to the `ComfyUI/custom_nodes/ComfyUI-Brekel/prompt_line_chooser/prompts.txt` example that ships with the node).
|
||||
2. Put one prompt on each line. Blank lines and lines starting with `#` are skipped by default, so you can keep notes in the file.
|
||||
3. `line_index` picks the line and wraps around when it is larger than the number of lines.
|
||||
4. Set the control below `line_index` to `increment` to step through the file, or to `randomize` to pick a random line each run.
|
||||
5. Connect the `prompt` output to a CLIP Text Encode node (or any text input).
|
||||
|
||||
After running, the node shows a readout of the chosen line at its bottom (`Line 3/5: a robot tending a greenhouse...`), so you can see which prompt was used.
|
||||
|
||||
Editing the file is picked up automatically, the node re-runs when the file's modification time changes.
|
||||
|
||||
#### Inputs
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|:-------------------- |:------- |:---------------------------------------------------------------------------------------------------- |
|
||||
| `file_path` | STRING | Full path to the `.txt` file containing one prompt per line, empty falls back to the bundled example. |
|
||||
| `line_index` | INT | Which line to output, wraps around. Has the increment/randomize control. |
|
||||
| `skip_blank_lines` | BOOLEAN | Ignore empty lines so they do not take up an index. |
|
||||
| `skip_comment_lines` | BOOLEAN | Ignore lines starting with `#` so the file can hold comments. |
|
||||
|
||||
#### Outputs
|
||||
|
||||
| Output | Type | Description |
|
||||
|:-------- |:------ |:---------------------------------------------------- |
|
||||
| `prompt` | STRING | The text of the chosen line. |
|
||||
<br>
|
||||
|
||||
|
||||
### 📐 Brekel Resolution Selector
|
||||
|
||||
A simple dropdown with common resolution presets, so width, height and the number of frames are set in
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# Import the mappings from the new prompt chooser node
|
||||
from .brekel_prompt_chooser import NODE_CLASS_MAPPINGS as CHOOSER_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as CHOOSER_NAME_MAPPINGS
|
||||
|
||||
# Import the mappings from the line-based prompt chooser node
|
||||
from .brekel_prompt_line_chooser import NODE_CLASS_MAPPINGS as LINE_CHOOSER_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as LINE_CHOOSER_NAME_MAPPINGS
|
||||
|
||||
# Import the mappings from the first node file using aliases to avoid name conflicts
|
||||
from .brekel_auto_prompt_generator import NODE_CLASS_MAPPINGS as AUTO_PROMPT_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as AUTO_PROMPT_NAME_MAPPINGS
|
||||
|
||||
@@ -22,6 +25,7 @@ from .brekel_lora_loader import NODE_CLASS_MAPPINGS as LORA_LOADER_CLASS_MAPPING
|
||||
# Merge the class mappings from all files into one dictionary
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
**CHOOSER_CLASS_MAPPINGS,
|
||||
**LINE_CHOOSER_CLASS_MAPPINGS,
|
||||
**AUTO_PROMPT_CLASS_MAPPINGS,
|
||||
**ENHANCE_PROMPT_CLASS_MAPPINGS,
|
||||
**RESOLUTION_CLASS_MAPPINGS,
|
||||
@@ -33,6 +37,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
# Merge the display name mappings from all files into one dictionary
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
**CHOOSER_NAME_MAPPINGS,
|
||||
**LINE_CHOOSER_NAME_MAPPINGS,
|
||||
**AUTO_PROMPT_NAME_MAPPINGS,
|
||||
**ENHANCE_PROMPT_NAME_MAPPINGS,
|
||||
**RESOLUTION_NAME_MAPPINGS,
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
#
|
||||
# Brekel Prompt Line Chooser Node for ComfyUI
|
||||
# Version: 1.0.0
|
||||
#
|
||||
# Author: Brekel - https://brekel.com
|
||||
#
|
||||
# This node reads a text file that holds one prompt per line and outputs a single line.
|
||||
# The line is picked by index, and because the index is a number widget it gets the
|
||||
# "fixed / increment / decrement / randomize" control, so a batch of queued runs can
|
||||
# step through or randomly pick the prompts in the file.
|
||||
|
||||
|
||||
# --- CONFIGURATION CONSTANT ---
|
||||
# Define the subfolder name where the example prompt list is stored.
|
||||
SUBFOLDER_NAME = "prompt_line_chooser"
|
||||
|
||||
|
||||
import os
|
||||
import logging
|
||||
|
||||
# import block to communicate with the frontend
|
||||
try:
|
||||
from server import PromptServer
|
||||
except ImportError:
|
||||
# If the server is not available (e.g., in a headless environment) create a dummy class to prevent errors.
|
||||
class PromptServer:
|
||||
instance = None
|
||||
|
||||
|
||||
# --- Setup basic logging ---
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# --- Default File Path ---
|
||||
SCRIPT_DIR = os.path.dirname(__file__)
|
||||
DEFAULT_PROMPTS_FILE = os.path.join(SCRIPT_DIR, SUBFOLDER_NAME, "prompts.txt")
|
||||
|
||||
|
||||
def _read_lines(file_path: str, skip_blank_lines: bool, skip_comment_lines: bool):
|
||||
"""Read the file and return the list of usable prompt lines."""
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
lines = [line.strip() for line in f.read().splitlines()]
|
||||
|
||||
if skip_blank_lines:
|
||||
lines = [line for line in lines if line != ""]
|
||||
if skip_comment_lines:
|
||||
lines = [line for line in lines if not line.startswith("#")]
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
# --- PROMPT LINE CHOOSER CLASS ---
|
||||
class BrekelPromptLineChooser:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"file_path": ("STRING", {
|
||||
"default": DEFAULT_PROMPTS_FILE,
|
||||
"multiline": False,
|
||||
"placeholder": "C:/path/to/my/prompts.txt",
|
||||
"tooltip": "Path to a .txt file containing one prompt per line."
|
||||
}),
|
||||
# control_after_generate adds the (fixed / increment / decrement /
|
||||
# randomize) box to this INT widget, so queued runs can step through
|
||||
# or randomly pick lines in the file.
|
||||
"line_index": ("INT", {
|
||||
"default": 0,
|
||||
"min": 0,
|
||||
"max": 0xFFFFFFFFFFFFFFF,
|
||||
"step": 1,
|
||||
"control_after_generate": True,
|
||||
"tooltip": "Which line to output, wraps around when it is larger than the number of lines."
|
||||
}),
|
||||
"skip_blank_lines": ("BOOLEAN", {
|
||||
"default": True,
|
||||
"tooltip": "Ignore empty lines so they do not take up an index."
|
||||
}),
|
||||
"skip_comment_lines": ("BOOLEAN", {
|
||||
"default": True,
|
||||
"tooltip": "Ignore lines starting with '#' so the file can hold comments."
|
||||
}),
|
||||
},
|
||||
"hidden": {
|
||||
"unique_id": "UNIQUE_ID",
|
||||
},
|
||||
}
|
||||
|
||||
# --- Node configuration for ComfyUI ---
|
||||
CATEGORY = "Brekel"
|
||||
FUNCTION = "choose_line"
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("prompt",)
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(s, file_path, line_index, skip_blank_lines, skip_comment_lines, unique_id=None):
|
||||
# Include the file's modification time so editing the prompt list re-runs the node
|
||||
# even when none of the widget values changed.
|
||||
try:
|
||||
mtime = os.path.getmtime(file_path)
|
||||
except OSError:
|
||||
mtime = 0
|
||||
return (file_path, mtime, line_index, skip_blank_lines, skip_comment_lines)
|
||||
|
||||
def choose_line(self, file_path: str, line_index: int, skip_blank_lines: bool, skip_comment_lines: bool, unique_id=None):
|
||||
"""
|
||||
Main execution function. It reads the given text file and returns a single line,
|
||||
selected by index (wrapping around when the index exceeds the number of lines).
|
||||
"""
|
||||
file_path = file_path.strip()
|
||||
|
||||
if file_path == "":
|
||||
file_path = DEFAULT_PROMPTS_FILE
|
||||
if not os.path.isabs(file_path):
|
||||
file_path = os.path.abspath(file_path)
|
||||
|
||||
if not os.path.isfile(file_path):
|
||||
error_msg = f"Prompt file not found at '{file_path}'"
|
||||
logger.error(f"[Brekel Prompt Line Chooser] {error_msg}")
|
||||
return (f"ERROR: {error_msg}",)
|
||||
|
||||
try:
|
||||
lines = _read_lines(file_path, skip_blank_lines, skip_comment_lines)
|
||||
except Exception as e:
|
||||
error_msg = f"Failed to read file '{file_path}': {e}"
|
||||
logger.error(f"[Brekel Prompt Line Chooser] {error_msg}")
|
||||
return (f"ERROR: {error_msg}",)
|
||||
|
||||
if not lines:
|
||||
error_msg = f"No usable prompt lines found in '{file_path}'"
|
||||
logger.error(f"[Brekel Prompt Line Chooser] {error_msg}")
|
||||
return (f"ERROR: {error_msg}",)
|
||||
|
||||
# Calculate index using modulo so it loops if index > line count
|
||||
actual_index = line_index % len(lines)
|
||||
chosen_line = lines[actual_index]
|
||||
|
||||
print(f"[Brekel Prompt Line Chooser] index {line_index} -> line {actual_index + 1} of {len(lines)} in '{file_path}'")
|
||||
|
||||
# Send the chosen line to the UI so it stays readable at the bottom of the node
|
||||
if unique_id and PromptServer.instance:
|
||||
text_to_display = f"Line {actual_index + 1}/{len(lines)}: {chosen_line}"
|
||||
PromptServer.instance.send_progress_text(text_to_display, unique_id)
|
||||
|
||||
return (chosen_line,)
|
||||
|
||||
|
||||
# --- ComfyUI Node Registration ---
|
||||
NODE_CLASS_MAPPINGS = {"BrekelPromptLineChooser": BrekelPromptLineChooser,}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {"BrekelPromptLineChooser": "Brekel Prompt Line Chooser",}
|
||||
@@ -0,0 +1,6 @@
|
||||
# One prompt per line, lines starting with '#' and blank lines are skipped.
|
||||
a cat wearing a wizard hat, sitting on a stack of spellbooks, warm candlelight
|
||||
a dog riding a skateboard down a sunlit street, motion blur, low angle
|
||||
a robot tending a greenhouse full of glowing plants, soft volumetric light
|
||||
a lighthouse on a rocky cliff during a storm, dramatic clouds, long exposure
|
||||
a cozy reading nook by a rainy window, steaming cup of tea, shallow depth of field
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "brekel"
|
||||
description = "Nodes to enhance & streamline prompts. Enhance using local LLM within ComfyUI, generate using random lines from text files, or randomly select text file prompts. Resolution selector with common presets, Load Image node with filename output, Save Image node with PNG/JPG, gap-filling numbering and caption metadata."
|
||||
version = "1.3.0"
|
||||
description = "Nodes to enhance & streamline prompts. Enhance using local LLM within ComfyUI, generate using random lines from text files, or randomly select text file prompts, or step through one prompt per line in a text file. Resolution selector with common presets, Load Image node with filename output, Save Image node with PNG/JPG, gap-filling numbering and caption metadata."
|
||||
version = "1.4.0"
|
||||
license = {file = "LICENSE"}
|
||||
dependencies = ["bitsandbytes", "accelerate"]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user