| import os |
| import random |
| import re |
|
|
|
|
| class TXTBatchLoader: |
| RETURN_TYPES = ("STRING", "STRING") |
| RETURN_NAMES = ("content", "filename") |
| FUNCTION = "load_batch_texts" |
| CATEGORY = "Batch Process" |
|
|
| def __init__(self): |
| self.txt_files = [] |
| self.current_index = 0 |
| self.current_directory = "" |
| self.search_states = {} |
| |
| self._last_reset_on_queue = {} |
|
|
| @classmethod |
| def INPUT_TYPES(cls): |
| return { |
| "required": { |
| "directory": ("STRING",), |
| "search_title": ("STRING", {"default": ""}), |
| "search_content": ("STRING", {"default": ""}), |
| "delimiter": ("STRING", {"default": ""}), |
| "mode": ( |
| ["single_text", "incremental_text", "random"], |
| {"default": "incremental_text"}, |
| ), |
| "seed": ("INT", {"default": 0, "min": 0, "max": 0xFFFFFFFFFFFFFFFF}), |
| "filename_option": ( |
| [ |
| "filename", |
| "prefix", |
| "suffix", |
| "prefix & suffix", |
| "prefix nor suffix", |
| ], |
| ), |
| "start_index": ( |
| "INT", |
| { |
| "default": 0, |
| "min": 0, |
| "max": 999999, |
| "step": 1, |
| "tooltip": "Start index (1-based). Use 0 to start from beginning, or set 1 for first text file, 2 for second text file, etc.", |
| }, |
| ), |
| "end_index": ( |
| "INT", |
| { |
| "default": 0, |
| "min": 0, |
| "max": 999999, |
| "step": 1, |
| "tooltip": "End index (inclusive, 1-based). Use 0 to include all remaining text files, or set >0 to limit range (1=first, 2=second, etc.).", |
| }, |
| ), |
| }, |
| "optional": { |
| "reset_on_queue": ( |
| "INT", |
| { |
| "default": 1, |
| "min": 0, |
| "max": 1, |
| "step": 1, |
| "tooltip": "Any change to this value (0↔1) resets the read index to start from the first file again. Keep unchanged during a queue run to advance normally.", |
| }, |
| ), |
| }, |
| } |
|
|
| def set_directory( |
| self, |
| directory, |
| filename_option="filename", |
| search_title="", |
| search_content="", |
| delimiter="", |
| ): |
| if ( |
| directory != self.current_directory |
| or filename_option |
| or search_title |
| or search_content |
| or delimiter |
| ): |
| print(f"Received directory: {directory}") |
|
|
| if not os.path.isdir(directory): |
| raise ValueError( |
| f"The provided path '{directory}' is not a valid directory." |
| ) |
|
|
| all_files = [f for f in os.listdir(directory) if f.endswith(".txt")] |
|
|
| filtered_files = self.filter_files( |
| directory, |
| all_files, |
| filename_option, |
| search_title, |
| search_content, |
| delimiter, |
| ) |
|
|
| |
| def _natural_sort_key(path): |
| name = os.path.basename(path) |
| return [ |
| int(part) if part.isdigit() else part.lower() |
| for part in re.split(r"(\d+)", name) |
| ] |
|
|
| self.txt_files = sorted( |
| [os.path.join(directory, f) for f in filtered_files], |
| key=_natural_sort_key, |
| ) |
|
|
| self.current_directory = directory |
|
|
| search_key = ( |
| directory, |
| filename_option, |
| search_title, |
| search_content, |
| delimiter, |
| ) |
| if search_key not in self.search_states: |
| self.search_states[search_key] = 0 |
|
|
| if not self.txt_files: |
| print("No matching TXT files found in the provided directory.") |
| else: |
| |
| pass |
|
|
| def filter_files( |
| self, directory, files, filename_option, search_title, search_content, delimiter |
| ): |
| def get_prefix(filename): |
| if delimiter: |
| return filename.split(delimiter)[0] |
| else: |
| return re.split(r"[^a-zA-Z0-9]", filename)[0] |
|
|
| def get_suffix(filename): |
| name_without_ext = os.path.splitext(filename)[0] |
| if delimiter: |
| return name_without_ext.split(delimiter)[-1] |
| else: |
| return re.split(r"[^a-zA-Z0-9]", name_without_ext)[-1] |
|
|
| filtered_files = files |
|
|
| if search_title: |
| if filename_option == "filename": |
| filtered_files = [f for f in filtered_files if search_title in f] |
| elif filename_option == "prefix": |
| search_prefix = get_prefix(search_title) |
| filtered_files = [ |
| f for f in filtered_files if get_prefix(f) == search_prefix |
| ] |
| elif filename_option == "suffix": |
| search_suffix = get_suffix(search_title) |
| filtered_files = [ |
| f for f in filtered_files if get_suffix(f) == search_suffix |
| ] |
| elif filename_option == "prefix & suffix": |
| search_prefix = get_prefix(search_title) |
| search_suffix = get_suffix(search_title) |
| filtered_files = [ |
| f |
| for f in filtered_files |
| if get_prefix(f) == search_prefix or get_suffix(f) == search_suffix |
| ] |
| elif filename_option == "prefix nor suffix": |
| search_prefix = get_prefix(search_title) |
| search_suffix = get_suffix(search_title) |
| filtered_files = [ |
| f |
| for f in filtered_files |
| if get_prefix(f) != search_prefix and get_suffix(f) != search_suffix |
| ] |
|
|
| if search_content: |
| content_filtered_files = [] |
| for f in filtered_files: |
| file_path = os.path.join(directory, f) |
| with open(file_path, "r", encoding="utf-8") as file: |
| content = file.read() |
| pattern = ( |
| r"\b(?<![a-zA-Z])" |
| + re.escape(search_content) |
| + r"(?![a-zA-Z])\b" |
| ) |
| if re.search(pattern, content, re.IGNORECASE): |
| content_filtered_files.append(f) |
| filtered_files = content_filtered_files |
|
|
| return filtered_files |
|
|
| def get_next_text(self, search_key): |
| if not self.txt_files: |
| print("No txt files loaded.") |
| return None, None |
|
|
| |
| if search_key not in self.search_states: |
| self.search_states[search_key] = 0 |
|
|
| current_index = self.search_states[search_key] |
| if current_index >= len(self.txt_files): |
| current_index = 0 |
|
|
| file_path = self.txt_files[current_index] |
| self.search_states[search_key] = (current_index + 1) % len(self.txt_files) |
|
|
| with open(file_path, "r", encoding="utf-8") as file: |
| content = file.read() |
|
|
| print(f"Current index: {current_index}, Reading file: {file_path}") |
| return content, os.path.basename(file_path) |
|
|
| def load_batch_texts( |
| self, |
| directory, |
| search_title="", |
| search_content="", |
| delimiter="", |
| mode="incremental_text", |
| seed=0, |
| filename_option="filename", |
| start_index=0, |
| end_index=0, |
| reset_on_queue=1, |
| ): |
| self.set_directory( |
| directory, filename_option, search_title, search_content, delimiter |
| ) |
|
|
| if not self.txt_files: |
| return ("", "") |
|
|
| |
| filtered_files = self.txt_files |
| if filtered_files: |
| |
| actual_start = (start_index - 1) if start_index > 0 else 0 |
| actual_end = ( |
| (end_index - 1) if end_index > 0 else (len(filtered_files) - 1) |
| ) |
|
|
| |
| if actual_start < 0: |
| actual_start = 0 |
| if actual_start >= len(filtered_files): |
| actual_start = ( |
| len(filtered_files) - 1 if len(filtered_files) > 0 else 0 |
| ) |
|
|
| if actual_end < 0: |
| actual_end = 0 |
| if actual_end >= len(filtered_files): |
| actual_end = len(filtered_files) - 1 |
| if actual_end < actual_start: |
| actual_end = actual_start |
|
|
| |
| if len(filtered_files) > 0 and actual_start <= actual_end: |
| |
| filtered_files = filtered_files[actual_start : actual_end + 1] |
| else: |
| filtered_files = [] |
|
|
| if not filtered_files: |
| return ("", "") |
|
|
| search_key = ( |
| directory, |
| filename_option, |
| search_title, |
| search_content, |
| delimiter, |
| start_index, |
| end_index, |
| ) |
|
|
| |
| if self._last_reset_on_queue.get(search_key) != reset_on_queue: |
| self.search_states[search_key] = 0 |
| self._last_reset_on_queue[search_key] = reset_on_queue |
|
|
| |
| if search_key not in self.search_states: |
| self.search_states[search_key] = 0 |
|
|
| |
| |
| original_files = self.txt_files |
| self.txt_files = filtered_files |
|
|
| if mode == "single_text": |
| content, filename = self.get_next_text(search_key) |
| elif mode == "incremental_text": |
| content, filename = self.get_next_text(search_key) |
| elif mode == "random": |
| random.seed(seed) |
| rnd_index = random.randint(0, len(filtered_files) - 1) |
| file_path = filtered_files[rnd_index] |
| with open(file_path, "r", encoding="utf-8") as file: |
| content = file.read() |
| filename = os.path.basename(file_path) |
| else: |
| |
| self.txt_files = original_files |
| raise ValueError(f"Unknown mode: {mode}") |
|
|
| |
| self.txt_files = original_files |
|
|
| if content: |
| print(f"Final output content: {content}") |
| print(f"Final output filename: {filename}") |
| else: |
| print("Failed to read any content.") |
|
|
| return (content, os.path.splitext(filename)[0]) |
|
|