Extensions/Comfy Text Stats
ComfyUI Extension

Comfy Text Stats

Node: Text Stats (Char & Word Count). A lightweight ComfyUI custom node that takes a text string and outputs character and word counts (INT). Ideal for caption…

By scofano·Created 10 months ago·Updated 3 months ago· 1
scofano/comfy-text-stats
Nodes8
On cloudLocal install
CategoryText/Utils
Stars1
Updated3 months ago
Readme

🧩 Comfy Text Stats

A lightweight ComfyUI custom node pack with text utility nodes for working directly inside your workflows:

  • Character Search Replace — applies search/replace rules from a multiline mapping list using a | separator, supporting multi-character search and replace strings
  • Line Batch Chunk — splits multiline text into fixed-size line batches using a batch index
  • Line Context Counter — uses a visible counter that auto-increments like a seed and returns the current, previous, and next line context
  • Remove Empty Lines — removes blank or whitespace-only lines from multiline text
  • Text Stats — returns character, word, and line counts
  • Text Load From File — loads a UTF-8 text file, skips comment lines, and returns both joined text and a line dictionary
  • Text String Truncate — truncates a string to a given number of characters or words, from the beginning or end
  • UTF-8 Cleaner — normalizes punctuation and removes invisible/control characters while preserving valid Unicode text

This pack is useful for text preprocessing, caption analytics, prompt inspection, and quick metadata generation directly in ComfyUI.


🚀 Features

✅ Simple and fast — no external dependencies
✅ Unicode-aware word detection (supports multilingual text)
✅ Line counting for multiline inputs
✅ Editable multiline character replacement rules
✅ Fixed-size multiline chunk extraction by batch number
✅ Sequential line traversal with previous/next context windows
✅ Visible line counter with seed-like auto-increment and manual reset control
✅ Empty-line cleanup for multiline strings
✅ UTF-8-safe text cleanup with smart punctuation normalization
✅ Deterministic utility nodes for stats and cleanup
✅ String truncation by character or word count, from either end ✅ UTF-8 text file loading with comment-line filtering ✅ Works with any STRING input from other nodes

<p align="center"> <img src="https://raw.githubusercontent.com/scofano/comfy-text-stats/main/text_stats.png" alt="Text stats" style="width: 100%; height: auto;"> </p>

📦 Installation

Option 1 — Via ComfyUI-Manager (recommended)

  1. Open ComfyUI-Manager
  2. Search for Comfy Text Stats
  3. Install the node pack
  4. Reload custom nodes or restart ComfyUI

Option 2 — Manual Install

  1. Clone or download this repository into your ComfyUI custom node directory:
    git clone https://github.com/scofano/comfy-text-stats.git
    
  2. The final structure should look like this:
    ComfyUI/
    └── custom_nodes/
        └── comfy-text-stats/
            ├── __init__.py
            ├── character_search_replace.py
            ├── line_batch_chunk.py
            ├── line_context_counter.py
            ├── remove_empty_lines.py
            ├── text_stats.py
            ├── text_load_from_file.py
            ├── text_string_truncate.py
            ├── utf8_processor.py
            ├── README.md
            └── requirements.txt
    
  3. Restart ComfyUI.

🧠 Usage

1) Character Search Replace

Category: Text/Utils

Inputs

| Name | Type | Description | |------|------|--------------| | text | STRING | The input text to process. Supports multiline input. | | replacements | STRING | Multiline replacement rules. Each non-empty line defines one rule as search|replacement. Everything before the first \| is the search string (may be multiple characters); everything after is the replacement (may be empty to delete). Lines without \| are silently skipped. |

Outputs

| Name | Type | Description | |------|------|--------------| | processed_text | STRING | The text after applying the replacement rules in order from top to bottom. |

Default replacement list:

