diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..c863dac --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,126 @@ +# ComfyUI-DD-Nodes Developer Guide + +## Architecture Overview + +This is a **ComfyUI extension** with hybrid Python/JavaScript architecture: +- **Backend**: Python nodes in `/node/` (image processing, AI workflows) +- **Frontend**: JavaScript extensions in `/extensions/` (UI enhancements, animations, APIs) +- **Localization**: Bilingual support via `/locales/zh/` and `/locales/en/` + +## Key Development Patterns + +### Adding New Nodes +1. Create Python class in `/node/` with ComfyUI conventions: + ```python + class YourNode: + @classmethod + def INPUT_TYPES(s): + return {"required": {"input": ("TYPE",)}} + + RETURN_TYPES = ("OUTPUT_TYPE",) + FUNCTION = "process" + CATEGORY = "🍺DD系列节点" + ``` + +2. Export in `NODE_CLASS_MAPPINGS = {"DD-YourNode": YourNode}` +3. Import in `__init__.py` and add to main `NODE_CLASS_MAPPINGS` +4. Add Chinese translations to `locales/zh/nodeDefs.json` + +### Frontend Extensions +Create extension in `/extensions/YourExt/`: +```javascript +import { app } from "/scripts/app.js"; + +app.registerExtension({ + name: "ComfyUI.YourExtension", + setup() { + // Initialization code + }, + beforeRegisterNodeDef(nodeType, nodeData, app) { + // Hook into node creation + } +}); +``` + +### Adding API Endpoints +In Python modules, use ComfyUI's PromptServer: +```python +from server import PromptServer + +@PromptServer.instance.routes.post("/your_api/endpoint") +async def your_handler(request): + return web.json_response({"success": True}) +``` + +## Critical Conventions + +### Canvas Monkey-Patching +When modifying ComfyUI's canvas behavior, **always preserve original methods**: +```javascript +const originalMethod = app.canvas.originalMethod; +app.canvas.originalMethod = function(...args) { + // Your enhancement + return originalMethod?.call(this, ...args); +}; +``` + +### Settings Integration +Use ComfyUI's built-in settings system: +```javascript +app.extensionManager.setting.get("YourExt.setting") ?? defaultValue +app.extensionManager.setting.onChange("YourExt.setting", callback) +``` + +### Theme Architecture +Themes must implement interface: `{init, applyTheme, flipCoin, cleanup, name}` +Register via: `registerTheme('theme-id', themeModule)` + +## Performance Patterns + +### Spatial Optimization +For canvas operations affecting many nodes, use spatial partitioning (see `StaticCircuitBoardStyle.js`): +- Build spatial grid index for O(1) lookups +- Use Bresenham line algorithm for collision detection +- Dynamic grid sizing based on node count + +### Lazy Loading +Extensions use singleton patterns: +```javascript +function getOrCreateInstance() { + if (!app.canvas._yourInstance) { + app.canvas._yourInstance = new YourClass(); + } + return app.canvas._yourInstance; +} +``` + +## File Structure Conventions + +``` +/node/ # Python backend nodes +/extensions/ # Frontend JavaScript modules + /YourExt/ + YourExtExt.js # Extension entry (imported in index.js) + YourExtCore.js # Core functionality + /styles/ # UI styling and utilities +/locales/zh/ # Chinese translations + nodeDefs.json # Node translations + main.json # UI translations +``` + +## Development Workflow + +1. **No build system** - Direct ES6 modules, hot-reload friendly +2. **Testing**: Restart ComfyUI to test Python changes, refresh for JS changes +3. **Dependencies**: Add to `requirements.txt`, use npm-style imports for JS +4. **Debugging**: Use browser DevTools for frontend, Python print/logging for backend + +## Integration Points + +- Node widgets auto-added via `beforeRegisterNodeDef` hooks +- Settings persist via ComfyUI's storage system +- API calls use `/scripts/api.js` from ComfyUI core +- Internationalization follows ComfyUI's i18n system +- Extension loading via `WEB_DIRECTORY = "extensions"` in `__init__.py` + +When adding features, follow existing patterns in `/extensions/Prompt_Manager/` (API integration) or `/extensions/Online_Animation/` (canvas enhancement) as reference implementations. diff --git a/README-EN.md b/README-EN.md index 521f253..de5e0c8 100644 --- a/README-EN.md +++ b/README-EN.md @@ -31,6 +31,9 @@ | **DD Image Size Limiter** | Smart image size limiter ensuring images are within specified maximum and minimum size ranges, preventing memory overflow and performance issues | ![Image Size Limiter Interface](https://github.com/user-attachments/assets/d2fac125-fad3-4f51-9b91-39d0be4c7753) | | **DD Switcher Series** | Includes condition switcher, latent switcher, model switcher and other switching nodes, simplifying workflows and improving processing flexibility | ![Switcher Series Interface](https://github.com/user-attachments/assets/54690c0c-3627-4970-9bc0-ef58ca4be2f7) | | **DD Video First Last Frame** | Professional video frame extraction tool that can precisely extract the first and last frames of videos, providing convenience for video processing workflows | ![Video First Last Frame](https://github.com/user-attachments/assets/243c4809-8c83-43a3-9c2b-768f16644ded) | +| **DD Image Splitter** | Intelligent image splitting tool that supports splitting images into multiple parts by custom ratios. Supports horizontal (left-right) or vertical (top-bottom) splitting, supports unequal ratio splitting (like 2:1:3), and can output specified split results | ![Image Splitter Interface](To be added) | +| **DD Aspect Ratio Selector** | Intelligent aspect ratio selection tool that provides recommended resolutions for different AI models (such as Qwen-image, Wan2.2). Supports three ratio categories: landscape, portrait, and square, automatically providing the most suitable size parameters for the model | ![Aspect Ratio Selector Interface](To be added) | +| **DD TXT File Merger** | Powerful text file merging tool that can recursively scan all TXT files in specified folders and subfolders, merging them into a single text content. Automatically adds file path identifiers and supports multiple text encoding formats | ![TXT File Merger Interface](To be added) | ## 🎯 Extension Functions Overview @@ -62,9 +65,11 @@ This extension now supports ComfyUI's native internationalization (i18n) system ## 📈 Version History ### Latest Version -- **v2.6.0** (2025-07-06) - - 🔧 Attempted to fix multi-system compatibility bugs in interface layout panel - - ➕ Added video first and last frame output node +- **v3.1.0** (2025-09-21) +- Added DD TXT File Merger node, supports recursive scanning of all TXT files in folders and subfolders and merging them into single text content + +- **v3.0.0** (2025-09-05) +- Added DD Image Splitter node, supports splitting images by custom ratios and outputting specified parts ### Important Updates - **v2.5.1** (2025-07-02) diff --git a/README.md b/README.md index 7f45ec2..7930c96 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,9 @@ | **DD 限制图像大小** | 智能图像尺寸限制器,确保图像在指定的最大和最小尺寸范围内,防止内存溢出和性能问题 | ![限制图像大小界面](https://github.com/user-attachments/assets/d2fac125-fad3-4f51-9b91-39d0be4c7753) | | **DD 切换器系列** | 包含条件切换器、Latent切换器、模型切换器等多种切换节点,简化工作流程,提高处理灵活性 | ![切换器系列界面](https://github.com/user-attachments/assets/54690c0c-3627-4970-9bc0-ef58ca4be2f7) | | **DD 视频首尾帧输出** | 专业的视频帧提取工具,可以精确提取视频的第一帧和最后一帧,为视频处理工作流提供便利 | ![首尾](https://github.com/user-attachments/assets/243c4809-8c83-43a3-9c2b-768f16644ded) | +| **DD 图像切分器** | 智能图像切分工具,支持按自定义比例将图像切分为多个部分。可选择左右切分或上下切分,支持不等比例切分(如2:1:3),并可输出指定位置的切分结果 | ![图像切分器界面](待添加) | +| **DD 比例选择器** | 智能的宽高比选择工具,针对不同AI模型(如Qwen-image、Wan2.2)提供推荐分辨率。支持横屏、竖屏、方形三种比例类别,自动提供最适合模型的尺寸参数 | ![比例选择器界面](待添加) | +| **DD TXT文件合并器** | 强大的文本文件合并工具,可以递归扫描指定文件夹及其子文件夹中的所有TXT文件,并将其合并为单个文本内容。自动添加文件路径标识,支持多种文本编码格式 | ![TXT合并器界面](待添加) | ## 🔧 安装方法 @@ -64,8 +67,11 @@ ## 📈 版本历史 ### 最新版本 +- **v3.1.0**(2025-09-21) +- 添加DD TXT文件合并器节点,支持递归扫描文件夹及子文件夹中的所有TXT文件并合并为单个文本内容 + - **v3.0.0**(2025-09-05) -- 添加比例切分器节点 +- 添加DD 图像切分器节点,支持按自定义比例切分图像并输出指定部分 - **v2.9.1**(2025-09-04) - 删除DD采样器节点,它已经不适用最新的CommyUI了 diff --git a/__init__.py b/__init__.py index dc36cee..28cea2e 100644 --- a/__init__.py +++ b/__init__.py @@ -12,6 +12,7 @@ from .node.condition_switcher import NODE_CLASS_MAPPINGS as CONDITION_SWITCHER_N from .node.latent_switcher import NODE_CLASS_MAPPINGS as LATENT_SWITCHER_NODES from .node.image_stroke import NODE_CLASS_MAPPINGS as IMAGE_STROKE_NODES from .node.image_splitter import NODE_CLASS_MAPPINGS as IMAGE_SPLITTER_NODES +from .node.txt_file_merger import NODE_CLASS_MAPPINGS as TXT_MERGER_NODES # 导入Qwen-MT翻译节点 from .extensions.Qwen_MT.nodes import NODE_CLASS_MAPPINGS as QWEN_MT_NODES @@ -39,6 +40,7 @@ NODE_CLASS_MAPPINGS = { **LATENT_SWITCHER_NODES, **IMAGE_STROKE_NODES, **IMAGE_SPLITTER_NODES, + **TXT_MERGER_NODES, **QWEN_MT_NODES, **ASPECT_RATIO_NODES, } @@ -59,6 +61,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "DD-LatentSwitcher": "DD Latent Switcher", "DD-ImageStroke": "DD Image Stroke", "DD-ImageSplitter": "DD Image Splitter", + "DD-TxtFileMerger": "DD TXT File Merger", "DD-QwenMTTranslator": "DD Qwen-MT", "DD-AspectRatioSelector": "DD Aspect Ratio Selector", } diff --git a/locales/en/nodeDefs.json b/locales/en/nodeDefs.json index 120a7da..b2073c8 100644 --- a/locales/en/nodeDefs.json +++ b/locales/en/nodeDefs.json @@ -382,5 +382,21 @@ "tooltip": "Extracted single frame image" } } + }, + "DD-TxtFileMerger": { + "display_name": "DD TXT File Merger", + "description": "Merge all TXT files from a folder and its subfolders into a single text content", + "inputs": { + "input_folder": { + "name": "Input Folder", + "tooltip": "Folder path containing TXT files, will recursively scan all subfolders" + } + }, + "outputs": { + "0": { + "name": "Merged Text", + "tooltip": "Complete merged text content with file path identifiers" + } + } } } diff --git a/locales/zh/nodeDefs.json b/locales/zh/nodeDefs.json index bdb5087..ba18553 100644 --- a/locales/zh/nodeDefs.json +++ b/locales/zh/nodeDefs.json @@ -410,5 +410,21 @@ "tooltip": "输出的图像高度" } } + }, + "DD-TxtFileMerger": { + "display_name": "DD TXT文件合并器", + "description": "将指定文件夹及其子文件夹中的所有TXT文件合并成一个文本内容", + "inputs": { + "input_folder": { + "name": "输入文件夹", + "tooltip": "包含TXT文件的文件夹路径,将递归扫描所有子文件夹" + } + }, + "outputs": { + "0": { + "name": "合并文本", + "tooltip": "合并后的完整文本内容,包含文件路径标识" + } + } } } diff --git a/node/txt_file_merger.py b/node/txt_file_merger.py new file mode 100644 index 0000000..f439a1a --- /dev/null +++ b/node/txt_file_merger.py @@ -0,0 +1,103 @@ +import os +from pathlib import Path + + +class DDTxtFileMerger: + """ + DD TXT文件合并器 + 将指定文件夹及其子文件夹中的所有TXT文件合并成一个文本内容 + """ + + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_folder": ("STRING", { + "default": "", + "multiline": False, + "placeholder": "输入文件夹路径,例如: C:\\texts" + }), + } + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("merged_text",) + FUNCTION = "merge_txt_files" + CATEGORY = "🍺DD系列节点/文本处理" + + def merge_txt_files(self, input_folder): + """ + 合并文件夹中的所有TXT文件,返回合并后的文本内容 + """ + try: + # 验证输入文件夹 + if not input_folder or not os.path.exists(input_folder): + return (f"错误:输入文件夹不存在: {input_folder}",) + + # 递归查找所有txt文件 + txt_files = [] + for root, dirs, files in os.walk(input_folder): + for file in files: + if file.lower().endswith('.txt'): + txt_files.append(os.path.join(root, file)) + + if not txt_files: + return (f"警告:在文件夹 {input_folder} 中未找到任何TXT文件",) + + # 按路径排序,确保合并顺序一致 + txt_files.sort() + + # 合并文件内容 + merged_content = [] + processed_files = 0 + + for txt_file in txt_files: + try: + # 尝试以UTF-8编码读取文件,失败时尝试其他编码 + content = None + for encoding in ['utf-8', 'gbk', 'gb2312', 'utf-8-sig']: + try: + with open(txt_file, 'r', encoding=encoding, errors='ignore') as f: + content = f.read().strip() + break + except UnicodeDecodeError: + continue + + if content: # 只处理非空文件 + # 获取相对于输入文件夹的相对路径 + relative_path = os.path.relpath(txt_file, input_folder) + merged_content.append(f"=== {relative_path} ===") + merged_content.append(content) + merged_content.append("") # 空行分隔 + processed_files += 1 + + except Exception as e: + print(f"警告:无法读取文件 {txt_file}: {str(e)}") + continue + + if not merged_content: + return (f"错误:所有TXT文件都为空或无法读取",) + + # 合并所有内容为一个字符串 + final_text = '\n'.join(merged_content) + + success_msg = f"成功合并 {processed_files} 个TXT文件" + print(success_msg) + + return (final_text,) + + except Exception as e: + return (f"错误:处理过程中发生异常: {str(e)}",) + + +# 导出节点映射 +NODE_CLASS_MAPPINGS = { + "DD-TxtFileMerger": DDTxtFileMerger, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "DD-TxtFileMerger": "DD TXT File Merger", +} \ No newline at end of file