diff --git a/README.md b/README.md index eb78fe3..96d1815 100644 --- a/README.md +++ b/README.md @@ -1,56 +1,56 @@ -# ComfyUI_StringOps -**Efficient Text Processing Tool for ComfyUI Workflow** - -ComfyUI_StringOps is an advanced text processing tool tailored for the ComfyUI workflow, offering robust text manipulation capabilities to address core issues such as text preprocessing, data transformation, and batch automation, enabling efficient and precise operations. - - - **Batch Replacement:** -Supports partial or global batch replacement of strings for rapid text processing and delivery to subsequent processes, enhancing task efficiency. - - - **Intelligent Splitting/Concatenation:** -Splits text based on character counts and delimiters, and supports flexible recombination of multiple text segments to meet complex processing needs. - - - **Precise Extraction:** -Accurately extracts specified content from text using positional indexing and keyword matching, providing precise data support for subsequent processes. - - - **Integration with Excel:** -Achieves deep integration with Excel, supporting read and write operations on paged and tabular data, facilitating batch completion of complex tasks and significantly improving efficiency. - -## Update Notes (2025-04-21): -1.Added LoadAndAdjustImage node to load and resize images. - -2.Added SaveImagEX node to save images to a specified directory (also added an image output port). - -3.Added functionality to get the current timestamp (can be used as a random seed). - -4.Added the ability to copy and cut files. - -5.Added the feature of reading web page content. - -## Partial Node Display -**Text Processing Node** -![image](image/meyo01.png) -![image](image/meyo02.png) -![image](image/meyo03.png) -![image](image/meyo04.png) -![image](image/meyo05.png) - -**Table Data Read/Write Nodes** -![image](image/meyo06.png) - -ComfyUI_StringOps excels in handling prompts within workflows, especially in organizing and optimizing them, thereby significantly enhancing efficiency and accuracy. - -## Install -```cd ComfyUI/custom_nodes``` - -```git clone https://github.com/MeeeyoAI/ComfyUI_StringOps.git``` - -Or download the zip file and extracted, copy the resulting folder to ```ComfyUI\custom_nodes``` Restart ComfyUI. - -## More Info -✅ `bilibili:` https://space.bilibili.com/3546690300676691 - -✅ `Website:` http://www.meeeyo.com - -✅ `Sponsorship:` https://t.zsxq.com/ufSS2 - +# ComfyUI_StringOps +**Efficient Text Processing Tool for ComfyUI Workflow** + +ComfyUI_StringOps is an advanced text processing tool tailored for the ComfyUI workflow, offering robust text manipulation capabilities to address core issues such as text preprocessing, data transformation, and batch automation, enabling efficient and precise operations. + + - **Batch Replacement:** +Supports partial or global batch replacement of strings for rapid text processing and delivery to subsequent processes, enhancing task efficiency. + + - **Intelligent Splitting/Concatenation:** +Splits text based on character counts and delimiters, and supports flexible recombination of multiple text segments to meet complex processing needs. + + - **Precise Extraction:** +Accurately extracts specified content from text using positional indexing and keyword matching, providing precise data support for subsequent processes. + + - **Integration with Excel:** +Achieves deep integration with Excel, supporting read and write operations on paged and tabular data, facilitating batch completion of complex tasks and significantly improving efficiency. + +## Update Notes (2025-04-21): +1.Added LoadAndAdjustImage node to load and resize images. + +2.Added SaveImagEX node to save images to a specified directory (also added an image output port). + +3.Added functionality to get the current timestamp (can be used as a random seed). + +4.Added the ability to copy and cut files. + +5.Added the feature of reading web page content. + +## Partial Node Display +**Text Processing Node** +![image](image/meyo01.png) +![image](image/meyo02.png) +![image](image/meyo03.png) +![image](image/meyo04.png) +![image](image/meyo05.png) + +**Table Data Read/Write Nodes** +![image](image/meyo06.png) + +ComfyUI_StringOps excels in handling prompts within workflows, especially in organizing and optimizing them, thereby significantly enhancing efficiency and accuracy. + +## Install +```cd ComfyUI/custom_nodes``` + +```git clone https://github.com/MeeeyoAI/ComfyUI_StringOps.git``` + +Or download the zip file and extracted, copy the resulting folder to ```ComfyUI\custom_nodes``` Restart ComfyUI. + +## More Info +✅ `bilibili:` https://space.bilibili.com/3546690300676691 + +✅ `Website:` http://www.meeeyo.com + +✅ `Sponsorship:` https://t.zsxq.com/ufSS2 + ✅ `WeChat`: meeeyo \ No newline at end of file diff --git a/__init__.py b/__init__.py index 40b2ba0..657b6a1 100644 --- a/__init__.py +++ b/__init__.py @@ -1,125 +1,125 @@ -from .meyo_node_Computational import * -from .meyo_node_String import * -from .meyo_node_File import * -from .meyo_node_Functional import * - - -NODE_CLASS_MAPPINGS = { - - #运算型节点:meyo_node_Computational - "CompareInt": CompareInt, - "FloatToInteger": FloatToInteger, - "GenerateNumbers": GenerateNumbers, - "GetRandomIntegerInRange": GetRandomIntegerInRange, - - - #字符串处理:meyo_node_String - "SingleTextInput": SingleTextInput, - "MultiParamInputNode": MultiParamInputNode, - "AddPrefixSuffix": AddPrefixSuffix, - "ExtractSubstring": ExtractSubstring, - "ExtractSubstringByIndices": ExtractSubstringByIndices, - "SplitStringByDelimiter": SplitStringByDelimiter, - "ProcessString": ProcessString, - "ExtractBeforeAfter": ExtractBeforeAfter, - "ReplaceNthOccurrence": ReplaceNthOccurrence, - "ReplaceMultiple": ReplaceMultiple, - "BatchReplaceStrings": BatchReplaceStrings, - "RandomLineFromText": RandomLineFromText, - "CheckSubstringPresence": CheckSubstringPresence, - "AddPrefixSuffixToLines": AddPrefixSuffixToLines, - "ExtractAndCombineLines": ExtractAndCombineLines, - "FilterLinesBySubstrings": FilterLinesBySubstrings, - "FilterLinesByWordCount": FilterLinesByWordCount, - "SplitAndExtractText": SplitAndExtractText, - "CountOccurrences": CountOccurrences, - "ExtractLinesByIndex": ExtractLinesByIndex, - "ExtractSpecificLines": ExtractSpecificLines, - "RemoveContentBetweenChars": RemoveContentBetweenChars, - "ShuffleTextLines": ShuffleTextLines, - "ConditionalTextOutput": ConditionalTextOutput, - "TextConditionCheck": TextConditionCheck, - "TextConcatenation": TextConcatenation, - "ExtractSpecificData": ExtractSpecificData, - "FindFirstLineContent": FindFirstLineContent, - - #文件处理:meyo_node_File - "LoadAndAdjustImage": LoadAndAdjustImage, - "ImageAdjuster": ImageAdjuster, - "CustomCrop": CustomCrop, - - "SaveImagEX": SaveImagEX, - "FileCopyCutNode": FileCopyCutNode, - "FileDeleteNode": FileDeleteNode, - "FileListAndSuffix": FileListAndSuffix, - "ReadExcelData": ReadExcelData, - "WriteExcelData": WriteExcelData, - "FindExcelData": FindExcelData, - "ReadExcelRowOrColumnDiff": ReadExcelRowOrColumnDiff, - - #功能型节点:meyo_node_Functional - "GetCurrentTime": GetCurrentTime, - "SelectionParameter": SelectionParameter, - "ReadWebNode": ReadWebNode, -} - - -NODE_DISPLAY_NAME_MAPPINGS = { - - #运算型节点:meyo_node_Computational - "CompareInt": "比较数值(meeeyo.com)", - "FloatToInteger": "规范数值(meeeyo.com)", - "GenerateNumbers": "生成范围数组(meeeyo.com)", - "GetRandomIntegerInRange": "范围内随机数(meeeyo.com)", - - #字符串处理:meyo_node_String - "SingleTextInput": "文本输入(meeeyo.com)", - "MultiParamInputNode": "多参数输入(meeeyo.com)", - "AddPrefixSuffix": "添加前后缀(meeeyo.com)", - "ExtractSubstring": "提取标签之间(meeeyo.com)", - "ExtractSubstringByIndices": "按数字范围提取(meeeyo.com)", - "SplitStringByDelimiter": "分隔符拆分两边(meeeyo.com)", - "ProcessString": "常规处理字符(meeeyo.com)", - "ExtractBeforeAfter": "提取前后字符(meeeyo.com)", - "ReplaceNthOccurrence": "替换第n次出现(meeeyo.com)", - "ReplaceMultiple": "多次出现依次替换(meeeyo.com)", - "BatchReplaceStrings": "批量替换字符(meeeyo.com)", - "RandomLineFromText": "随机行内容(meeeyo.com)", - "CheckSubstringPresence": "判断是否包含字符(meeeyo.com)", - "AddPrefixSuffixToLines": "段落每行添加前后缀(meeeyo.com)", - "ExtractAndCombineLines": "段落提取指定索引行(meeeyo.com)", - "FilterLinesBySubstrings": "段落提取或移除字符行(meeeyo.com)", - "FilterLinesByWordCount": "段落字数条件过滤行(meeeyo.com)", - "SplitAndExtractText": "按序号提取分割文本(meeeyo.com)", - "CountOccurrences": "文本出现次数(meeeyo.com)", - "ExtractLinesByIndex": "文本拆分(meeeyo.com)", - "ExtractSpecificLines": "提取特定行(meeeyo.com)", - "RemoveContentBetweenChars": "删除标签内的内容(meeeyo.com)", - "ShuffleTextLines": "随机打乱(meeeyo.com)", - "ConditionalTextOutput": "判断返回内容(meeeyo.com)", - "TextConditionCheck": "文本按条件判断(meeeyo.com)", - "TextConcatenation": "文本组合(meeeyo.com)", - "ExtractSpecificData": "提取多层指定数据(meeeyo.com)", - "FindFirstLineContent": "指定字符行参数(meeeyo.com)", - - #文件处理:meyo_node_File - "LoadAndAdjustImage": "加载重置图像(meeeyo.com)", - "ImageAdjuster": "重置图像(meeeyo.com)", - "CustomCrop": "裁剪图像(meeeyo.com)", - - "SaveImagEX": "保存图像(meeeyo.com)", - "FileCopyCutNode": "文件操作(meeeyo.com)", - "FileDeleteNode": "清理文件(meeeyo.com)", - "FileListAndSuffix": "从路径加载(meeeyo.com)", - "ReadExcelData": "读取表格数据(meeeyo.com)", - "WriteExcelData": "写入表格数据(meeeyo.com)", - "FindExcelData": "查找表格数据(meeeyo.com)", - "ReadExcelRowOrColumnDiff": "读取表格数量差(meeeyo.com)", - - #功能型节点:meyo_node_Functional - "GetCurrentTime": "当前时间(戳)", - "SelectionParameter": "选择参数(meeeyo.com)", - "ReadWebNode": "读取页面(meeeyo.com)", -} - - +from .meyo_node_Computational import * +from .meyo_node_String import * +from .meyo_node_File import * +from .meyo_node_Functional import * + + +NODE_CLASS_MAPPINGS = { + + #运算型节点:meyo_node_Computational + "CompareInt": CompareInt, + "FloatToInteger": FloatToInteger, + "GenerateNumbers": GenerateNumbers, + "GetRandomIntegerInRange": GetRandomIntegerInRange, + + + #字符串处理:meyo_node_String + "SingleTextInput": SingleTextInput, + "MultiParamInputNode": MultiParamInputNode, + "AddPrefixSuffix": AddPrefixSuffix, + "ExtractSubstring": ExtractSubstring, + "ExtractSubstringByIndices": ExtractSubstringByIndices, + "SplitStringByDelimiter": SplitStringByDelimiter, + "ProcessString": ProcessString, + "ExtractBeforeAfter": ExtractBeforeAfter, + "ReplaceNthOccurrence": ReplaceNthOccurrence, + "ReplaceMultiple": ReplaceMultiple, + "BatchReplaceStrings": BatchReplaceStrings, + "RandomLineFromText": RandomLineFromText, + "CheckSubstringPresence": CheckSubstringPresence, + "AddPrefixSuffixToLines": AddPrefixSuffixToLines, + "ExtractAndCombineLines": ExtractAndCombineLines, + "FilterLinesBySubstrings": FilterLinesBySubstrings, + "FilterLinesByWordCount": FilterLinesByWordCount, + "SplitAndExtractText": SplitAndExtractText, + "CountOccurrences": CountOccurrences, + "ExtractLinesByIndex": ExtractLinesByIndex, + "ExtractSpecificLines": ExtractSpecificLines, + "RemoveContentBetweenChars": RemoveContentBetweenChars, + "ShuffleTextLines": ShuffleTextLines, + "ConditionalTextOutput": ConditionalTextOutput, + "TextConditionCheck": TextConditionCheck, + "TextConcatenation": TextConcatenation, + "ExtractSpecificData": ExtractSpecificData, + "FindFirstLineContent": FindFirstLineContent, + + #文件处理:meyo_node_File + "LoadAndAdjustImage": LoadAndAdjustImage, + "ImageAdjuster": ImageAdjuster, + "CustomCrop": CustomCrop, + + "SaveImagEX": SaveImagEX, + "FileCopyCutNode": FileCopyCutNode, + "FileDeleteNode": FileDeleteNode, + "FileListAndSuffix": FileListAndSuffix, + "ReadExcelData": ReadExcelData, + "WriteExcelData": WriteExcelData, + "FindExcelData": FindExcelData, + "ReadExcelRowOrColumnDiff": ReadExcelRowOrColumnDiff, + + #功能型节点:meyo_node_Functional + "GetCurrentTime": GetCurrentTime, + "SelectionParameter": SelectionParameter, + "ReadWebNode": ReadWebNode, +} + + +NODE_DISPLAY_NAME_MAPPINGS = { + + #运算型节点:meyo_node_Computational + "CompareInt": "比较数值(meeeyo.com)", + "FloatToInteger": "规范数值(meeeyo.com)", + "GenerateNumbers": "生成范围数组(meeeyo.com)", + "GetRandomIntegerInRange": "范围内随机数(meeeyo.com)", + + #字符串处理:meyo_node_String + "SingleTextInput": "文本输入(meeeyo.com)", + "MultiParamInputNode": "多参数输入(meeeyo.com)", + "AddPrefixSuffix": "添加前后缀(meeeyo.com)", + "ExtractSubstring": "提取标签之间(meeeyo.com)", + "ExtractSubstringByIndices": "按数字范围提取(meeeyo.com)", + "SplitStringByDelimiter": "分隔符拆分两边(meeeyo.com)", + "ProcessString": "常规处理字符(meeeyo.com)", + "ExtractBeforeAfter": "提取前后字符(meeeyo.com)", + "ReplaceNthOccurrence": "替换第n次出现(meeeyo.com)", + "ReplaceMultiple": "多次出现依次替换(meeeyo.com)", + "BatchReplaceStrings": "批量替换字符(meeeyo.com)", + "RandomLineFromText": "随机行内容(meeeyo.com)", + "CheckSubstringPresence": "判断是否包含字符(meeeyo.com)", + "AddPrefixSuffixToLines": "段落每行添加前后缀(meeeyo.com)", + "ExtractAndCombineLines": "段落提取指定索引行(meeeyo.com)", + "FilterLinesBySubstrings": "段落提取或移除字符行(meeeyo.com)", + "FilterLinesByWordCount": "段落字数条件过滤行(meeeyo.com)", + "SplitAndExtractText": "按序号提取分割文本(meeeyo.com)", + "CountOccurrences": "文本出现次数(meeeyo.com)", + "ExtractLinesByIndex": "文本拆分(meeeyo.com)", + "ExtractSpecificLines": "提取特定行(meeeyo.com)", + "RemoveContentBetweenChars": "删除标签内的内容(meeeyo.com)", + "ShuffleTextLines": "随机打乱(meeeyo.com)", + "ConditionalTextOutput": "判断返回内容(meeeyo.com)", + "TextConditionCheck": "文本按条件判断(meeeyo.com)", + "TextConcatenation": "文本组合(meeeyo.com)", + "ExtractSpecificData": "提取多层指定数据(meeeyo.com)", + "FindFirstLineContent": "指定字符行参数(meeeyo.com)", + + #文件处理:meyo_node_File + "LoadAndAdjustImage": "加载重置图像(meeeyo.com)", + "ImageAdjuster": "重置图像(meeeyo.com)", + "CustomCrop": "裁剪图像(meeeyo.com)", + + "SaveImagEX": "保存图像(meeeyo.com)", + "FileCopyCutNode": "文件操作(meeeyo.com)", + "FileDeleteNode": "清理文件(meeeyo.com)", + "FileListAndSuffix": "从路径加载(meeeyo.com)", + "ReadExcelData": "读取表格数据(meeeyo.com)", + "WriteExcelData": "写入表格数据(meeeyo.com)", + "FindExcelData": "查找表格数据(meeeyo.com)", + "ReadExcelRowOrColumnDiff": "读取表格数量差(meeeyo.com)", + + #功能型节点:meyo_node_Functional + "GetCurrentTime": "当前时间(戳)", + "SelectionParameter": "选择参数(meeeyo.com)", + "ReadWebNode": "读取页面(meeeyo.com)", +} + + diff --git a/meyo_node_Computational.py b/meyo_node_Computational.py index 8411bed..6f9de3d 100644 --- a/meyo_node_Computational.py +++ b/meyo_node_Computational.py @@ -1,155 +1,155 @@ -import math -import random -import torch -import numpy as np - -#======比较数值 -class CompareInt: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "input_float": ("FLOAT", {"default": 4.0}), - "range": ("STRING", {"default": "3.5-5.5"}), - }, - "optional": {}, - } - - RETURN_TYPES = ("STRING",) - FUNCTION = "compare_float_to_range" - CATEGORY = "Meeeyo/Number" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def compare_float_to_range(self, input_float, range): - try: - if '-' in range: - lower_bound, upper_bound = map(float, range.split('-')) - else: - lower_bound = upper_bound = float(range) - if input_float < lower_bound: - return ("小",) - elif input_float > upper_bound: - return ("大",) - else: - return ("中",) - except ValueError: - return ("Error: Invalid input format.",) - -#======规范数值 -class FloatToInteger: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "float_value": ("FLOAT", {"default": 3.14}), - "operation": (["四舍五入", "取大值", "取小值", "最近32倍"], {"default": "四舍五入"}), - }, - "optional": {}, - } - - RETURN_TYPES = ("INT",) - FUNCTION = "convert_float_to_integer" - CATEGORY = "Meeeyo/Number" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def IS_CHANGED(): - return float("NaN") - - def convert_float_to_integer(self, float_value, operation): - if operation == "四舍五入": - result = round(float_value) - elif operation == "取大值": - result = math.ceil(float_value) - elif operation == "取小值": - result = math.floor(float_value) - elif operation == "最近32倍": - result = round(float_value / 32) * 32 - return (result,) - - -#======生成范围数组 -class GenerateNumbers: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "range_rule": ("STRING", {"default": "3|1-10"}), - "mode": (["顺序", "随机"], {"default": "顺序"}), - "prefix_suffix": ("STRING", {"default": "|"}), - }, - "optional": {}, - } - - RETURN_TYPES = ("STRING",) - FUNCTION = "generate_numbers" - CATEGORY = "Meeeyo/Number" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def IS_CHANGED(): - return float("NaN") - - def generate_numbers(self, range_rule, mode, prefix_suffix): - try: - start_str, range_str = range_rule.split('|') - start = int(start_str) - end_range = list(map(int, range_str.split('-'))) - if len(end_range) == 1: - end = end_range[0] - numbers = [str(i).zfill(start) for i in range(1, end + 1)] - else: - start_range, end = end_range - numbers = [str(i).zfill(start) for i in range(start_range, end + 1)] - if prefix_suffix.strip(): - prefix, suffix = prefix_suffix.split('|') - else: - prefix, suffix = "", "" - if mode == "随机": - random.shuffle(numbers) - numbers = [f"{prefix}{num}{suffix}" for num in numbers] - result = '\n'.join(numbers) - return (result,) - except ValueError: - return ("",) - - -#======范围内随机数 -class GetRandomIntegerInRange: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "range_str": ("STRING", {"default": "0-10"}), - }, - "optional": {}, - } - - RETURN_TYPES = ("INT", "STRING") - FUNCTION = "get_random_integer_in_range" - CATEGORY = "Meeeyo/Number" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def IS_CHANGED(): - return float("NaN") - - def get_random_integer_in_range(self, range_str): - try: - start, end = map(int, range_str.split('-')) - if start > end: - start, end = end, start - random_int = random.randint(start, end) - return (random_int, str(random_int)) - except ValueError: - return (0, "0") - +import math +import random +import torch +import numpy as np + +#======比较数值 +class CompareInt: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_float": ("FLOAT", {"default": 4.0}), + "range": ("STRING", {"default": "3.5-5.5"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "compare_float_to_range" + CATEGORY = "Meeeyo/Number" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def compare_float_to_range(self, input_float, range): + try: + if '-' in range: + lower_bound, upper_bound = map(float, range.split('-')) + else: + lower_bound = upper_bound = float(range) + if input_float < lower_bound: + return ("小",) + elif input_float > upper_bound: + return ("大",) + else: + return ("中",) + except ValueError: + return ("Error: Invalid input format.",) + +#======规范数值 +class FloatToInteger: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "float_value": ("FLOAT", {"default": 3.14}), + "operation": (["四舍五入", "取大值", "取小值", "最近32倍"], {"default": "四舍五入"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("INT",) + FUNCTION = "convert_float_to_integer" + CATEGORY = "Meeeyo/Number" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def IS_CHANGED(): + return float("NaN") + + def convert_float_to_integer(self, float_value, operation): + if operation == "四舍五入": + result = round(float_value) + elif operation == "取大值": + result = math.ceil(float_value) + elif operation == "取小值": + result = math.floor(float_value) + elif operation == "最近32倍": + result = round(float_value / 32) * 32 + return (result,) + + +#======生成范围数组 +class GenerateNumbers: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "range_rule": ("STRING", {"default": "3|1-10"}), + "mode": (["顺序", "随机"], {"default": "顺序"}), + "prefix_suffix": ("STRING", {"default": "|"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "generate_numbers" + CATEGORY = "Meeeyo/Number" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def IS_CHANGED(): + return float("NaN") + + def generate_numbers(self, range_rule, mode, prefix_suffix): + try: + start_str, range_str = range_rule.split('|') + start = int(start_str) + end_range = list(map(int, range_str.split('-'))) + if len(end_range) == 1: + end = end_range[0] + numbers = [str(i).zfill(start) for i in range(1, end + 1)] + else: + start_range, end = end_range + numbers = [str(i).zfill(start) for i in range(start_range, end + 1)] + if prefix_suffix.strip(): + prefix, suffix = prefix_suffix.split('|') + else: + prefix, suffix = "", "" + if mode == "随机": + random.shuffle(numbers) + numbers = [f"{prefix}{num}{suffix}" for num in numbers] + result = '\n'.join(numbers) + return (result,) + except ValueError: + return ("",) + + +#======范围内随机数 +class GetRandomIntegerInRange: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "range_str": ("STRING", {"default": "0-10"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("INT", "STRING") + FUNCTION = "get_random_integer_in_range" + CATEGORY = "Meeeyo/Number" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def IS_CHANGED(): + return float("NaN") + + def get_random_integer_in_range(self, range_str): + try: + start, end = map(int, range_str.split('-')) + if start > end: + start, end = end, start + random_int = random.randint(start, end) + return (random_int, str(random_int)) + except ValueError: + return (0, "0") + diff --git a/meyo_node_File.py b/meyo_node_File.py index ba55995..ecc750c 100644 --- a/meyo_node_File.py +++ b/meyo_node_File.py @@ -1,828 +1,828 @@ -import os -import csv -import torch -import shutil -import requests -import chardet -import pathlib -import openpyxl -import folder_paths -import node_helpers -import numpy as np -from PIL import Image, ImageOps, ImageSequence -from pathlib import Path - -#======加载重置图像 -class LoadAndAdjustImage: - @classmethod - def INPUT_TYPES(s): - input_dir = folder_paths.get_input_directory() - files = [f.name for f in Path(input_dir).iterdir() if f.is_file()] - return { - "required": { - "image": (sorted(files), {"image_upload": True}), - "max_dimension": ("INT", {"default": 0, "min": 0, "max": 4096, "step": 8}), - "size_option": (["No Change", "Custom", "Million Pixels", "Small", "Medium", "Large", - "480P-H(vid 4:3)", "480P-V(vid 3:4)", "720P-H(vid 16:9)", "720P-V(vid 9:16)", "832×480", "480×832"], - {"default": "No Change"}) - } - } - - RETURN_TYPES = ("IMAGE", "MASK", "STRING") - RETURN_NAMES = ("image", "mask", "info") - FUNCTION = "load_image" - CATEGORY = "Meeeyo/File" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def IS_CHANGED(): - return float("NaN") - - def load_image(self, image, max_dimension, size_option): - image_path = folder_paths.get_annotated_filepath(image) - img = Image.open(image_path) - W, H = img.size - aspect_ratio = W / H - - def get_target_size(): - if size_option == "No Change": - # No resizing or cropping, just return the original size - return W, H - elif size_option == "Million Pixels": - return self._resize_to_million_pixels(W, H) - elif size_option == "Custom": - ratio = min(max_dimension / W, max_dimension / H) - return round(W * ratio), round(H * ratio) - - size_options = { - "Small": ( - (768, 512) if aspect_ratio >= 1.23 else - (512, 768) if aspect_ratio <= 0.82 else - (768, 768) - ), - "Medium": ( - (1216, 832) if aspect_ratio >= 1.23 else - (832, 1216) if aspect_ratio <= 0.82 else - (1216, 1216) - ), - "Large": ( - (1600, 1120) if aspect_ratio >= 1.23 else - (1120, 1600) if aspect_ratio <= 0.82 else - (1600, 1600) - ), - "Million Pixels": self._resize_to_million_pixels(W, H), # Million Pixels option - "480P-H(vid 4:3)": (640, 480), # 480P-H, 640x480 - "480P-V(vid 3:4)": (480, 640), # 480P-V, 480x640 - "720P-H(vid 16:9)": (1280, 720), # 720P-H, 1280x720 - "720P-V(vid 9:16)": (720, 1280), # 720P-V, 720x1280 - "832×480": (832, 480), # 832x480 - "480×832": (480, 832), # 480x832 - } - return size_options[size_option] - - target_width, target_height = get_target_size() - output_images = [] - output_masks = [] - - for frame in ImageSequence.Iterator(img): - frame = ImageOps.exif_transpose(frame) - if frame.mode == 'P': - frame = frame.convert("RGBA") - elif 'A' in frame.getbands(): - frame = frame.convert("RGBA") - - if size_option == "No Change": - # No resizing, just use the original frame - image_frame = frame.convert("RGB") - else: - if size_option == "Custom" or size_option == "Million Pixels": - ratio = min(target_width / W, target_height / H) - adjusted_width = round(W * ratio) - adjusted_height = round(H * ratio) - image_frame = frame.convert("RGB").resize((adjusted_width, adjusted_height), Image.Resampling.BILINEAR) - else: - image_frame = frame.convert("RGB") - image_frame = ImageOps.fit(image_frame, (target_width, target_height), method=Image.Resampling.BILINEAR, centering=(0.5, 0.5)) - - image_array = np.array(image_frame).astype(np.float32) / 255.0 - output_images.append(torch.from_numpy(image_array)[None,]) - - # Process the mask if available - if 'A' in frame.getbands(): - mask_frame = frame.getchannel('A') - if size_option == "Custom" or size_option == "Million Pixels": - mask_frame = mask_frame.resize((adjusted_width, adjusted_height), Image.Resampling.BILINEAR) - else: - mask_frame = ImageOps.fit(mask_frame, (target_width, target_height), method=Image.Resampling.BILINEAR, centering=(0.5, 0.5)) - mask = np.array(mask_frame).astype(np.float32) / 255.0 - mask = 1. - mask - else: - if size_option == "Custom" or size_option == "Million Pixels": - mask = np.zeros((adjusted_height, adjusted_width), dtype=np.float32) - else: - mask = np.zeros((target_height, target_width), dtype=np.float32) - output_masks.append(torch.from_numpy(mask).unsqueeze(0)) - - output_image = torch.cat(output_images, dim=0) if len(output_images) > 1 else output_images[0] - output_mask = torch.cat(output_masks, dim=0) if len(output_masks) > 1 else output_masks[0] - info = f"Image Path: {image_path}\nOriginal Size: {W}x{H}\nAdjusted Size: {target_width}x{target_height}" - return (output_image, output_mask, info) - - @classmethod - def VALIDATE_INPUTS(s, image): - if not folder_paths.exists_annotated_filepath(image): - return f"Invalid image file: {image}" - return True - - def _resize_to_million_pixels(self, W, H): - # Calculate the aspect ratio of the original image - aspect_ratio = W / H - target_area = 1000000 # 1 million pixels - - # Calculate the new width and height while maintaining the aspect ratio - if aspect_ratio > 1: # Landscape - width = int(np.sqrt(target_area * aspect_ratio)) - height = int(target_area / width) - else: # Portrait - height = int(np.sqrt(target_area / aspect_ratio)) - width = int(target_area / height) - - # Round width and height to the nearest multiple of 8 - width = (width + 7) // 8 * 8 - height = (height + 7) // 8 * 8 - - return width, height - - - -#======重置图像 -class ImageAdjuster: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "image": ("IMAGE",), - "max_dimension": ("INT", {"default": 0, "min": 0, "max": 4096, "step": 8}), - "size_option": ([ - "Custom", "Million Pixels", "Small", "Medium", "Large", - "480P-H(vid 4:3)", "480P-V(vid 3:4)", "720P-H(vid 16:9)", "720P-V(vid 9:16)", "832×480", "480×832"], - {"default": "Million Pixels"}) - } - } - - RETURN_TYPES = ("IMAGE", "INT", "INT") - RETURN_NAMES = ("image", "width", "height") - FUNCTION = "process_image" - CATEGORY = "Meeeyo/File" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def IS_CHANGED(): - return float("NaN") - - def process_image(self, image, max_dimension=1024, size_option="Custom"): - input_image = Image.fromarray((image.squeeze(0).numpy() * 255).astype(np.uint8)) - W, H = input_image.size - - processed_images = [] - - for frame in [input_image]: - frame = ImageOps.exif_transpose(frame) - - if frame.mode == 'P': - frame = frame.convert("RGBA") - elif 'A' in frame.getbands(): - frame = frame.convert("RGBA") - - if size_option != "Custom": - aspect_ratio = W / H - - size_options = { - "Small": ( - (768, 512) if aspect_ratio >= 1.23 else - (512, 768) if aspect_ratio <= 0.82 else - (768, 768) - ), - "Medium": ( - (1216, 832) if aspect_ratio >= 1.23 else - (832, 1216) if aspect_ratio <= 0.82 else - (1216, 1216) - ), - "Large": ( - (1600, 1120) if aspect_ratio >= 1.23 else - (1120, 1600) if aspect_ratio <= 0.82 else - (1600, 1600) - ), - "Million Pixels": self._resize_to_million_pixels(W, H), # Million Pixels option - "480P-H(vid 4:3)": (640, 480), # 480P-H, 640x480 - "480P-V(vid 3:4)": (480, 640), # 480P-V, 480x640 - "720P-H(vid 16:9)": (1280, 720), # 720P-H, 1280x720 - "720P-V(vid 9:16)": (720, 1280), # 720P-V, 720x1280 - "832×480": (832, 480), # 832x480 - "480×832": (480, 832), # 480x832 - } - - target_width, target_height = size_options[size_option] - processed_image = frame.convert("RGB") - processed_image = ImageOps.fit(processed_image, (target_width, target_height), method=Image.Resampling.BILINEAR, centering=(0.5, 0.5)) - else: - ratio = min(max_dimension / W, max_dimension / H) - adjusted_width = round(W * ratio) - adjusted_height = round(H * ratio) - - processed_image = frame.convert("RGB") - processed_image = processed_image.resize((adjusted_width, adjusted_height), Image.Resampling.BILINEAR) - - processed_image = np.array(processed_image).astype(np.float32) / 255.0 - processed_image = torch.from_numpy(processed_image)[None,] - processed_images.append(processed_image) - - output_image = torch.cat(processed_images, dim=0) if len(processed_images) > 1 else processed_images[0] - if size_option != "Custom": - return (output_image, target_width, target_height) - else: - return (output_image, adjusted_width, adjusted_height) - - def _resize_to_million_pixels(self, W, H): - # Calculate the aspect ratio of the original image - aspect_ratio = W / H - target_area = 1000000 # 1 million pixels - - # Calculate the new width and height while maintaining the aspect ratio - if aspect_ratio > 1: # Landscape - width = int(np.sqrt(target_area * aspect_ratio)) - height = int(target_area / width) - else: # Portrait - height = int(np.sqrt(target_area / aspect_ratio)) - width = int(target_area / height) - - # Round width and height to the nearest multiple of 8 - width = (width + 7) // 8 * 8 - height = (height + 7) // 8 * 8 - - return width, height - - - -#======裁剪图像 -class CustomCrop: - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "image": ("IMAGE",), - "width": ("INT", {"default": 768, "min": 0, "max": 4096, "step": 8}), - "height": ("INT", {"default": 768, "min": 0, "max": 4096, "step": 8}), - } - } - - RETURN_TYPES = ("IMAGE", "INT", "INT") - RETURN_NAMES = ("image", "width", "height") - FUNCTION = "process_image" - CATEGORY = "Meeeyo/File" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def IS_CHANGED(self): - return float("NaN") - - def process_image(self, image, width=768, height=768): - input_image = Image.fromarray((image.squeeze(0).numpy() * 255).astype(np.uint8)) - W, H = input_image.size - - processed_images = [] - - for frame in [input_image]: - frame = ImageOps.exif_transpose(frame) - - if frame.mode == 'P': - frame = frame.convert("RGBA") - elif 'A' in frame.getbands(): - frame = frame.convert("RGBA") - - processed_image = frame.convert("RGB") - processed_image = ImageOps.fit(processed_image, (width, height), method=Image.Resampling.BILINEAR, centering=(0.5, 0.5)) - - processed_image = np.array(processed_image).astype(np.float32) / 255.0 - processed_image = torch.from_numpy(processed_image)[None,] - processed_images.append(processed_image) - - output_image = torch.cat(processed_images, dim=0) if len(processed_images) > 1 else processed_images[0] - - return (output_image, width, height) - - -#======保存图像 -class SaveImagEX: - def __init__(self): - self.output_dir = folder_paths.get_output_directory() - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "image": ("IMAGE",), - "save_path": ("STRING", {"default": "./output"}), - "image_name": ("STRING", {"default": "ComfyUI"}), - "image_format": (["PNG", "JPG"], {"default": "JPG"}) - }, - } - - RETURN_TYPES = ("IMAGE",) - RETURN_NAMES = ("image",) - FUNCTION = "save_image" - OUTPUT_NODE = True - CATEGORY = "Meeeyo/File" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def IS_CHANGED(): - return float("NaN") - - def save_image(self, image, save_path, image_name, image_format): - if not isinstance(image, torch.Tensor): - raise ValueError("Invalid image tensor format") - if save_path == "./output": - save_path = self.output_dir - elif not os.path.isabs(save_path): - save_path = os.path.join(self.output_dir, save_path) - os.makedirs(save_path, exist_ok=True) - - # 移除可能存在的扩展名,然后添加用户选择的格式对应的扩展名 - base_name = os.path.splitext(image_name)[0] - - batch_size = image.shape[0] - channel_to_mode = {1: "L", 3: "RGB", 4: "RGBA"} - - for i in range(batch_size): - # 根据选择的格式动态设置扩展名 - if image_format == "PNG": - filename = f"{base_name}.png" if batch_size == 1 else f"{base_name}_{i:05d}.png" - save_format = "PNG" - save_params = {"compress_level": 0} - else: # JPG - filename = f"{base_name}.jpg" if batch_size == 1 else f"{base_name}_{i:05d}.jpg" - save_format = "JPEG" - save_params = {"quality": 100} - - full_path = os.path.join(save_path, filename) - single_image = image[i].cpu().numpy() - single_image = (single_image * 255).astype('uint8') - channels = single_image.shape[2] - if channels not in channel_to_mode: - raise ValueError(f"Unsupported channel number: {channels}") - mode = channel_to_mode[channels] - if channels == 1: - single_image = single_image[:, :, 0] - pil_image = Image.fromarray(single_image, mode) - - # 如果是JPG格式,需要转换为RGB模式 - if image_format == "JPG": - pil_image = pil_image.convert("RGB") - - pil_image.save(full_path, format=save_format, **save_params) - return (image,) - - -#======文件操作 -class FileCopyCutNode: - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "source_path": ("STRING", {"default": "", "multiline": False}), - "destination_path": ("STRING", {"default": "", "multiline": False}), - "operation": (["copy", "cut"], {"default": "copy"}), - }, - "optional": { - "any": ("*",), - }, - } - - RETURN_TYPES = ("STRING",) # 返回操作结果字符串 - RETURN_NAMES = ("result",) - FUNCTION = "copy_cut_file" - CATEGORY = "Meeeyo/File" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def IS_CHANGED(): - return float("NaN") - - def copy_cut_file(self, source_path, destination_path, operation, **kwargs): - result = "执行失败" - try: - # 检查文件是否存在 - if not os.path.isfile(source_path): - raise Exception(f"Source file not found: {source_path}") - - # 确保目录存在 - os.makedirs(os.path.dirname(destination_path), exist_ok=True) - - # 执行复制或剪切操作 - if operation == "copy": - shutil.copy2(source_path, destination_path) - result = "执行成功: 文件已复制" - elif operation == "cut": - shutil.move(source_path, destination_path) - result = "执行成功: 文件已剪切" - except Exception as e: - result = f"执行失败: {str(e)}" - - return (result,) - - -#======清理文件 -COMFYUI_OUTPUT_DIR = "output" - -class FileDeleteNode: - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "items_to_delete": ("STRING", {"default": "33.png\ncs1/01.png\ncs1", "multiline": True}), - }, - "optional": { - "any": ("*",), - }, - } - - RETURN_TYPES = ("STRING",) - RETURN_NAMES = ("result",) - FUNCTION = "delete_files" - CATEGORY = "Meeeyo/File" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def IS_CHANGED(self): - return float("NaN") - - def delete_files(self, items_to_delete, **kwargs): - result = "执行成功: 所有指定项已从output目录删除" - error_messages = [] - - # 确保只操作COMFYUI_OUTPUT_DIR目录下的内容 - base_output_dir = Path.cwd() / COMFYUI_OUTPUT_DIR - - # 处理多行文本输入 - items = items_to_delete.strip().split('\n') - - for item in items: - item = item.strip() - if not item: - continue - - # 检查是否有特殊命令[DeleteAll] - if item == "[DeleteAll]": - try: - # 删除output目录下所有内容 - for file_or_dir in base_output_dir.glob('*'): - if file_or_dir.is_file() or file_or_dir.is_symlink(): - file_or_dir.unlink() - elif file_or_dir.is_dir(): - shutil.rmtree(file_or_dir) - continue - except Exception as e: - error_messages.append(f"从output目录删除全部失败: {str(e)}") - continue - - # 构建完整路径 - target_path = base_output_dir / item - - # 检查是否位于output目录下 - try: - target_path.relative_to(base_output_dir) - except ValueError: - error_messages.append(f"{item} 不在output目录范围内,无法删除") - continue - - # 检查文件/目录是否存在 - if not target_path.exists(): - error_messages.append(f"在output目录下找不到 {item}") - continue - - # 删除操作 - try: - if target_path.is_file() or target_path.is_symlink(): - target_path.unlink() - elif target_path.is_dir(): - shutil.rmtree(target_path) - except Exception as e: - error_messages.append(f"从output目录删除 {item} 失败: {str(e)}") - - # 汇总结果 - if error_messages: - result = "部分执行失败:\n" + "\n".join(error_messages) - return (result,) - - -#======文件路径和后缀统计 -class FileListAndSuffix: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "folder_path": ("STRING",), - "file_extension": (["jpg", "png", "jpg&png", "txt", "csv", "all"], {"default": "jpg"}), - }, - "optional": {}, - } - - RETURN_TYPES = ("STRING", "INT", "LIST") - FUNCTION = "file_list_and_suffix" - CATEGORY = "Meeeyo/File" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def IS_CHANGED(): - return float("NaN") - - def file_list_and_suffix(self, folder_path, file_extension): - try: - if not os.path.isdir(folder_path): - return ("", 0, []) - - if file_extension == "all": - file_paths = [os.path.join(folder_path, f) for f in os.listdir(folder_path) if os.path.isfile(os.path.join(folder_path, f))] - elif file_extension == "jpg&png": - file_paths = [os.path.join(folder_path, f) for f in os.listdir(folder_path) if f.lower().endswith(('.jpg', '.png'))] - else: - file_paths = [os.path.join(folder_path, f) for f in os.listdir(folder_path) if f.lower().endswith('.' + file_extension)] - - return ('\n'.join(file_paths), len(file_paths), file_paths) - except Exception as e: - return ("", 0, []) - - -#======读取表格数据 -class ReadExcelData: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "excel_path": ("STRING", {"default": "path/to/your/file.xlsx"}), - "sheet_name": ("STRING", {"default": "Sheet1"}), - "row_range": ("STRING", {"default": "2-3"}), - "col_range": ("STRING", {"default": "1-4"}), - } - } - - RETURN_TYPES = ("STRING",) - FUNCTION = "read_excel_data" - CATEGORY = "Meeeyo/File" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def IS_CHANGED(): - return float("NaN") - - def read_excel_data(self, excel_path, sheet_name, row_range, col_range): - try: - if "-" in row_range: - start_row, end_row = map(int, row_range.split("-")) - else: - start_row = end_row = int(row_range) - - if "-" in col_range: - start_col, end_col = map(int, col_range.split("-")) - else: - start_col = end_col = int(col_range) - - start_row = max(1, start_row) - start_col = max(1, start_col) - - workbook = openpyxl.load_workbook(excel_path, read_only=True, data_only=True) - sheet = workbook[sheet_name] - - output_lines = [] - for row in range(start_row, end_row + 1): - row_data = [] - for col in range(start_col, end_col + 1): - cell_value = sheet.cell(row=row, column=col).value - row_data.append(str(cell_value) if cell_value is not None else "") - output_lines.append("|".join(row_data)) - - workbook.close() - del workbook - - return ("\n".join(output_lines),) - - except Exception as e: - return (f"Error: {str(e)}",) - - -#======写入表格数据 -class WriteExcelData: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "excel_path": ("STRING", {"default": "path/to/your/file.xlsx"}), - "sheet_name": ("STRING", {"default": "Sheet1"}), - "row_range": ("STRING", {"default": "2-3"}), - "col_range": ("STRING", {"default": "1-4"}), - "data": ("STRING", {"default": "", "multiline": True}), - } - } - - RETURN_TYPES = ("STRING",) - FUNCTION = "write_excel_data" - CATEGORY = "Meeeyo/File" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def IS_CHANGED(): - return float("NaN") - - def write_excel_data(self, excel_path, sheet_name, row_range, col_range, data): - try: - if not os.path.exists(excel_path): - return (f"Error: File does not exist at path: {excel_path}",) - - if not os.access(excel_path, os.W_OK): - return (f"Error: No write permission for file at path: {excel_path}",) - - if "-" in row_range: - start_row, end_row = map(int, row_range.split("-")) - else: - start_row = end_row = int(row_range) - - if "-" in col_range: - start_col, end_col = map(int, col_range.split("-")) - else: - start_col = end_col = int(col_range) - - start_row = max(1, start_row) - start_col = max(1, start_col) - - workbook = openpyxl.load_workbook(excel_path, read_only=False, data_only=True) - sheet = workbook[sheet_name] - - data_lines = data.strip().split("\n") - - for row_index, line in enumerate(data_lines, start=start_row): - if row_index > end_row: - break - - cell_values = line.split("|") - for col_index, cell_value in enumerate(cell_values, start=start_col): - if col_index > end_col: - break - - if cell_value.strip(): - sheet.cell(row=row_index, column=col_index).value = cell_value.strip() - - workbook.save(excel_path) - - workbook.close() - del workbook - - return ("Data written successfully!",) - - except PermissionError as pe: - return (f"Permission Error: {str(pe)}",) - except Exception as e: - return (f"Error: {str(e)}",) - - -#======查找表格数据 -class FindExcelData: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "excel_path": ("STRING", {"default": "path/to/your/file.xlsx"}), - "sheet_name": ("STRING", {"default": "Sheet1"}), - "search_content": ("STRING", {"default": "查找内容"}), - "search_mode": (["精确查找", "模糊查找"], {"default": "精确查找"}), - }, - "optional": {}, - } - - RETURN_TYPES = ("STRING", "INT", "INT") - FUNCTION = "find_excel_data" - CATEGORY = "Meeeyo/File" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def IS_CHANGED(): - return float("NaN") - - def find_excel_data(self, excel_path, sheet_name, search_content, search_mode): - try: - if not os.path.exists(excel_path): - return (f"Error: File does not exist at path: {excel_path}", None, None) - - if not os.access(excel_path, os.R_OK): - return (f"Error: No read permission for file at path: {excel_path}", None, None) - - workbook = openpyxl.load_workbook(excel_path, read_only=True, data_only=True) - sheet = workbook[sheet_name] - - results = [] - found_row = None - found_col = None - - for row in range(1, sheet.max_row + 1): - for col in range(1, sheet.max_column + 1): - cell = sheet.cell(row=row, column=col) - cell_value = cell.value if cell.value is not None else "" - - if (search_mode == "精确查找" and cell_value == search_content) or \ - (search_mode == "模糊查找" and search_content in cell_value): - results.append(f"{sheet_name}|{row}|{col}|{cell_value}") - found_row = row - found_col = col - - workbook.close() - del workbook - - if not results: - return ("No results found.", None, None) - - return ("\n".join(results), found_row, found_col) - - except Exception as e: - return (f"Error: {str(e)}", None, None) - - -#======读取表格数量差 -class ReadExcelRowOrColumnDiff: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "excel_path": ("STRING", {"default": "path/to/your/file.xlsx"}), - "sheet_name": ("STRING", {"default": "Sheet1"}), - "read_mode": (["读行", "读列"], {"default": "读行"}), - "indices": ("STRING", {"default": "1,3"}), - } - } - - RETURN_TYPES = ("INT",) - FUNCTION = "read_excel_row_or_column_diff" - CATEGORY = "Meeeyo/File" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def IS_CHANGED(): - return float("NaN") - - def read_excel_row_or_column_diff(self, excel_path, sheet_name, read_mode, indices): - try: - if not os.path.exists(excel_path): - return (f"Error: File does not exist at path: {excel_path}",) - - if not os.access(excel_path, os.R_OK): - return (f"Error: No read permission for file at path: {excel_path}",) - - workbook = openpyxl.load_workbook(excel_path, read_only=True, data_only=True) - sheet = workbook[sheet_name] - - def count_cells(mode, index): - count = 0 - if mode == "读行": - for col in range(1, sheet.max_column + 1): - cell_value = sheet.cell(row=index, column=col).value - if cell_value is not None: - count += 1 - else: - break - elif mode == "读列": - for row in range(1, sheet.max_row + 1): - cell_value = sheet.cell(row=row, column=index).value - if cell_value is not None: - count += 1 - else: - break - return count - - indices = indices.strip() - if "," in indices: - try: - index1, index2 = map(int, indices.split(",")) - except ValueError: - return (f"Error: Invalid indices format. Please use 'number,number' format.",) - - count1 = count_cells(read_mode, index1) - count2 = count_cells(read_mode, index2) - result = count1 - count2 - else: - try: - index = int(indices) - except ValueError: - return (f"Error: Invalid index format. Please enter a valid number.",) - - result = count_cells(read_mode, index) - - workbook.close() - del workbook - - return (result,) - - except Exception as e: - return (f"Error: {str(e)}",) +import os +import csv +import torch +import shutil +import requests +import chardet +import pathlib +import openpyxl +import folder_paths +import node_helpers +import numpy as np +from PIL import Image, ImageOps, ImageSequence +from pathlib import Path + +#======加载重置图像 +class LoadAndAdjustImage: + @classmethod + def INPUT_TYPES(s): + input_dir = folder_paths.get_input_directory() + files = [f.name for f in Path(input_dir).iterdir() if f.is_file()] + return { + "required": { + "image": (sorted(files), {"image_upload": True}), + "max_dimension": ("INT", {"default": 0, "min": 0, "max": 4096, "step": 8}), + "size_option": (["No Change", "Custom", "Million Pixels", "Small", "Medium", "Large", + "480P-H(vid 4:3)", "480P-V(vid 3:4)", "720P-H(vid 16:9)", "720P-V(vid 9:16)", "832×480", "480×832"], + {"default": "No Change"}) + } + } + + RETURN_TYPES = ("IMAGE", "MASK", "STRING") + RETURN_NAMES = ("image", "mask", "info") + FUNCTION = "load_image" + CATEGORY = "Meeeyo/File" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def IS_CHANGED(): + return float("NaN") + + def load_image(self, image, max_dimension, size_option): + image_path = folder_paths.get_annotated_filepath(image) + img = Image.open(image_path) + W, H = img.size + aspect_ratio = W / H + + def get_target_size(): + if size_option == "No Change": + # No resizing or cropping, just return the original size + return W, H + elif size_option == "Million Pixels": + return self._resize_to_million_pixels(W, H) + elif size_option == "Custom": + ratio = min(max_dimension / W, max_dimension / H) + return round(W * ratio), round(H * ratio) + + size_options = { + "Small": ( + (768, 512) if aspect_ratio >= 1.23 else + (512, 768) if aspect_ratio <= 0.82 else + (768, 768) + ), + "Medium": ( + (1216, 832) if aspect_ratio >= 1.23 else + (832, 1216) if aspect_ratio <= 0.82 else + (1216, 1216) + ), + "Large": ( + (1600, 1120) if aspect_ratio >= 1.23 else + (1120, 1600) if aspect_ratio <= 0.82 else + (1600, 1600) + ), + "Million Pixels": self._resize_to_million_pixels(W, H), # Million Pixels option + "480P-H(vid 4:3)": (640, 480), # 480P-H, 640x480 + "480P-V(vid 3:4)": (480, 640), # 480P-V, 480x640 + "720P-H(vid 16:9)": (1280, 720), # 720P-H, 1280x720 + "720P-V(vid 9:16)": (720, 1280), # 720P-V, 720x1280 + "832×480": (832, 480), # 832x480 + "480×832": (480, 832), # 480x832 + } + return size_options[size_option] + + target_width, target_height = get_target_size() + output_images = [] + output_masks = [] + + for frame in ImageSequence.Iterator(img): + frame = ImageOps.exif_transpose(frame) + if frame.mode == 'P': + frame = frame.convert("RGBA") + elif 'A' in frame.getbands(): + frame = frame.convert("RGBA") + + if size_option == "No Change": + # No resizing, just use the original frame + image_frame = frame.convert("RGB") + else: + if size_option == "Custom" or size_option == "Million Pixels": + ratio = min(target_width / W, target_height / H) + adjusted_width = round(W * ratio) + adjusted_height = round(H * ratio) + image_frame = frame.convert("RGB").resize((adjusted_width, adjusted_height), Image.Resampling.BILINEAR) + else: + image_frame = frame.convert("RGB") + image_frame = ImageOps.fit(image_frame, (target_width, target_height), method=Image.Resampling.BILINEAR, centering=(0.5, 0.5)) + + image_array = np.array(image_frame).astype(np.float32) / 255.0 + output_images.append(torch.from_numpy(image_array)[None,]) + + # Process the mask if available + if 'A' in frame.getbands(): + mask_frame = frame.getchannel('A') + if size_option == "Custom" or size_option == "Million Pixels": + mask_frame = mask_frame.resize((adjusted_width, adjusted_height), Image.Resampling.BILINEAR) + else: + mask_frame = ImageOps.fit(mask_frame, (target_width, target_height), method=Image.Resampling.BILINEAR, centering=(0.5, 0.5)) + mask = np.array(mask_frame).astype(np.float32) / 255.0 + mask = 1. - mask + else: + if size_option == "Custom" or size_option == "Million Pixels": + mask = np.zeros((adjusted_height, adjusted_width), dtype=np.float32) + else: + mask = np.zeros((target_height, target_width), dtype=np.float32) + output_masks.append(torch.from_numpy(mask).unsqueeze(0)) + + output_image = torch.cat(output_images, dim=0) if len(output_images) > 1 else output_images[0] + output_mask = torch.cat(output_masks, dim=0) if len(output_masks) > 1 else output_masks[0] + info = f"Image Path: {image_path}\nOriginal Size: {W}x{H}\nAdjusted Size: {target_width}x{target_height}" + return (output_image, output_mask, info) + + @classmethod + def VALIDATE_INPUTS(s, image): + if not folder_paths.exists_annotated_filepath(image): + return f"Invalid image file: {image}" + return True + + def _resize_to_million_pixels(self, W, H): + # Calculate the aspect ratio of the original image + aspect_ratio = W / H + target_area = 1000000 # 1 million pixels + + # Calculate the new width and height while maintaining the aspect ratio + if aspect_ratio > 1: # Landscape + width = int(np.sqrt(target_area * aspect_ratio)) + height = int(target_area / width) + else: # Portrait + height = int(np.sqrt(target_area / aspect_ratio)) + width = int(target_area / height) + + # Round width and height to the nearest multiple of 8 + width = (width + 7) // 8 * 8 + height = (height + 7) // 8 * 8 + + return width, height + + + +#======重置图像 +class ImageAdjuster: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "image": ("IMAGE",), + "max_dimension": ("INT", {"default": 0, "min": 0, "max": 4096, "step": 8}), + "size_option": ([ + "Custom", "Million Pixels", "Small", "Medium", "Large", + "480P-H(vid 4:3)", "480P-V(vid 3:4)", "720P-H(vid 16:9)", "720P-V(vid 9:16)", "832×480", "480×832"], + {"default": "Million Pixels"}) + } + } + + RETURN_TYPES = ("IMAGE", "INT", "INT") + RETURN_NAMES = ("image", "width", "height") + FUNCTION = "process_image" + CATEGORY = "Meeeyo/File" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def IS_CHANGED(): + return float("NaN") + + def process_image(self, image, max_dimension=1024, size_option="Custom"): + input_image = Image.fromarray((image.squeeze(0).numpy() * 255).astype(np.uint8)) + W, H = input_image.size + + processed_images = [] + + for frame in [input_image]: + frame = ImageOps.exif_transpose(frame) + + if frame.mode == 'P': + frame = frame.convert("RGBA") + elif 'A' in frame.getbands(): + frame = frame.convert("RGBA") + + if size_option != "Custom": + aspect_ratio = W / H + + size_options = { + "Small": ( + (768, 512) if aspect_ratio >= 1.23 else + (512, 768) if aspect_ratio <= 0.82 else + (768, 768) + ), + "Medium": ( + (1216, 832) if aspect_ratio >= 1.23 else + (832, 1216) if aspect_ratio <= 0.82 else + (1216, 1216) + ), + "Large": ( + (1600, 1120) if aspect_ratio >= 1.23 else + (1120, 1600) if aspect_ratio <= 0.82 else + (1600, 1600) + ), + "Million Pixels": self._resize_to_million_pixels(W, H), # Million Pixels option + "480P-H(vid 4:3)": (640, 480), # 480P-H, 640x480 + "480P-V(vid 3:4)": (480, 640), # 480P-V, 480x640 + "720P-H(vid 16:9)": (1280, 720), # 720P-H, 1280x720 + "720P-V(vid 9:16)": (720, 1280), # 720P-V, 720x1280 + "832×480": (832, 480), # 832x480 + "480×832": (480, 832), # 480x832 + } + + target_width, target_height = size_options[size_option] + processed_image = frame.convert("RGB") + processed_image = ImageOps.fit(processed_image, (target_width, target_height), method=Image.Resampling.BILINEAR, centering=(0.5, 0.5)) + else: + ratio = min(max_dimension / W, max_dimension / H) + adjusted_width = round(W * ratio) + adjusted_height = round(H * ratio) + + processed_image = frame.convert("RGB") + processed_image = processed_image.resize((adjusted_width, adjusted_height), Image.Resampling.BILINEAR) + + processed_image = np.array(processed_image).astype(np.float32) / 255.0 + processed_image = torch.from_numpy(processed_image)[None,] + processed_images.append(processed_image) + + output_image = torch.cat(processed_images, dim=0) if len(processed_images) > 1 else processed_images[0] + if size_option != "Custom": + return (output_image, target_width, target_height) + else: + return (output_image, adjusted_width, adjusted_height) + + def _resize_to_million_pixels(self, W, H): + # Calculate the aspect ratio of the original image + aspect_ratio = W / H + target_area = 1000000 # 1 million pixels + + # Calculate the new width and height while maintaining the aspect ratio + if aspect_ratio > 1: # Landscape + width = int(np.sqrt(target_area * aspect_ratio)) + height = int(target_area / width) + else: # Portrait + height = int(np.sqrt(target_area / aspect_ratio)) + width = int(target_area / height) + + # Round width and height to the nearest multiple of 8 + width = (width + 7) // 8 * 8 + height = (height + 7) // 8 * 8 + + return width, height + + + +#======裁剪图像 +class CustomCrop: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "image": ("IMAGE",), + "width": ("INT", {"default": 768, "min": 0, "max": 4096, "step": 8}), + "height": ("INT", {"default": 768, "min": 0, "max": 4096, "step": 8}), + } + } + + RETURN_TYPES = ("IMAGE", "INT", "INT") + RETURN_NAMES = ("image", "width", "height") + FUNCTION = "process_image" + CATEGORY = "Meeeyo/File" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def IS_CHANGED(self): + return float("NaN") + + def process_image(self, image, width=768, height=768): + input_image = Image.fromarray((image.squeeze(0).numpy() * 255).astype(np.uint8)) + W, H = input_image.size + + processed_images = [] + + for frame in [input_image]: + frame = ImageOps.exif_transpose(frame) + + if frame.mode == 'P': + frame = frame.convert("RGBA") + elif 'A' in frame.getbands(): + frame = frame.convert("RGBA") + + processed_image = frame.convert("RGB") + processed_image = ImageOps.fit(processed_image, (width, height), method=Image.Resampling.BILINEAR, centering=(0.5, 0.5)) + + processed_image = np.array(processed_image).astype(np.float32) / 255.0 + processed_image = torch.from_numpy(processed_image)[None,] + processed_images.append(processed_image) + + output_image = torch.cat(processed_images, dim=0) if len(processed_images) > 1 else processed_images[0] + + return (output_image, width, height) + + +#======保存图像 +class SaveImagEX: + def __init__(self): + self.output_dir = folder_paths.get_output_directory() + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "image": ("IMAGE",), + "save_path": ("STRING", {"default": "./output"}), + "image_name": ("STRING", {"default": "ComfyUI"}), + "image_format": (["PNG", "JPG"], {"default": "JPG"}) + }, + } + + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("image",) + FUNCTION = "save_image" + OUTPUT_NODE = True + CATEGORY = "Meeeyo/File" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def IS_CHANGED(): + return float("NaN") + + def save_image(self, image, save_path, image_name, image_format): + if not isinstance(image, torch.Tensor): + raise ValueError("Invalid image tensor format") + if save_path == "./output": + save_path = self.output_dir + elif not os.path.isabs(save_path): + save_path = os.path.join(self.output_dir, save_path) + os.makedirs(save_path, exist_ok=True) + + # 移除可能存在的扩展名,然后添加用户选择的格式对应的扩展名 + base_name = os.path.splitext(image_name)[0] + + batch_size = image.shape[0] + channel_to_mode = {1: "L", 3: "RGB", 4: "RGBA"} + + for i in range(batch_size): + # 根据选择的格式动态设置扩展名 + if image_format == "PNG": + filename = f"{base_name}.png" if batch_size == 1 else f"{base_name}_{i:05d}.png" + save_format = "PNG" + save_params = {"compress_level": 0} + else: # JPG + filename = f"{base_name}.jpg" if batch_size == 1 else f"{base_name}_{i:05d}.jpg" + save_format = "JPEG" + save_params = {"quality": 100} + + full_path = os.path.join(save_path, filename) + single_image = image[i].cpu().numpy() + single_image = (single_image * 255).astype('uint8') + channels = single_image.shape[2] + if channels not in channel_to_mode: + raise ValueError(f"Unsupported channel number: {channels}") + mode = channel_to_mode[channels] + if channels == 1: + single_image = single_image[:, :, 0] + pil_image = Image.fromarray(single_image, mode) + + # 如果是JPG格式,需要转换为RGB模式 + if image_format == "JPG": + pil_image = pil_image.convert("RGB") + + pil_image.save(full_path, format=save_format, **save_params) + return (image,) + + +#======文件操作 +class FileCopyCutNode: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "source_path": ("STRING", {"default": "", "multiline": False}), + "destination_path": ("STRING", {"default": "", "multiline": False}), + "operation": (["copy", "cut"], {"default": "copy"}), + }, + "optional": { + "any": ("*",), + }, + } + + RETURN_TYPES = ("STRING",) # 返回操作结果字符串 + RETURN_NAMES = ("result",) + FUNCTION = "copy_cut_file" + CATEGORY = "Meeeyo/File" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def IS_CHANGED(): + return float("NaN") + + def copy_cut_file(self, source_path, destination_path, operation, **kwargs): + result = "执行失败" + try: + # 检查文件是否存在 + if not os.path.isfile(source_path): + raise Exception(f"Source file not found: {source_path}") + + # 确保目录存在 + os.makedirs(os.path.dirname(destination_path), exist_ok=True) + + # 执行复制或剪切操作 + if operation == "copy": + shutil.copy2(source_path, destination_path) + result = "执行成功: 文件已复制" + elif operation == "cut": + shutil.move(source_path, destination_path) + result = "执行成功: 文件已剪切" + except Exception as e: + result = f"执行失败: {str(e)}" + + return (result,) + + +#======清理文件 +COMFYUI_OUTPUT_DIR = "output" + +class FileDeleteNode: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "items_to_delete": ("STRING", {"default": "33.png\ncs1/01.png\ncs1", "multiline": True}), + }, + "optional": { + "any": ("*",), + }, + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("result",) + FUNCTION = "delete_files" + CATEGORY = "Meeeyo/File" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def IS_CHANGED(self): + return float("NaN") + + def delete_files(self, items_to_delete, **kwargs): + result = "执行成功: 所有指定项已从output目录删除" + error_messages = [] + + # 确保只操作COMFYUI_OUTPUT_DIR目录下的内容 + base_output_dir = Path.cwd() / COMFYUI_OUTPUT_DIR + + # 处理多行文本输入 + items = items_to_delete.strip().split('\n') + + for item in items: + item = item.strip() + if not item: + continue + + # 检查是否有特殊命令[DeleteAll] + if item == "[DeleteAll]": + try: + # 删除output目录下所有内容 + for file_or_dir in base_output_dir.glob('*'): + if file_or_dir.is_file() or file_or_dir.is_symlink(): + file_or_dir.unlink() + elif file_or_dir.is_dir(): + shutil.rmtree(file_or_dir) + continue + except Exception as e: + error_messages.append(f"从output目录删除全部失败: {str(e)}") + continue + + # 构建完整路径 + target_path = base_output_dir / item + + # 检查是否位于output目录下 + try: + target_path.relative_to(base_output_dir) + except ValueError: + error_messages.append(f"{item} 不在output目录范围内,无法删除") + continue + + # 检查文件/目录是否存在 + if not target_path.exists(): + error_messages.append(f"在output目录下找不到 {item}") + continue + + # 删除操作 + try: + if target_path.is_file() or target_path.is_symlink(): + target_path.unlink() + elif target_path.is_dir(): + shutil.rmtree(target_path) + except Exception as e: + error_messages.append(f"从output目录删除 {item} 失败: {str(e)}") + + # 汇总结果 + if error_messages: + result = "部分执行失败:\n" + "\n".join(error_messages) + return (result,) + + +#======文件路径和后缀统计 +class FileListAndSuffix: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "folder_path": ("STRING",), + "file_extension": (["jpg", "png", "jpg&png", "txt", "csv", "all"], {"default": "jpg"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING", "INT", "LIST") + FUNCTION = "file_list_and_suffix" + CATEGORY = "Meeeyo/File" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def IS_CHANGED(): + return float("NaN") + + def file_list_and_suffix(self, folder_path, file_extension): + try: + if not os.path.isdir(folder_path): + return ("", 0, []) + + if file_extension == "all": + file_paths = [os.path.join(folder_path, f) for f in os.listdir(folder_path) if os.path.isfile(os.path.join(folder_path, f))] + elif file_extension == "jpg&png": + file_paths = [os.path.join(folder_path, f) for f in os.listdir(folder_path) if f.lower().endswith(('.jpg', '.png'))] + else: + file_paths = [os.path.join(folder_path, f) for f in os.listdir(folder_path) if f.lower().endswith('.' + file_extension)] + + return ('\n'.join(file_paths), len(file_paths), file_paths) + except Exception as e: + return ("", 0, []) + + +#======读取表格数据 +class ReadExcelData: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "excel_path": ("STRING", {"default": "path/to/your/file.xlsx"}), + "sheet_name": ("STRING", {"default": "Sheet1"}), + "row_range": ("STRING", {"default": "2-3"}), + "col_range": ("STRING", {"default": "1-4"}), + } + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "read_excel_data" + CATEGORY = "Meeeyo/File" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def IS_CHANGED(): + return float("NaN") + + def read_excel_data(self, excel_path, sheet_name, row_range, col_range): + try: + if "-" in row_range: + start_row, end_row = map(int, row_range.split("-")) + else: + start_row = end_row = int(row_range) + + if "-" in col_range: + start_col, end_col = map(int, col_range.split("-")) + else: + start_col = end_col = int(col_range) + + start_row = max(1, start_row) + start_col = max(1, start_col) + + workbook = openpyxl.load_workbook(excel_path, read_only=True, data_only=True) + sheet = workbook[sheet_name] + + output_lines = [] + for row in range(start_row, end_row + 1): + row_data = [] + for col in range(start_col, end_col + 1): + cell_value = sheet.cell(row=row, column=col).value + row_data.append(str(cell_value) if cell_value is not None else "") + output_lines.append("|".join(row_data)) + + workbook.close() + del workbook + + return ("\n".join(output_lines),) + + except Exception as e: + return (f"Error: {str(e)}",) + + +#======写入表格数据 +class WriteExcelData: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "excel_path": ("STRING", {"default": "path/to/your/file.xlsx"}), + "sheet_name": ("STRING", {"default": "Sheet1"}), + "row_range": ("STRING", {"default": "2-3"}), + "col_range": ("STRING", {"default": "1-4"}), + "data": ("STRING", {"default": "", "multiline": True}), + } + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "write_excel_data" + CATEGORY = "Meeeyo/File" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def IS_CHANGED(): + return float("NaN") + + def write_excel_data(self, excel_path, sheet_name, row_range, col_range, data): + try: + if not os.path.exists(excel_path): + return (f"Error: File does not exist at path: {excel_path}",) + + if not os.access(excel_path, os.W_OK): + return (f"Error: No write permission for file at path: {excel_path}",) + + if "-" in row_range: + start_row, end_row = map(int, row_range.split("-")) + else: + start_row = end_row = int(row_range) + + if "-" in col_range: + start_col, end_col = map(int, col_range.split("-")) + else: + start_col = end_col = int(col_range) + + start_row = max(1, start_row) + start_col = max(1, start_col) + + workbook = openpyxl.load_workbook(excel_path, read_only=False, data_only=True) + sheet = workbook[sheet_name] + + data_lines = data.strip().split("\n") + + for row_index, line in enumerate(data_lines, start=start_row): + if row_index > end_row: + break + + cell_values = line.split("|") + for col_index, cell_value in enumerate(cell_values, start=start_col): + if col_index > end_col: + break + + if cell_value.strip(): + sheet.cell(row=row_index, column=col_index).value = cell_value.strip() + + workbook.save(excel_path) + + workbook.close() + del workbook + + return ("Data written successfully!",) + + except PermissionError as pe: + return (f"Permission Error: {str(pe)}",) + except Exception as e: + return (f"Error: {str(e)}",) + + +#======查找表格数据 +class FindExcelData: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "excel_path": ("STRING", {"default": "path/to/your/file.xlsx"}), + "sheet_name": ("STRING", {"default": "Sheet1"}), + "search_content": ("STRING", {"default": "查找内容"}), + "search_mode": (["精确查找", "模糊查找"], {"default": "精确查找"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING", "INT", "INT") + FUNCTION = "find_excel_data" + CATEGORY = "Meeeyo/File" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def IS_CHANGED(): + return float("NaN") + + def find_excel_data(self, excel_path, sheet_name, search_content, search_mode): + try: + if not os.path.exists(excel_path): + return (f"Error: File does not exist at path: {excel_path}", None, None) + + if not os.access(excel_path, os.R_OK): + return (f"Error: No read permission for file at path: {excel_path}", None, None) + + workbook = openpyxl.load_workbook(excel_path, read_only=True, data_only=True) + sheet = workbook[sheet_name] + + results = [] + found_row = None + found_col = None + + for row in range(1, sheet.max_row + 1): + for col in range(1, sheet.max_column + 1): + cell = sheet.cell(row=row, column=col) + cell_value = cell.value if cell.value is not None else "" + + if (search_mode == "精确查找" and cell_value == search_content) or \ + (search_mode == "模糊查找" and search_content in cell_value): + results.append(f"{sheet_name}|{row}|{col}|{cell_value}") + found_row = row + found_col = col + + workbook.close() + del workbook + + if not results: + return ("No results found.", None, None) + + return ("\n".join(results), found_row, found_col) + + except Exception as e: + return (f"Error: {str(e)}", None, None) + + +#======读取表格数量差 +class ReadExcelRowOrColumnDiff: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "excel_path": ("STRING", {"default": "path/to/your/file.xlsx"}), + "sheet_name": ("STRING", {"default": "Sheet1"}), + "read_mode": (["读行", "读列"], {"default": "读行"}), + "indices": ("STRING", {"default": "1,3"}), + } + } + + RETURN_TYPES = ("INT",) + FUNCTION = "read_excel_row_or_column_diff" + CATEGORY = "Meeeyo/File" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def IS_CHANGED(): + return float("NaN") + + def read_excel_row_or_column_diff(self, excel_path, sheet_name, read_mode, indices): + try: + if not os.path.exists(excel_path): + return (f"Error: File does not exist at path: {excel_path}",) + + if not os.access(excel_path, os.R_OK): + return (f"Error: No read permission for file at path: {excel_path}",) + + workbook = openpyxl.load_workbook(excel_path, read_only=True, data_only=True) + sheet = workbook[sheet_name] + + def count_cells(mode, index): + count = 0 + if mode == "读行": + for col in range(1, sheet.max_column + 1): + cell_value = sheet.cell(row=index, column=col).value + if cell_value is not None: + count += 1 + else: + break + elif mode == "读列": + for row in range(1, sheet.max_row + 1): + cell_value = sheet.cell(row=row, column=index).value + if cell_value is not None: + count += 1 + else: + break + return count + + indices = indices.strip() + if "," in indices: + try: + index1, index2 = map(int, indices.split(",")) + except ValueError: + return (f"Error: Invalid indices format. Please use 'number,number' format.",) + + count1 = count_cells(read_mode, index1) + count2 = count_cells(read_mode, index2) + result = count1 - count2 + else: + try: + index = int(indices) + except ValueError: + return (f"Error: Invalid index format. Please enter a valid number.",) + + result = count_cells(read_mode, index) + + workbook.close() + del workbook + + return (result,) + + except Exception as e: + return (f"Error: {str(e)}",) diff --git a/meyo_node_Functional.py b/meyo_node_Functional.py index bf20f93..cf0fd1c 100644 --- a/meyo_node_Functional.py +++ b/meyo_node_Functional.py @@ -1,108 +1,108 @@ -import datetime -import time -import secrets -import requests -import base64 - - -class GetCurrentTime: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "prefix": ("STRING", {"default": ""}), - }, - "optional": { - "any": ("*",), - }, - } - - RETURN_TYPES = ("STRING", "INT") - FUNCTION = "get_current_time" - CATEGORY = "Meeeyo/Functional" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def IS_CHANGED(): - return float("NaN") - - def get_current_time(self, prefix="", any=None): - current_time = datetime.datetime.now() - formatted_time = current_time.strftime("%Y-%m-%d %H:%M:%S") - result = f"{prefix}{formatted_time}" - timestamp = int(round(time.time() * 1000)) - return (result, timestamp) - - -class SelectionParameter: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "gender": (["男性", "女性"], {"default": "男性"}), - "version": (["竖版", "横版"], {"default": "竖版"}), - "additional_text": ("STRING", {"multiline": True, "default": "附加的多行文本内容"}), - }, - "optional": {}, - } - - RETURN_TYPES = ("STRING",) - FUNCTION = "gender_output" - CATEGORY = "Meeeyo/Functional" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def gender_output(self, gender, version, additional_text): - gender_value = 1 if gender == "男性" else 2 - version_value = 1 if version == "竖版" else 2 - result = f"{gender_value}+{version_value}" - combined_result = f"{result}\n\n{additional_text.strip()}" - return (combined_result,) - - -class ReadWebNode: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "Instruction": ("STRING", {"default": ""}), - "prefix_suffix": ("STRING", {"default": ""}), - }, - "optional": { - "any": ("*",), - }, - "hidden": {} - } - - RETURN_TYPES = ("STRING",) - FUNCTION = "fetch_data" - CATEGORY = "Meeeyo/Functional" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def IS_CHANGED(): - return float("NaN") - - def fetch_data(self, Instruction, prefix_suffix, any=None): - if "|" in prefix_suffix: - prefix, suffix = prefix_suffix.split("|", 1) - else: - prefix = prefix_suffix - suffix = "" - modified_url = f"{base64.b64decode('aHR0cHM6Ly93d3cubWVlZXlvLmNvbS8=').decode()}{Instruction.lower()}{base64.b64decode('LnBocA==').decode()}" - - try: - token = secrets.token_hex(16) - headers = {'Authorization': f'Bearer {token}'} - response = requests.get(modified_url, headers=headers) - response.raise_for_status() - response_text = f"{prefix}{response.text}{suffix}" - return (response_text,) - except requests.RequestException as e: +import datetime +import time +import secrets +import requests +import base64 + + +class GetCurrentTime: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "prefix": ("STRING", {"default": ""}), + }, + "optional": { + "any": ("*",), + }, + } + + RETURN_TYPES = ("STRING", "INT") + FUNCTION = "get_current_time" + CATEGORY = "Meeeyo/Functional" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def IS_CHANGED(): + return float("NaN") + + def get_current_time(self, prefix="", any=None): + current_time = datetime.datetime.now() + formatted_time = current_time.strftime("%Y-%m-%d %H:%M:%S") + result = f"{prefix}{formatted_time}" + timestamp = int(round(time.time() * 1000)) + return (result, timestamp) + + +class SelectionParameter: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "gender": (["男性", "女性"], {"default": "男性"}), + "version": (["竖版", "横版"], {"default": "竖版"}), + "additional_text": ("STRING", {"multiline": True, "default": "附加的多行文本内容"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "gender_output" + CATEGORY = "Meeeyo/Functional" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def gender_output(self, gender, version, additional_text): + gender_value = 1 if gender == "男性" else 2 + version_value = 1 if version == "竖版" else 2 + result = f"{gender_value}+{version_value}" + combined_result = f"{result}\n\n{additional_text.strip()}" + return (combined_result,) + + +class ReadWebNode: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "Instruction": ("STRING", {"default": ""}), + "prefix_suffix": ("STRING", {"default": ""}), + }, + "optional": { + "any": ("*",), + }, + "hidden": {} + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "fetch_data" + CATEGORY = "Meeeyo/Functional" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def IS_CHANGED(): + return float("NaN") + + def fetch_data(self, Instruction, prefix_suffix, any=None): + if "|" in prefix_suffix: + prefix, suffix = prefix_suffix.split("|", 1) + else: + prefix = prefix_suffix + suffix = "" + modified_url = f"{base64.b64decode('aHR0cHM6Ly93d3cubWVlZXlvLmNvbS8=').decode()}{Instruction.lower()}{base64.b64decode('LnBocA==').decode()}" + + try: + token = secrets.token_hex(16) + headers = {'Authorization': f'Bearer {token}'} + response = requests.get(modified_url, headers=headers) + response.raise_for_status() + response_text = f"{prefix}{response.text}{suffix}" + return (response_text,) + except requests.RequestException as e: return ('Error!解析失败,请检查后重试!',) \ No newline at end of file diff --git a/meyo_node_String.py b/meyo_node_String.py index 423ce8c..f277c13 100644 --- a/meyo_node_String.py +++ b/meyo_node_String.py @@ -1,1109 +1,1109 @@ -import datetime -import random -import secrets -import requests -import string -import math -import re - - -#======文本输入 -class SingleTextInput: - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "text": ("STRING", {"default": "", "multiline": True}), - } - } - - RETURN_TYPES = ("STRING",) - FUNCTION = "process_input" - CATEGORY = "Meeeyo/String" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - OUTPUT_NODE = False - - def process_input(self, text): - return (text,) - - -#======多参数输入 -class MultiParamInputNode: - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "text1": ("STRING", {"default": "", "multiline": True}), # 第一个多行文本输入框 - "text2": ("STRING", {"default": "", "multiline": True}), # 第二个多行文本输入框 - "int1": ("INT", {"default": 0, "min": -1000000, "max": 1000000}), # 第一个整数输入框 - "int2": ("INT", {"default": 0, "min": -1000000, "max": 1000000}), # 第二个整数输入框 - } - } - - RETURN_TYPES = ("STRING", "STRING", "INT", "INT") # 返回两个字符串和两个整数 - FUNCTION = "process_inputs" - CATEGORY = "Meeeyo/String" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - OUTPUT_NODE = False - - def process_inputs(self, text1, text2, int1, int2): - return (text1, text2, int1, int2) - - -#======添加前后缀 -class AddPrefixSuffix: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "input_string": ("STRING", {"default": ""}), - "prefix": ("STRING", {"default": "前缀符"}), - "suffix": ("STRING", {"default": "后缀符"}), - }, - "optional": {}, - } - - RETURN_TYPES = ("STRING",) - FUNCTION = "add_prefix_suffix" - CATEGORY = "Meeeyo/String" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def add_prefix_suffix(self, input_string, prefix, suffix): - return (f"{prefix}{input_string}{suffix}",) - -#======提取标签之间 -class ExtractSubstring: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "input_string": ("STRING", {"multiline": True, "default": ""}), - "pattern": ("STRING", {"default": "标签始|标签尾"}), - }, - "optional": {}, - } - - RETURN_TYPES = ("STRING",) - FUNCTION = "extract_substring" - CATEGORY = "Meeeyo/String" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def extract_substring(self, input_string, pattern): - try: - parts = pattern.split('|') - start_str = parts[0] - end_str = parts[1] if len(parts) > 1 and parts[1].strip() else "\n" - - start_index = input_string.index(start_str) + len(start_str) - - end_index = input_string.find(end_str, start_index) - if end_index == -1: - end_index = input_string.find("\n", start_index) - if end_index == -1: - end_index = len(input_string) - - extracted = input_string[start_index:end_index] - - lines = extracted.splitlines() - non_empty_lines = [line for line in lines if line.strip()] - result = '\n'.join(non_empty_lines) - - return (result,) - except ValueError: - return ("",) - -#======按数字范围提取 -class ExtractSubstringByIndices: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "input_string": ("STRING", {"default": ""}), - "indices": ("STRING", {"default": "2-6"}), - "direction": (["从前面", "从后面"], {"default": "从前面"}), - }, - "optional": {}, - } - - RETURN_TYPES = ("STRING",) - FUNCTION = "extract_substring_by_indices" - CATEGORY = "Meeeyo/String" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def extract_substring_by_indices(self, input_string, indices, direction): - try: - if '-' in indices: - start_index, end_index = map(int, indices.split('-')) - else: - start_index = end_index = int(indices) - - start_index -= 1 - end_index -= 1 - - if start_index < 0 or start_index >= len(input_string): - return ("",) - - if end_index < 0 or end_index >= len(input_string): - end_index = len(input_string) - 1 - - if start_index > end_index: - start_index, end_index = end_index, start_index - - if direction == "从前面": - return (input_string[start_index:end_index + 1],) - elif direction == "从后面": - start_index = len(input_string) - start_index - 1 - end_index = len(input_string) - end_index - 1 - if start_index > end_index: - start_index, end_index = end_index, start_index - return (input_string[start_index:end_index + 1],) - except ValueError: - return ("",) - -#======分隔符拆分两边 -class SplitStringByDelimiter: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "input_string": ("STRING", {"default": "文本|内容"}), - "delimiter": ("STRING", {"default": "|"}), - }, - "optional": {}, - } - - RETURN_TYPES = ("STRING", "STRING") - FUNCTION = "split_string_by_delimiter" - CATEGORY = "Meeeyo/String" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def split_string_by_delimiter(self, input_string, delimiter): - parts = input_string.split(delimiter, 1) - if len(parts) == 2: - return (parts[0], parts[1]) - else: - return (input_string, "") - -#======常规处理字符 -class ProcessString: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "input_string": ("STRING", {"multiline": True, "default": ""}), - "option": (["不改变", "取数字", "取字母", "转大写", "转小写", "取中文", "去标点", "去换行", "去空行", "去空格", "去格式", "统计字数"], {"default": "不改变"}), - }, - "optional": {}, - } - - RETURN_TYPES = ("STRING",) - FUNCTION = "process_string" - CATEGORY = "Meeeyo/String" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def process_string(self, input_string, option): - if option == "取数字": - result = ''.join(re.findall(r'\d', input_string)) - elif option == "取字母": - result = ''.join(filter(lambda char: char.isalpha() and not self.is_chinese(char), input_string)) - elif option == "转大写": - result = input_string.upper() - elif option == "转小写": - result = input_string.lower() - elif option == "取中文": - result = ''.join(filter(self.is_chinese, input_string)) - elif option == "去标点": - result = re.sub(r'[^\w\s\u4e00-\u9fff]', '', input_string) - elif option == "去换行": - result = input_string.replace('\n', '') - elif option == "去空行": - result = '\n'.join(filter(lambda line: line.strip(), input_string.splitlines())) - elif option == "去空格": - result = input_string.replace(' ', '') - elif option == "去格式": - result = re.sub(r'\s+', '', input_string) - elif option == "统计字数": - result = str(len(input_string)) - else: - result = input_string - - return (result,) - - @staticmethod - def is_chinese(char): - return '\u4e00' <= char <= '\u9fff' - -#======提取前后字符 -class ExtractBeforeAfter: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "input_string": ("STRING", {"default": ""}), - "pattern": ("STRING", {"default": "标签符"}), - "position": (["保留最初之前", "保留最初之后", "保留最后之前", "保留最后之后"], {"default": "保留最初之前"}), - "include_delimiter": ("BOOLEAN", {"default": False}), - }, - "optional": {}, - } - - RETURN_TYPES = ("STRING",) - FUNCTION = "extract_before_after" - CATEGORY = "Meeeyo/String" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def extract_before_after(self, input_string, pattern, position, include_delimiter): - if position == "保留最初之前": - index = input_string.find(pattern) - if index != -1: - result = input_string[:index + len(pattern) if include_delimiter else index] - return (result,) - elif position == "保留最初之后": - index = input_string.find(pattern) - if index != -1: - result = input_string[index:] if include_delimiter else input_string[index + len(pattern):] - return (result,) - elif position == "保留最后之前": - index = input_string.rfind(pattern) - if index != -1: - result = input_string[:index + len(pattern) if include_delimiter else index] - return (result,) - elif position == "保留最后之后": - index = input_string.rfind(pattern) - if index != -1: - result = input_string[index:] if include_delimiter else input_string[index + len(pattern):] - return (result,) - return ("",) - - -#======替换第n次出现 -class ReplaceNthOccurrence: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "original_text": ("STRING", {"multiline": True, "default": ""}), - "occurrence": ("INT", {"default": 1, "min": 0}), - "search_str": ("STRING", {"default": "替换前字符"}), - "replace_str": ("STRING", {"default": "替换后字符"}), - }, - "optional": {}, - } - - RETURN_TYPES = ("STRING",) - FUNCTION = "replace_nth_occurrence" - CATEGORY = "Meeeyo/String" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def replace_nth_occurrence(self, original_text, occurrence, search_str, replace_str): - if occurrence == 0: - result = original_text.replace(search_str, replace_str) - else: - def replace_nth_match(match): - nonlocal occurrence - occurrence -= 1 - return replace_str if occurrence == 0 else match.group(0) - - result = re.sub(re.escape(search_str), replace_nth_match, original_text, count=occurrence) - - return (result,) - - -#======多次出现依次替换 -class ReplaceMultiple: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "original_text": ("STRING", {"multiline": True, "default": ""}), - "replacement_rule": ("STRING", {"default": ""}), - }, - "optional": {}, - } - - RETURN_TYPES = ("STRING",) - FUNCTION = "replace_multiple" - CATEGORY = "Meeeyo/String" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def replace_multiple(self, original_text, replacement_rule): - try: - search_str, replacements = replacement_rule.split('|') - replacements = [rep for rep in replacements.split(',') if rep] - - def replace_match(match): - nonlocal replacements - if replacements: - return replacements.pop(0) - return match.group(0) - - result = re.sub(re.escape(search_str), replace_match, original_text) - - return (result,) - except ValueError: - return ("",) - -#======批量替换字符 -class BatchReplaceStrings: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "original_text": ("STRING", {"multiline": False, "default": "文本内容"}), - "replacement_rules": ("STRING", {"multiline": True, "default": ""}), - }, - "optional": {}, - } - - RETURN_TYPES = ("STRING",) - FUNCTION = "batch_replace_strings" - CATEGORY = "Meeeyo/String" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def batch_replace_strings(self, original_text, replacement_rules): - rules = replacement_rules.strip().split('\n') - for rule in rules: - if '|' in rule: - search_strs, replace_str = rule.split('|', 1) - - search_strs = search_strs.replace("\\n", "\n") - replace_str = replace_str.replace("\\n", "\n") - - search_strs = search_strs.split(',') - - for search_str in search_strs: - original_text = original_text.replace(search_str, replace_str) - return (original_text,) - - -#======随机行内容 -class RandomLineFromText: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "input_text": ("STRING", {"multiline": True, "default": ""}), - }, - "optional": {}, - } - - RETURN_TYPES = ("STRING",) - FUNCTION = "get_random_line" - CATEGORY = "Meeeyo/String" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def IS_CHANGED(): - return float("NaN") - - def get_random_line(self, input_text): - lines = input_text.strip().splitlines() - if not lines: - return ("",) - return (random.choice(lines),) - -#======判断是否包含字符 -class CheckSubstringPresence: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "input_text": ("STRING", {"default": "文本内容"}), - "substring": ("STRING", {"default": "查找符1|查找符2"}), - "mode": (["同时满足", "任意满足"], {"default": "任意满足"}), - }, - "optional": {}, - } - - RETURN_TYPES = ("INT",) - FUNCTION = "check_substring_presence" - CATEGORY = "Meeeyo/String" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def check_substring_presence(self, input_text, substring, mode): - substrings = substring.split('|') - - if mode == "同时满足": - for sub in substrings: - if sub not in input_text: - return (0,) - return (1,) - elif mode == "任意满足": - for sub in substrings: - if sub in input_text: - return (1,) - return (0,) - -#======段落每行添加前后缀 -class AddPrefixSuffixToLines: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "input_text": ("STRING", {"multiline": True, "default": ""}), - "prefix_suffix": ("STRING", {"default": "前缀符|后缀符"}), - }, - "optional": {}, - } - - RETURN_TYPES = ("STRING",) - FUNCTION = "add_prefix_suffix_to_lines" - CATEGORY = "Meeeyo/String" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def add_prefix_suffix_to_lines(self, prefix_suffix, input_text): - try: - prefix, suffix = prefix_suffix.split('|') - lines = input_text.splitlines() - modified_lines = [f"{prefix}{line}{suffix}" for line in lines] - result = '\n'.join(modified_lines) - return (result,) - except ValueError: - return ("",) - -#======段落提取指定索引行 -class ExtractAndCombineLines: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "input_text": ("STRING", {"multiline": True, "default": ""}), - "line_indices": ("STRING", {"default": "2-3"}), - }, - "optional": {}, - } - - RETURN_TYPES = ("STRING",) - FUNCTION = "extract_and_combine_lines" - CATEGORY = "Meeeyo/String" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def extract_and_combine_lines(self, input_text, line_indices): - try: - lines = input_text.splitlines() - result_lines = [] - - if '-' in line_indices: - start, end = map(int, line_indices.split('-')) - start = max(1, start) - end = min(len(lines), end) - result_lines = lines[start - 1:end] - else: - indices = map(int, line_indices.split('|')) - for index in indices: - if 1 <= index <= len(lines): - result_lines.append(lines[index - 1]) - - result = '\n'.join(result_lines) - return (result,) - except ValueError: - return ("",) - - -#======段落提取或移除字符行 -class FilterLinesBySubstrings: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "input_text": ("STRING", {"multiline": True, "default": ""}), - "substrings": ("STRING", {"default": "查找符1|查找符2"}), - "action": (["保留", "移除"], {"default": "保留"}), - }, - "optional": {}, - } - - RETURN_TYPES = ("STRING",) - FUNCTION = "filter_lines_by_substrings" - CATEGORY = "Meeeyo/String" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def filter_lines_by_substrings(self, input_text, substrings, action): - lines = input_text.splitlines() - substring_list = substrings.split('|') - result_lines = [] - - for line in lines: - contains_substring = any(substring in line for substring in substring_list) - if (action == "保留" and contains_substring) or (action == "移除" and not contains_substring): - result_lines.append(line) - - non_empty_lines = [line for line in result_lines if line.strip()] - result = '\n'.join(non_empty_lines) - return (result,) - - -#======根据字数范围过滤文本行 -class FilterLinesByWordCount: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "input_text": ("STRING", {"multiline": True, "default": ""}), - "word_count_range": ("STRING", {"default": "2-10"}), - }, - "optional": {}, - } - - RETURN_TYPES = ("STRING",) - FUNCTION = "filter_lines_by_word_count" - CATEGORY = "Meeeyo/String" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def filter_lines_by_word_count(self, input_text, word_count_range): - try: - lines = input_text.splitlines() - result_lines = [] - - if '-' in word_count_range: - min_count, max_count = map(int, word_count_range.split('-')) - result_lines = [line for line in lines if min_count <= len(line) <= max_count] - - result = '\n'.join(result_lines) - return (result,) - except ValueError: - return ("",) - - -#======按序号提取分割文本 -class SplitAndExtractText: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "input_text": ("STRING", {"multiline": True, "default": ""}), - "delimiter": ("STRING", {"default": "分隔符"}), - "index": ("INT", {"default": 1, "min": 1}), - "order": (["顺序", "倒序"], {"default": "顺序"}), - "include_delimiter": ("BOOLEAN", {"default": False}), - }, - "optional": {}, - } - - RETURN_TYPES = ("STRING",) - FUNCTION = "split_and_extract" - CATEGORY = "Meeeyo/String" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def split_and_extract(self, input_text, delimiter, index, order, include_delimiter): - try: - if not delimiter: - parts = input_text.splitlines() - else: - parts = input_text.split(delimiter) - - if order == "倒序": - parts = parts[::-1] - - if index > 0 and index <= len(parts): - selected_part = parts[index - 1] - - if include_delimiter and delimiter: - if order == "顺序": - if index > 1: - selected_part = delimiter + selected_part - if index < len(parts): - selected_part += delimiter - elif order == "倒序": - if index > 1: - selected_part += delimiter - if index < len(parts): - selected_part = delimiter + selected_part - - lines = selected_part.splitlines() - non_empty_lines = [line for line in lines if line.strip()] - result = '\n'.join(non_empty_lines) - return (result,) - else: - return ("",) - except ValueError: - return ("",) - -#======文本出现次数 -class CountOccurrences: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "input_text": ("STRING", {"multiline": True, "default": ""}), - "char": ("STRING", {"default": "查找符"}), - }, - "optional": {}, - } - - RETURN_TYPES = ("INT", "STRING") - FUNCTION = "count_text_segments" - CATEGORY = "Meeeyo/String" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def count_text_segments(self, input_text, char): - try: - if char == "\\n": - lines = [line for line in input_text.splitlines() if line.strip()] - count = len(lines) - else: - count = input_text.count(char) - return (count, str(count)) - except ValueError: - return (0, "0") - -#======文本拆分:根据索引和特定字符获取多行文本中的行内容 -class ExtractLinesByIndex: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "input_text": ("STRING", {"multiline": True, "default": ""}), - "delimiter": ("STRING", {"default": "标签符"}), - "index": ("INT", {"default": 1, "min": 1}), - }, - "optional": {}, - } - - RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING") - FUNCTION = "extract_lines_by_index" - CATEGORY = "Meeeyo/String" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - OUTPUT_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING") - OUTPUT_NAMES = ("文本1", "文本2", "文本3", "文本4", "文本5") - - def extract_lines_by_index(self, input_text, index, delimiter): - try: - if delimiter == "" or delimiter == "\n": - lines = input_text.splitlines() - else: - lines = input_text.split(delimiter) - - result_lines = [] - - for i in range(index - 1, index + 4): - if 0 <= i < len(lines): - result_lines.append(lines[i]) - else: - result_lines.append("") - - return tuple(result_lines) - except ValueError: - return ("", "", "", "", "") - -#======提取特定行 -class ExtractSpecificLines: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "input_text": ("STRING", {"multiline": True, "default": ""}), - "line_indices": ("STRING", {"default": "1|2"}), - "split_char": ("STRING", {"default": "\n"}), - }, - "optional": {}, - } - - RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING", "STRING") - FUNCTION = "extract_specific_lines" - CATEGORY = "Meeeyo/String" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def extract_specific_lines(self, input_text, line_indices, split_char): - if not split_char or split_char == "\n": - lines = input_text.split('\n') - else: - lines = input_text.split(split_char) - - indices = [int(index) - 1 for index in line_indices.split('|') if index.isdigit()] - - results = [] - for index in indices: - if 0 <= index < len(lines): - results.append(lines[index]) - else: - results.append("") - - while len(results) < 5: - results.append("") - - non_empty_results = [result for result in results[:5] if result.strip()] - if not split_char or split_char == "\n": - combined_result = '\n'.join(non_empty_results) - else: - combined_result = split_char.join(non_empty_results) - - return tuple(results[:5] + [combined_result]) - -#======删除标签内的内容 -class RemoveContentBetweenChars: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "input_text": ("STRING", {"multiline": True, "default": ""}), - "chars": ("STRING", {"default": "(|)"}), - }, - "optional": {}, - } - - RETURN_TYPES = ("STRING",) - FUNCTION = "remove_content_between_chars" - CATEGORY = "Meeeyo/String" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def remove_content_between_chars(self, input_text, chars): - try: - if len(chars) == 3 and chars[1] == '|': - start_char, end_char = chars[0], chars[2] - else: - return input_text - - pattern = re.escape(start_char) + '.*?' + re.escape(end_char) - result = re.sub(pattern, '', input_text) - - return (result,) - except ValueError: - return ("",) - -#======随机打乱 -class ShuffleTextLines: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "input_text": ("STRING", {"multiline": True, "default": ""}), - "delimiter": ("STRING", {"default": "分隔符"}), - }, - "optional": {}, - } - - RETURN_TYPES = ("STRING",) - FUNCTION = "shuffle_text_lines" - CATEGORY = "Meeeyo/String" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def IS_CHANGED(): - return float("NaN") - - def shuffle_text_lines(self, input_text, delimiter): - if delimiter == "": - lines = input_text.splitlines() - elif delimiter == "\n": - lines = input_text.split("\n") - else: - lines = input_text.split(delimiter) - - lines = [line for line in lines if line.strip()] - - random.shuffle(lines) - - if delimiter == "": - result = "\n".join(lines) - elif delimiter == "\n": - result = "\n".join(lines) - else: - result = delimiter.join(lines) - - return (result,) - -#======判断返回内容 -class ConditionalTextOutput: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "original_content": ("STRING", {"multiline": True, "default": ""}), - "check_text": ("STRING", {"default": "查找字符"}), - "text_if_exists": ("STRING", {"default": "存在返回内容"}), - "text_if_not_exists": ("STRING", {"default": "不存在返回内容"}), - }, - "optional": {}, - } - - RETURN_TYPES = ("STRING",) - FUNCTION = "conditional_text_output" - CATEGORY = "Meeeyo/String" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def conditional_text_output(self, original_content, check_text, text_if_exists, text_if_not_exists): - if not check_text: - return ("",) - - if check_text in original_content: - return (text_if_exists,) - else: - return (text_if_not_exists,) - - -#======文本按条件判断 -class TextConditionCheck: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "original_content": ("STRING", {"multiline": True, "default": ""}), # 输入多行文本 - "length_condition": ("STRING", {"default": "3-6"}), - "frequency_condition": ("STRING", {"default": ""}), - }, - "optional": {}, - } - - RETURN_TYPES = ("INT", "STRING") - FUNCTION = "text_condition_check" - CATEGORY = "Meeeyo/String" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def text_condition_check(self, original_content, length_condition, frequency_condition): - length_valid = self.check_length_condition(original_content, length_condition) - - frequency_valid = self.check_frequency_condition(original_content, frequency_condition) - - if length_valid and frequency_valid: - return (1, "1") - else: - return (0, "0") - - def check_length_condition(self, content, condition): - if '-' in condition: - start, end = map(int, condition.split('-')) - return start <= len(content) <= end - else: - target_length = int(condition) - return len(content) == target_length - - def check_frequency_condition(self, content, condition): - conditions = condition.split('|') - for cond in conditions: - char, count = cond.split(',') - if content.count(char) != int(count): - return False - return True - - -#======文本组合 -class TextConcatenation: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "original_text": ("STRING", {"multiline": True, "default": ""}), - "concatenation_rules": ("STRING", {"multiline": True, "default": ""}), - "split_char": ("STRING", {"default": ""}), - }, - "optional": {}, - } - - RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING") - FUNCTION = "text_concatenation" - CATEGORY = "Meeeyo/String" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def text_concatenation(self, original_text, concatenation_rules, split_char): - if split_char: - original_lines = [line.strip() for line in original_text.split(split_char) if line.strip()] - else: - original_lines = [line.strip() for line in original_text.split('\n') if line.strip()] - - rules_lines = [line.strip() for line in concatenation_rules.split('\n') if line.strip()] - - outputs = [] - for rule in rules_lines[:5]: - result = rule - for i, line in enumerate(original_lines, start=1): - result = result.replace(f"[{i}]", line) - outputs.append(result) - - while len(outputs) < 5: - outputs.append("") - - return tuple(outputs) - -#======提取多层指定数据 -class ExtractSpecificData: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "input_text": ("STRING", {"multiline": True, "default": ""}), - "rule1": ("STRING", {"default": "[3],@|2"}), - "rule2": ("STRING", {"default": "三,【|】"}), - }, - "optional": {}, - } - - RETURN_TYPES = ("STRING",) - FUNCTION = "extract_specific_data" - CATEGORY = "Meeeyo/String" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def extract_specific_data(self, input_text, rule1, rule2): - if rule1.strip(): - return self.extract_by_rule1(input_text, rule1) - else: - return self.extract_by_rule2(input_text, rule2) - - def extract_by_rule1(self, input_text, rule): - try: - line_rule, split_rule = rule.split(',') - split_char, group_index = split_rule.split('|') - group_index = int(group_index) - 1 - except ValueError: - return ("",) - - lines = input_text.split('\n') - - if line_rule.startswith('[') and line_rule.endswith(']'): - try: - line_index = int(line_rule[1:-1]) - 1 - if 0 <= line_index < len(lines): - target_line = lines[line_index] - else: - return ("",) - except ValueError: - return ("",) - else: - target_lines = [line for line in lines if line_rule in line] - if not target_lines: - return ("",) - target_line = target_lines[0] - - parts = target_line.split(split_char) - if 0 <= group_index < len(parts): - return (parts[group_index],) - return ("",) - - def extract_by_rule2(self, input_text, rule): - try: - line_rule, tags = rule.split(',') - start_tag, end_tag = tags.split('|') - except ValueError: - return ("",) - - lines = input_text.split('\n') - - if line_rule.startswith('[') and line_rule.endswith(']'): - try: - line_index = int(line_rule[1:-1]) - 1 - if 0 <= line_index < len(lines): - target_line = lines[line_index] - else: - return ("",) - except ValueError: - return ("",) - else: - target_lines = [line for line in lines if line_rule in line] - if not target_lines: - return ("",) - target_line = target_lines[0] - - start_index = target_line.find(start_tag) - end_index = target_line.find(end_tag, start_index) - if start_index != -1 and end_index != -1: - return (target_line[start_index + len(start_tag):end_index],) - return ("",) - - -#======指定字符行参数 -class FindFirstLineContent: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "input_text": ("STRING", {"multiline": True, "default": ""}), - "target_char": ("STRING", {"default": "数据a"}), - }, - "optional": {}, - } - - RETURN_TYPES = ("STRING",) - FUNCTION = "find_first_line_content" - CATEGORY = "Meeeyo/String" - DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" - - def find_first_line_content(self, input_text, target_char): - try: - lines = input_text.splitlines() - - for line in lines: - if target_char in line: - start_index = line.index(target_char) - result = line[start_index + len(target_char):] - return (result,) - - return ("",) - except Exception as e: +import datetime +import random +import secrets +import requests +import string +import math +import re + + +#======文本输入 +class SingleTextInput: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "text": ("STRING", {"default": "", "multiline": True}), + } + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "process_input" + CATEGORY = "Meeeyo/String" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + OUTPUT_NODE = False + + def process_input(self, text): + return (text,) + + +#======多参数输入 +class MultiParamInputNode: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "text1": ("STRING", {"default": "", "multiline": True}), # 第一个多行文本输入框 + "text2": ("STRING", {"default": "", "multiline": True}), # 第二个多行文本输入框 + "int1": ("INT", {"default": 0, "min": -1000000, "max": 1000000}), # 第一个整数输入框 + "int2": ("INT", {"default": 0, "min": -1000000, "max": 1000000}), # 第二个整数输入框 + } + } + + RETURN_TYPES = ("STRING", "STRING", "INT", "INT") # 返回两个字符串和两个整数 + FUNCTION = "process_inputs" + CATEGORY = "Meeeyo/String" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + OUTPUT_NODE = False + + def process_inputs(self, text1, text2, int1, int2): + return (text1, text2, int1, int2) + + +#======添加前后缀 +class AddPrefixSuffix: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_string": ("STRING", {"default": ""}), + "prefix": ("STRING", {"default": "前缀符"}), + "suffix": ("STRING", {"default": "后缀符"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "add_prefix_suffix" + CATEGORY = "Meeeyo/String" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def add_prefix_suffix(self, input_string, prefix, suffix): + return (f"{prefix}{input_string}{suffix}",) + +#======提取标签之间 +class ExtractSubstring: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_string": ("STRING", {"multiline": True, "default": ""}), + "pattern": ("STRING", {"default": "标签始|标签尾"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "extract_substring" + CATEGORY = "Meeeyo/String" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def extract_substring(self, input_string, pattern): + try: + parts = pattern.split('|') + start_str = parts[0] + end_str = parts[1] if len(parts) > 1 and parts[1].strip() else "\n" + + start_index = input_string.index(start_str) + len(start_str) + + end_index = input_string.find(end_str, start_index) + if end_index == -1: + end_index = input_string.find("\n", start_index) + if end_index == -1: + end_index = len(input_string) + + extracted = input_string[start_index:end_index] + + lines = extracted.splitlines() + non_empty_lines = [line for line in lines if line.strip()] + result = '\n'.join(non_empty_lines) + + return (result,) + except ValueError: + return ("",) + +#======按数字范围提取 +class ExtractSubstringByIndices: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_string": ("STRING", {"default": ""}), + "indices": ("STRING", {"default": "2-6"}), + "direction": (["从前面", "从后面"], {"default": "从前面"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "extract_substring_by_indices" + CATEGORY = "Meeeyo/String" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def extract_substring_by_indices(self, input_string, indices, direction): + try: + if '-' in indices: + start_index, end_index = map(int, indices.split('-')) + else: + start_index = end_index = int(indices) + + start_index -= 1 + end_index -= 1 + + if start_index < 0 or start_index >= len(input_string): + return ("",) + + if end_index < 0 or end_index >= len(input_string): + end_index = len(input_string) - 1 + + if start_index > end_index: + start_index, end_index = end_index, start_index + + if direction == "从前面": + return (input_string[start_index:end_index + 1],) + elif direction == "从后面": + start_index = len(input_string) - start_index - 1 + end_index = len(input_string) - end_index - 1 + if start_index > end_index: + start_index, end_index = end_index, start_index + return (input_string[start_index:end_index + 1],) + except ValueError: + return ("",) + +#======分隔符拆分两边 +class SplitStringByDelimiter: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_string": ("STRING", {"default": "文本|内容"}), + "delimiter": ("STRING", {"default": "|"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING", "STRING") + FUNCTION = "split_string_by_delimiter" + CATEGORY = "Meeeyo/String" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def split_string_by_delimiter(self, input_string, delimiter): + parts = input_string.split(delimiter, 1) + if len(parts) == 2: + return (parts[0], parts[1]) + else: + return (input_string, "") + +#======常规处理字符 +class ProcessString: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_string": ("STRING", {"multiline": True, "default": ""}), + "option": (["不改变", "取数字", "取字母", "转大写", "转小写", "取中文", "去标点", "去换行", "去空行", "去空格", "去格式", "统计字数"], {"default": "不改变"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "process_string" + CATEGORY = "Meeeyo/String" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def process_string(self, input_string, option): + if option == "取数字": + result = ''.join(re.findall(r'\d', input_string)) + elif option == "取字母": + result = ''.join(filter(lambda char: char.isalpha() and not self.is_chinese(char), input_string)) + elif option == "转大写": + result = input_string.upper() + elif option == "转小写": + result = input_string.lower() + elif option == "取中文": + result = ''.join(filter(self.is_chinese, input_string)) + elif option == "去标点": + result = re.sub(r'[^\w\s\u4e00-\u9fff]', '', input_string) + elif option == "去换行": + result = input_string.replace('\n', '') + elif option == "去空行": + result = '\n'.join(filter(lambda line: line.strip(), input_string.splitlines())) + elif option == "去空格": + result = input_string.replace(' ', '') + elif option == "去格式": + result = re.sub(r'\s+', '', input_string) + elif option == "统计字数": + result = str(len(input_string)) + else: + result = input_string + + return (result,) + + @staticmethod + def is_chinese(char): + return '\u4e00' <= char <= '\u9fff' + +#======提取前后字符 +class ExtractBeforeAfter: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_string": ("STRING", {"default": ""}), + "pattern": ("STRING", {"default": "标签符"}), + "position": (["保留最初之前", "保留最初之后", "保留最后之前", "保留最后之后"], {"default": "保留最初之前"}), + "include_delimiter": ("BOOLEAN", {"default": False}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "extract_before_after" + CATEGORY = "Meeeyo/String" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def extract_before_after(self, input_string, pattern, position, include_delimiter): + if position == "保留最初之前": + index = input_string.find(pattern) + if index != -1: + result = input_string[:index + len(pattern) if include_delimiter else index] + return (result,) + elif position == "保留最初之后": + index = input_string.find(pattern) + if index != -1: + result = input_string[index:] if include_delimiter else input_string[index + len(pattern):] + return (result,) + elif position == "保留最后之前": + index = input_string.rfind(pattern) + if index != -1: + result = input_string[:index + len(pattern) if include_delimiter else index] + return (result,) + elif position == "保留最后之后": + index = input_string.rfind(pattern) + if index != -1: + result = input_string[index:] if include_delimiter else input_string[index + len(pattern):] + return (result,) + return ("",) + + +#======替换第n次出现 +class ReplaceNthOccurrence: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "original_text": ("STRING", {"multiline": True, "default": ""}), + "occurrence": ("INT", {"default": 1, "min": 0}), + "search_str": ("STRING", {"default": "替换前字符"}), + "replace_str": ("STRING", {"default": "替换后字符"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "replace_nth_occurrence" + CATEGORY = "Meeeyo/String" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def replace_nth_occurrence(self, original_text, occurrence, search_str, replace_str): + if occurrence == 0: + result = original_text.replace(search_str, replace_str) + else: + def replace_nth_match(match): + nonlocal occurrence + occurrence -= 1 + return replace_str if occurrence == 0 else match.group(0) + + result = re.sub(re.escape(search_str), replace_nth_match, original_text, count=occurrence) + + return (result,) + + +#======多次出现依次替换 +class ReplaceMultiple: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "original_text": ("STRING", {"multiline": True, "default": ""}), + "replacement_rule": ("STRING", {"default": ""}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "replace_multiple" + CATEGORY = "Meeeyo/String" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def replace_multiple(self, original_text, replacement_rule): + try: + search_str, replacements = replacement_rule.split('|') + replacements = [rep for rep in replacements.split(',') if rep] + + def replace_match(match): + nonlocal replacements + if replacements: + return replacements.pop(0) + return match.group(0) + + result = re.sub(re.escape(search_str), replace_match, original_text) + + return (result,) + except ValueError: + return ("",) + +#======批量替换字符 +class BatchReplaceStrings: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "original_text": ("STRING", {"multiline": False, "default": "文本内容"}), + "replacement_rules": ("STRING", {"multiline": True, "default": ""}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "batch_replace_strings" + CATEGORY = "Meeeyo/String" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def batch_replace_strings(self, original_text, replacement_rules): + rules = replacement_rules.strip().split('\n') + for rule in rules: + if '|' in rule: + search_strs, replace_str = rule.split('|', 1) + + search_strs = search_strs.replace("\\n", "\n") + replace_str = replace_str.replace("\\n", "\n") + + search_strs = search_strs.split(',') + + for search_str in search_strs: + original_text = original_text.replace(search_str, replace_str) + return (original_text,) + + +#======随机行内容 +class RandomLineFromText: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_text": ("STRING", {"multiline": True, "default": ""}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "get_random_line" + CATEGORY = "Meeeyo/String" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def IS_CHANGED(): + return float("NaN") + + def get_random_line(self, input_text): + lines = input_text.strip().splitlines() + if not lines: + return ("",) + return (random.choice(lines),) + +#======判断是否包含字符 +class CheckSubstringPresence: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_text": ("STRING", {"default": "文本内容"}), + "substring": ("STRING", {"default": "查找符1|查找符2"}), + "mode": (["同时满足", "任意满足"], {"default": "任意满足"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("INT",) + FUNCTION = "check_substring_presence" + CATEGORY = "Meeeyo/String" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def check_substring_presence(self, input_text, substring, mode): + substrings = substring.split('|') + + if mode == "同时满足": + for sub in substrings: + if sub not in input_text: + return (0,) + return (1,) + elif mode == "任意满足": + for sub in substrings: + if sub in input_text: + return (1,) + return (0,) + +#======段落每行添加前后缀 +class AddPrefixSuffixToLines: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_text": ("STRING", {"multiline": True, "default": ""}), + "prefix_suffix": ("STRING", {"default": "前缀符|后缀符"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "add_prefix_suffix_to_lines" + CATEGORY = "Meeeyo/String" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def add_prefix_suffix_to_lines(self, prefix_suffix, input_text): + try: + prefix, suffix = prefix_suffix.split('|') + lines = input_text.splitlines() + modified_lines = [f"{prefix}{line}{suffix}" for line in lines] + result = '\n'.join(modified_lines) + return (result,) + except ValueError: + return ("",) + +#======段落提取指定索引行 +class ExtractAndCombineLines: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_text": ("STRING", {"multiline": True, "default": ""}), + "line_indices": ("STRING", {"default": "2-3"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "extract_and_combine_lines" + CATEGORY = "Meeeyo/String" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def extract_and_combine_lines(self, input_text, line_indices): + try: + lines = input_text.splitlines() + result_lines = [] + + if '-' in line_indices: + start, end = map(int, line_indices.split('-')) + start = max(1, start) + end = min(len(lines), end) + result_lines = lines[start - 1:end] + else: + indices = map(int, line_indices.split('|')) + for index in indices: + if 1 <= index <= len(lines): + result_lines.append(lines[index - 1]) + + result = '\n'.join(result_lines) + return (result,) + except ValueError: + return ("",) + + +#======段落提取或移除字符行 +class FilterLinesBySubstrings: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_text": ("STRING", {"multiline": True, "default": ""}), + "substrings": ("STRING", {"default": "查找符1|查找符2"}), + "action": (["保留", "移除"], {"default": "保留"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "filter_lines_by_substrings" + CATEGORY = "Meeeyo/String" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def filter_lines_by_substrings(self, input_text, substrings, action): + lines = input_text.splitlines() + substring_list = substrings.split('|') + result_lines = [] + + for line in lines: + contains_substring = any(substring in line for substring in substring_list) + if (action == "保留" and contains_substring) or (action == "移除" and not contains_substring): + result_lines.append(line) + + non_empty_lines = [line for line in result_lines if line.strip()] + result = '\n'.join(non_empty_lines) + return (result,) + + +#======根据字数范围过滤文本行 +class FilterLinesByWordCount: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_text": ("STRING", {"multiline": True, "default": ""}), + "word_count_range": ("STRING", {"default": "2-10"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "filter_lines_by_word_count" + CATEGORY = "Meeeyo/String" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def filter_lines_by_word_count(self, input_text, word_count_range): + try: + lines = input_text.splitlines() + result_lines = [] + + if '-' in word_count_range: + min_count, max_count = map(int, word_count_range.split('-')) + result_lines = [line for line in lines if min_count <= len(line) <= max_count] + + result = '\n'.join(result_lines) + return (result,) + except ValueError: + return ("",) + + +#======按序号提取分割文本 +class SplitAndExtractText: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_text": ("STRING", {"multiline": True, "default": ""}), + "delimiter": ("STRING", {"default": "分隔符"}), + "index": ("INT", {"default": 1, "min": 1}), + "order": (["顺序", "倒序"], {"default": "顺序"}), + "include_delimiter": ("BOOLEAN", {"default": False}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "split_and_extract" + CATEGORY = "Meeeyo/String" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def split_and_extract(self, input_text, delimiter, index, order, include_delimiter): + try: + if not delimiter: + parts = input_text.splitlines() + else: + parts = input_text.split(delimiter) + + if order == "倒序": + parts = parts[::-1] + + if index > 0 and index <= len(parts): + selected_part = parts[index - 1] + + if include_delimiter and delimiter: + if order == "顺序": + if index > 1: + selected_part = delimiter + selected_part + if index < len(parts): + selected_part += delimiter + elif order == "倒序": + if index > 1: + selected_part += delimiter + if index < len(parts): + selected_part = delimiter + selected_part + + lines = selected_part.splitlines() + non_empty_lines = [line for line in lines if line.strip()] + result = '\n'.join(non_empty_lines) + return (result,) + else: + return ("",) + except ValueError: + return ("",) + +#======文本出现次数 +class CountOccurrences: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_text": ("STRING", {"multiline": True, "default": ""}), + "char": ("STRING", {"default": "查找符"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("INT", "STRING") + FUNCTION = "count_text_segments" + CATEGORY = "Meeeyo/String" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def count_text_segments(self, input_text, char): + try: + if char == "\\n": + lines = [line for line in input_text.splitlines() if line.strip()] + count = len(lines) + else: + count = input_text.count(char) + return (count, str(count)) + except ValueError: + return (0, "0") + +#======文本拆分:根据索引和特定字符获取多行文本中的行内容 +class ExtractLinesByIndex: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_text": ("STRING", {"multiline": True, "default": ""}), + "delimiter": ("STRING", {"default": "标签符"}), + "index": ("INT", {"default": 1, "min": 1}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING") + FUNCTION = "extract_lines_by_index" + CATEGORY = "Meeeyo/String" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + OUTPUT_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING") + OUTPUT_NAMES = ("文本1", "文本2", "文本3", "文本4", "文本5") + + def extract_lines_by_index(self, input_text, index, delimiter): + try: + if delimiter == "" or delimiter == "\n": + lines = input_text.splitlines() + else: + lines = input_text.split(delimiter) + + result_lines = [] + + for i in range(index - 1, index + 4): + if 0 <= i < len(lines): + result_lines.append(lines[i]) + else: + result_lines.append("") + + return tuple(result_lines) + except ValueError: + return ("", "", "", "", "") + +#======提取特定行 +class ExtractSpecificLines: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_text": ("STRING", {"multiline": True, "default": ""}), + "line_indices": ("STRING", {"default": "1|2"}), + "split_char": ("STRING", {"default": "\n"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING", "STRING") + FUNCTION = "extract_specific_lines" + CATEGORY = "Meeeyo/String" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def extract_specific_lines(self, input_text, line_indices, split_char): + if not split_char or split_char == "\n": + lines = input_text.split('\n') + else: + lines = input_text.split(split_char) + + indices = [int(index) - 1 for index in line_indices.split('|') if index.isdigit()] + + results = [] + for index in indices: + if 0 <= index < len(lines): + results.append(lines[index]) + else: + results.append("") + + while len(results) < 5: + results.append("") + + non_empty_results = [result for result in results[:5] if result.strip()] + if not split_char or split_char == "\n": + combined_result = '\n'.join(non_empty_results) + else: + combined_result = split_char.join(non_empty_results) + + return tuple(results[:5] + [combined_result]) + +#======删除标签内的内容 +class RemoveContentBetweenChars: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_text": ("STRING", {"multiline": True, "default": ""}), + "chars": ("STRING", {"default": "(|)"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "remove_content_between_chars" + CATEGORY = "Meeeyo/String" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def remove_content_between_chars(self, input_text, chars): + try: + if len(chars) == 3 and chars[1] == '|': + start_char, end_char = chars[0], chars[2] + else: + return input_text + + pattern = re.escape(start_char) + '.*?' + re.escape(end_char) + result = re.sub(pattern, '', input_text) + + return (result,) + except ValueError: + return ("",) + +#======随机打乱 +class ShuffleTextLines: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_text": ("STRING", {"multiline": True, "default": ""}), + "delimiter": ("STRING", {"default": "分隔符"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "shuffle_text_lines" + CATEGORY = "Meeeyo/String" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def IS_CHANGED(): + return float("NaN") + + def shuffle_text_lines(self, input_text, delimiter): + if delimiter == "": + lines = input_text.splitlines() + elif delimiter == "\n": + lines = input_text.split("\n") + else: + lines = input_text.split(delimiter) + + lines = [line for line in lines if line.strip()] + + random.shuffle(lines) + + if delimiter == "": + result = "\n".join(lines) + elif delimiter == "\n": + result = "\n".join(lines) + else: + result = delimiter.join(lines) + + return (result,) + +#======判断返回内容 +class ConditionalTextOutput: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "original_content": ("STRING", {"multiline": True, "default": ""}), + "check_text": ("STRING", {"default": "查找字符"}), + "text_if_exists": ("STRING", {"default": "存在返回内容"}), + "text_if_not_exists": ("STRING", {"default": "不存在返回内容"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "conditional_text_output" + CATEGORY = "Meeeyo/String" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def conditional_text_output(self, original_content, check_text, text_if_exists, text_if_not_exists): + if not check_text: + return ("",) + + if check_text in original_content: + return (text_if_exists,) + else: + return (text_if_not_exists,) + + +#======文本按条件判断 +class TextConditionCheck: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "original_content": ("STRING", {"multiline": True, "default": ""}), # 输入多行文本 + "length_condition": ("STRING", {"default": "3-6"}), + "frequency_condition": ("STRING", {"default": ""}), + }, + "optional": {}, + } + + RETURN_TYPES = ("INT", "STRING") + FUNCTION = "text_condition_check" + CATEGORY = "Meeeyo/String" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def text_condition_check(self, original_content, length_condition, frequency_condition): + length_valid = self.check_length_condition(original_content, length_condition) + + frequency_valid = self.check_frequency_condition(original_content, frequency_condition) + + if length_valid and frequency_valid: + return (1, "1") + else: + return (0, "0") + + def check_length_condition(self, content, condition): + if '-' in condition: + start, end = map(int, condition.split('-')) + return start <= len(content) <= end + else: + target_length = int(condition) + return len(content) == target_length + + def check_frequency_condition(self, content, condition): + conditions = condition.split('|') + for cond in conditions: + char, count = cond.split(',') + if content.count(char) != int(count): + return False + return True + + +#======文本组合 +class TextConcatenation: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "original_text": ("STRING", {"multiline": True, "default": ""}), + "concatenation_rules": ("STRING", {"multiline": True, "default": ""}), + "split_char": ("STRING", {"default": ""}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING") + FUNCTION = "text_concatenation" + CATEGORY = "Meeeyo/String" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def text_concatenation(self, original_text, concatenation_rules, split_char): + if split_char: + original_lines = [line.strip() for line in original_text.split(split_char) if line.strip()] + else: + original_lines = [line.strip() for line in original_text.split('\n') if line.strip()] + + rules_lines = [line.strip() for line in concatenation_rules.split('\n') if line.strip()] + + outputs = [] + for rule in rules_lines[:5]: + result = rule + for i, line in enumerate(original_lines, start=1): + result = result.replace(f"[{i}]", line) + outputs.append(result) + + while len(outputs) < 5: + outputs.append("") + + return tuple(outputs) + +#======提取多层指定数据 +class ExtractSpecificData: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_text": ("STRING", {"multiline": True, "default": ""}), + "rule1": ("STRING", {"default": "[3],@|2"}), + "rule2": ("STRING", {"default": "三,【|】"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "extract_specific_data" + CATEGORY = "Meeeyo/String" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def extract_specific_data(self, input_text, rule1, rule2): + if rule1.strip(): + return self.extract_by_rule1(input_text, rule1) + else: + return self.extract_by_rule2(input_text, rule2) + + def extract_by_rule1(self, input_text, rule): + try: + line_rule, split_rule = rule.split(',') + split_char, group_index = split_rule.split('|') + group_index = int(group_index) - 1 + except ValueError: + return ("",) + + lines = input_text.split('\n') + + if line_rule.startswith('[') and line_rule.endswith(']'): + try: + line_index = int(line_rule[1:-1]) - 1 + if 0 <= line_index < len(lines): + target_line = lines[line_index] + else: + return ("",) + except ValueError: + return ("",) + else: + target_lines = [line for line in lines if line_rule in line] + if not target_lines: + return ("",) + target_line = target_lines[0] + + parts = target_line.split(split_char) + if 0 <= group_index < len(parts): + return (parts[group_index],) + return ("",) + + def extract_by_rule2(self, input_text, rule): + try: + line_rule, tags = rule.split(',') + start_tag, end_tag = tags.split('|') + except ValueError: + return ("",) + + lines = input_text.split('\n') + + if line_rule.startswith('[') and line_rule.endswith(']'): + try: + line_index = int(line_rule[1:-1]) - 1 + if 0 <= line_index < len(lines): + target_line = lines[line_index] + else: + return ("",) + except ValueError: + return ("",) + else: + target_lines = [line for line in lines if line_rule in line] + if not target_lines: + return ("",) + target_line = target_lines[0] + + start_index = target_line.find(start_tag) + end_index = target_line.find(end_tag, start_index) + if start_index != -1 and end_index != -1: + return (target_line[start_index + len(start_tag):end_index],) + return ("",) + + +#======指定字符行参数 +class FindFirstLineContent: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_text": ("STRING", {"multiline": True, "default": ""}), + "target_char": ("STRING", {"default": "数据a"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "find_first_line_content" + CATEGORY = "Meeeyo/String" + DESCRIPTION = "如需更多帮助或商务需求(For tech and business support)+VX/WeChat: meeeyo" + + def find_first_line_content(self, input_text, target_char): + try: + lines = input_text.splitlines() + + for line in lines: + if target_char in line: + start_index = line.index(target_char) + result = line[start_index + len(target_char):] + return (result,) + + return ("",) + except Exception as e: return (f"Error: {str(e)}",) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index b64b3e6..be147cd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -requests -ntplib -numpy -torch -Pillow -openpyxl +requests +ntplib +numpy +torch +Pillow +openpyxl chardet \ No newline at end of file