“|”
“|”
„|”
«|”
»|”
‘|’
‘|’
‚|’
´|’
`|’
–|-
—|, 
−|-
‐|-
‑|-
•|-
·|-
*|
 |_
…|...

Notes:

  • Each non-empty line defines one rule using search|replacement format.
  • Everything before the first | is the search string — it can be one or more characters (e.g. ,10|, replaces ,10 with ,).
  • Everything after the | is the replacement — it can be empty (to delete the search string), one character, or multiple characters.
  • Lines without a | are silently ignored.
  • Leading and trailing spaces in a rule are meaningful.
  • The default list is only a starting suggestion; you can edit it directly in the node.

2) Line Batch Chunk

Category: Text/Utils

Inputs

| Name | Type | Description | |------|------|--------------| | text | STRING | Multiline text to split into chunks. | | number_of_lines | INT | How many lines each output chunk should contain. Minimum: 1. | | batch | INT | Which chunk to return, starting at 1. Batch 1 returns the first chunk, batch 2 the next chunk, and so on. This control auto-increments after each run like a seed. |

Outputs

| Name | Type | Description | |------|------|--------------| | chunk_text | STRING | The requested batch of lines. Returns a partial chunk if only some lines remain, or an empty string if the batch is fully out of range. |

Notes:

  • The node uses splitlines(), so each logical input line becomes one item in the batch calculation.
  • batch = 1 with number_of_lines = 2 returns lines 1-2.
  • batch = 2 with number_of_lines = 2 returns lines 3-4.
  • If the final chunk has fewer than number_of_lines available, only the remaining lines are returned.
  • If the requested batch starts beyond the available lines, the output is an empty string.

3) Line Context Counter

Category: Text/Utils

Inputs

| Name | Type | Description | |------|------|--------------| | text | STRING | Multiline text to iterate through. | | set_counter | INT | Visible line counter. Starts at 1, auto-increments after each run like a seed, and can be changed manually at any time. | | previous_lines | INT | Number of previous lines to include. Default: 1. | | next_lines | INT | Number of next lines to include. Default: 1. | | current_prefix | STRING | Optional prefix/template for the current line output. Default: Current line: {line} | | previous_prefix | STRING | Optional prefix/template for the previous lines output. Default: Previous {count} lines: {lines} | | next_prefix | STRING | Optional prefix/template for the next lines output. Default: Next {count} lines: {lines} | | all_lines_prefix | STRING | Optional prefix/template for the full text output. Default: All the content: |

Outputs

| Name | Type | Description | |------|------|--------------| | counter | INT | The visible current line counter, starting at 1. | | current_line_text | STRING | The current line selected by the automatic counter, starting at line 1. | | previous_lines_text | STRING | Up to the requested number of lines before the current line. Empty if none exist. | | next_lines_text | STRING | Up to the requested number of lines after the current line. Empty if none exist. | | combined_context_text | STRING | Previous, current, and next outputs concatenated together in that order, separated by newlines. Empty parts are skipped. | | all_lines_text | STRING | The original input text with the optional all-lines prefix applied. |

Notes:

  • set_counter behaves like a seed-style control and defaults to the increment post-generate mode in ComfyUI.
  • The counter output lets you see which line is currently selected.
  • You can edit set_counter manually at any time to jump to or reset the current line.
  • If set_counter is larger than the number of lines, it wraps around using the available line count.
  • combined_context_text joins previous_lines_text, current_line_text, and next_lines_text using newline separators.
  • If previous_lines = 0, previous_lines_text is always an empty string.
  • If next_lines = 0, next_lines_text is always an empty string.
  • Prefix fields are optional; if left blank, the node outputs only the line content.
  • Templates can use placeholders such as {line}, {lines}, {count}, {actual_count}, {line_number}, and {total_lines}.
  • If there are no previous lines or no next lines available, that output is an empty string.
  • If the input text is empty, the node returns counter = 0 and empty string outputs.

4) Remove Empty Lines

Category: Text/Utils

Inputs

| Name | Type | Description | |------|------|--------------| | text | STRING | Multiline text to clean. |

Outputs

| Name | Type | Description | |------|------|--------------| | processed_text | STRING | The input text with empty and whitespace-only lines removed. |

Notes:

  • Lines containing only spaces or tabs are treated as empty and removed.
  • Non-empty lines keep their original content and order.
  • The output is rejoined using newline characters.

5) Text Stats

Category: Text/Utils

Inputs

| Name | Type | Description | |------|------|--------------| | text | STRING | The text input to analyze. Supports multiline input. |

Outputs

| Name | Type | Description | |------|------|--------------| | char_count | INT | Number of characters (including spaces, punctuation, and newlines). | | word_count | INT | Number of words (using Unicode-aware regex). | | line_count | INT | Number of lines in the input text. |

6) UTF-8 Cleaner

Category: Text/Utils

Inputs

| Name | Type | Description | |------|------|--------------| | text | STRING | The raw text input to sanitize. Supports multiline input. |

Outputs

| Name | Type | Description | |------|------|--------------| | clean_text | STRING | Cleaned UTF-8-safe text with common smart punctuation normalized and invisible/control characters removed. |

7) Text Load From File

Category: Text/Utils

Inputs

| Name | Type | Description | |------|------|--------------| | file_path | STRING | Path to the UTF-8 text file to load. | | dictionary_name | STRING | Output dictionary key. Use [filename] to use the file name without extension, or enter a custom key. |

Outputs

| Name | Type | Description | |------|------|--------------| | text | STRING | Loaded file content after comment-line filtering, rejoined with newline characters. | | dictionary | DICT | Dictionary containing one key mapped to the filtered line list. |

Notes:

  • Lines whose trimmed content starts with # are skipped.
  • Remaining lines have \n and \r removed before being returned.
  • If dictionary_name = "[filename]", the dictionary key is the source file name without extension.
  • If the file path does not exist, the node returns an empty string and {key: []}.
  • The file is read as UTF-8.

8) Text String Truncate

Category: Text/Utils

Inputs

| Name | Type | Description | |------|------|--------------| | text | STRING | The input text to truncate. Supports multiline input. | | truncate_to | INT | Target length. Positive values keep that many units from the selected side. Negative values use Python-style slicing from the selected side. | | truncate_by | ["characters", "words"] | Unit of truncation: by character count or word count. | | truncate_from | ["end", "beginning"] | Which side to slice from. "beginning" slices from the start of the text. "end" slices from the end of the text. |

Outputs

| Name | Type | Description | |------|------|--------------| | text | STRING | The truncated string. |

Notes:

  • When truncate_by = "characters", the string is sliced by character index.
  • When truncate_by = "words", the string is split on whitespace and the requested number of words are kept.
  • truncate_from = "beginning" with a positive truncate_to keeps the first N characters/words; with a negative value it keeps the last N characters/words.
  • truncate_from = "end" with a positive truncate_to keeps the last N characters/words; with a negative value it removes the last N characters/words.
  • If the input text is empty or the requested length is zero, the result is an empty string.

🧪 Example

Character Search Replace input text:

“hello”— world…*

Default rules output:

"hello",_world...

Line Context Counter input:

alpha
beta
gamma
delta

Line Batch Chunk input:

1
2
3
4
5
6
7
8
9
10

Line Batch Chunk examples:

number_of_lines = 2, batch = 1 -> 1
2

number_of_lines = 2, batch = 2 -> 3
4

number_of_lines = 6, batch = 2 -> 7
8
9
10

number_of_lines = 6, batch = 3 -> ""

Run 1 outputs (defaults, set_counter = 1, previous_lines = 1, next_lines = 1):

counter = 1
current_line_text = Current line: alpha
previous_lines_text = 
next_lines_text = Next 1 lines: beta
combined_context_text = Current line: alpha
Next 1 lines: beta
all_lines_text = All the content: alpha
beta
gamma
delta

Run 2 outputs:

counter = 2
current_line_text = Current line: beta
previous_lines_text = Previous 1 lines: alpha
next_lines_text = Next 1 lines: gamma
combined_context_text = Previous 1 lines: alpha
Current line: beta
Next 1 lines: gamma

Manual reset example (set_counter = 4):

counter = 4
current_line_text = Current line: delta
previous_lines_text = Previous 1 lines: gamma
next_lines_text = 

Zero-window examples:

previous_lines = 0  -> previous_lines_text = ""
next_lines = 0      -> next_lines_text = ""

Out-of-range counter example (set_counter = 6 on 4 lines):

counter = 2
current_line_text = Current line: beta
previous_lines_text = Previous 1 lines: alpha
next_lines_text = Next 1 lines: gamma

Text Stats input:

ComfyUI makes custom nodes easy!

Text Stats outputs:

char_count = 32
word_count = 5
line_count = 1

UTF-8 Cleaner example:

Olá… mundo – teste\u200B

becomes:

Olá... mundo - teste

Remove Empty Lines input:

alpha

   
beta

gamma

Remove Empty Lines output:

alpha
beta
gamma

Text Load From File input file (example.txt):

# comment lines are skipped
alpha
beta
# another comment
gamma

Text Load From File outputs (dictionary_name = "[filename]"):

text = alpha
beta
gamma

dictionary = {"example": ["alpha", "beta", "gamma"]}

🔧 Technical Details

  • CharacterSearchReplace parses the replacements field line by line. Each rule uses search|replacement format: everything before the first | is the search string (any length), everything after is the replacement (may be empty). Rules are applied in order using Python's str.replace().
  • Word detection uses the regex:
    \b\w+\b
    
    which matches Unicode word boundaries.
  • LineBatchChunk calculates the starting line using (batch - 1) * number_of_lines and returns up to number_of_lines lines from that position.
  • LineContextCounter uses a seed-like set_counter widget with control_after_generate = "increment", so the counter updates automatically after each run in ComfyUI.
  • LineContextCounter selection is driven by the current input counter value and wraps around when the counter exceeds the available line count.
  • RemoveEmptyLines removes lines whose content is empty after trimming whitespace.
  • UTF-8 Cleaner normalizes text to Unicode NFC, replaces common smart punctuation (such as -> ... and / -> -), converts common arrows like -> ->, removes invisible/control characters, and preserves valid Unicode characters such as áéíóúç.
  • TextLoadFromFile reads UTF-8 files, removes lines whose trimmed content starts with #, strips newline/carriage-return characters from each retained line, and returns both joined text and a {dictionary_name: lines} dictionary.
  • TextStats counts lines using splitlines(), so an empty string returns 0 lines.
  • TextStats and UTF8Processor are deterministic, and their IS_CHANGED implementations currently use input length as a lightweight cache hint.

💡 Use Cases

  • Text preprocessing for captioning or prompt tuning
  • Normalizing punctuation or replacing/removing unwanted characters
  • Splitting long multiline prompts or subtitle blocks into chunked batches
  • Generating text metadata before feeding into LLM pipelines
  • Measuring caption sizes in dataset workflows
  • Cleaning copy/pasted text before saving or reusing it in prompts
  • Stepping through caption or subtitle files one line at a time with surrounding context
  • Loading prompt, caption, or dictionary text files while ignoring # comment lines

📄 License

MIT License © 2025 — freely usable and modifiable.
Contributions welcome!