ComfyUI Extension: Comfy Text Stats

Authored by scofano

Created

Updated

1 stars

Run ComfyUI workflows without the setup

No installs, no CUDA version roulette, no GPU sitting idle on your bill. Bring a workflow and run it in the browser.

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 preprocessing, prompt analytics, and dataset workflows.

Looking for a different extension?

Custom Nodes (0)

    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!

    Run ComfyUI workflows without the setup

    No installs, no CUDA version roulette, no GPU sitting idle on your bill. Bring a workflow and run it in the browser.

    Learn more