dailypromptnode

This commit is contained in:
dseditor
2025-06-19 23:46:46 +08:00
parent 666dbadd01
commit db4246a3b2
7 changed files with 425 additions and 14 deletions
+63 -1
View File
@@ -21,7 +21,7 @@ A powerful workflow scheduling extension for ComfyUI that enables automated dail
## 🚀 Installation 安裝方式
### Method 1: ComfyUI Manager (Recommended)
### 方法 1:ComfyUI 管理器(推薦/稍後可用)
### 方法 1:ComfyUI 管理器(推薦)
1. Open ComfyUI Manager 開啟 ComfyUI 管理器
2. Search for "ComfyUI-ScheduledTask" 搜尋 "ComfyUI-ScheduledTask"
3. Click Install 點擊安裝
@@ -114,6 +114,65 @@ A special utility node that generates time-based random seeds:
- Ensures different results for each scheduled run 確保每次排程運行都有不同結果
- Setting this for create large image list and run 在設定時間內進行大規模隨機排程
### Daily Prompt Scheduler Node / 每日提示詞排程節點
![Demo](readme/demo3.jpg)
A ComfyUI node that provides time-based prompt management with daily rotation capabilities.
提供基於時間的提示詞管理和每日輪換功能。
####Features / 功能特色
- **Daily Rotation / 每日輪換**: Automatically selects different prompts each day / 每天自動選擇不同的提示詞
- **Two Operation Modes / 兩種運作模式**:
- **Scheduled Mode / 排程模式**: Sequential selection with time seed (predictable daily progression) / 使用時間種子的順序選擇(可預測的每日進展)
- **Random Mode / 隨機模式**: Random selection with daily seed (random but consistent per day) / 使用每日種子的隨機選擇(隨機但每天一致)
- **File-based Management / 檔案管理**: Load prompts from text files in the `Prompt` folder / 從Prompt資料夾中的文字檔載入提示詞
- **Individual Time Seeds / 獨立時間種子**: Each text file maintains its own scheduling timeline / 每個文字檔維護自己的排程時間線
- **List Output / 列表輸出**: Returns prompts as a connectable string list for workflow integration / 返回可連接的字串列表用於工作流程整合
- **Loop Integration / 迴圈整合**: Provides count output for easy loop control / 提供計數輸出便於迴圈控制
#### Setup / 設置
1. Visit`Prompt` folder in your node directory: `/ComfyUI-ScheduledTask/Prompt/` / 在節點目錄中訪問`Prompt`資料夾:`/ComfyUI-ScheduledTask/Prompt/`
2. Place your text files (`.txt`) in this folder / 將文字檔(`.txt`)放置在此資料夾中
3. Each line in the text file represents one prompt (empty lines are ignored) / 文字檔中每一行代表一個提示詞(空行會被忽略)
#### Inputs / 輸入參數
- **txt_file / 文字檔**: Dropdown selection of available text files in the Prompt folder / 下拉選單選擇Prompt資料夾中的可用文字檔
- **daily_count / 每日數量**: Number of prompts to select each day (1-5000) / 每天選擇的提示詞數量(1-5000)
- **scheduled / 排程**: Toggle between Scheduled and Random modes (Default is Random Mode)/ 在排程模式和隨機模式之間切換,預設為隨機模式,每天隨機抽選提示組數
#### Outputs / 輸出參數
- **daily_prompts / 每日提示詞**: List of selected prompts (STRING list, connectable to other nodes) / 選中的提示詞列表(字串列表,可連接到其他節點)
- **total_index / 總數索引**: Number of prompts actually returned (INT, useful for loop control) / 實際返回的提示詞數量(整數,用於迴圈控制)
#### Operation Modes / 運作模式
#### Scheduled Mode / 排程模式
- **First Run / 首次運行**: Creates a time seed based on current date / 基於當前日期創建時間種子
- **Daily Progression / 每日進展**: Selects prompts sequentially / 按順序選擇提示詞
- **Example / 範例**: With 12 prompts, selecting 3 per day / 有12個提示詞,每天選擇3個:
- Day 1 / 第1天: Prompts 1, 2, 3 / 提示詞1, 2, 3
- Day 2 / 第2天: Prompts 4, 5, 6 / 提示詞4, 5, 6
- Day 3 / 第3天: Prompts 7, 8, 9 / 提示詞7, 8, 9
- Day 4 / 第4天: Prompts 10, 11, 12 / 提示詞10, 11, 12
- Day 5 / 第5天: Prompts 1, 2, 3 (cycles back) / 提示詞1, 2, 3(循環回到開頭)
#### Random Mode / 隨機模式
- **Daily Random / 每日隨機**: Uses date-based seed for random selection / 使用基於日期的種子進行隨機選擇
- **Consistent / 一致性**: Same random selection throughout the day / 整天保持相同的隨機選擇
- **Example / 範例**: Random but fixed selection per day (e.g., prompts 3, 7, 11) / 每天隨機但固定的選擇(例如:提示詞3, 7, 11)
#### File Management / 檔案管理
- **Multiple Files / 多檔案**: Each `.txt` file has independent scheduling / 每個`.txt`檔案都有獨立的排程
- **Time Seeds / 時間種子**: Stored as `{filename}_time_seed.json` in the Prompt folder(If Scheduled) / 以`{檔名}_time_seed.json`的形式儲存在Prompt資料夾中,假使是使用排程模式
- **Auto-wrapping / 自動循環**: Never causes index errors, automatically wraps around (With Time Seed) / 永不會造成索引錯誤,自動循環,但可能會因此採樣到相同的圖片,請配合時間種子使用
- **UTF-8 Support / UTF-8支援**: Handles international characters and languages / 支援國際字符和多種語言
### File Structure 檔案結構
```
@@ -121,12 +180,15 @@ ComfyUI-ScheduledTask/
├── __init__.py # Extension entry point 擴展入口點
├── scheduler.py # Core scheduling logic & TimeToSeedList node 核心排程邏輯和時間種子節點
├── web_handler.py # API endpoints API 端點
├── Prompt/ # 提示詞檔案庫
│ ├── Example.txt # 範例檔案
├── web/
│ └── scheduled_task.js # Frontend interface 前端介面
├── Workflow/ # Saved workflow files (auto-created) 保存的工作流程檔案(自動創建)
│ ├── workflow1.json
│ └── workflow2.json
└── schedules.json # Schedule configuration (auto-created) 排程配置(自動創建)
```
### Workflow Format 工作流程格式
Saved workflows are stored in ComfyUI API format:
+33 -10
View File
@@ -4,10 +4,20 @@ ComfyUI-ScheduledTask
"""
import os
from .scheduler import SchedulerManager, TimeToSeedList
from .web_handler import setup_routes
import threading
import time
import atexit
# 匯入節點類別和排程管理器
from .scheduler import SchedulerManager, TimeToSeedList, DailyPromptScheduler
# 匯入web處理器 (如果存在的話)
try:
from .web_handler import setup_routes
HAS_WEB_HANDLER = True
except ImportError:
HAS_WEB_HANDLER = False
print("Warning: web_handler not found, web interface will be disabled")
# 全域排程管理器
scheduler_manager = None
@@ -20,35 +30,48 @@ def get_scheduler():
# 延遲初始化
def delayed_init():
time.sleep(3) # 等待ComfyUI完全啟動
get_scheduler()
try:
time.sleep(3) # 等待ComfyUI完全啟動
get_scheduler()
except Exception as e:
print(f"Error in delayed initialization: {e}")
# 啟動初始化執行緒
init_thread = threading.Thread(target=delayed_init, daemon=True)
init_thread.start()
# 設定web路由
setup_routes()
# 設定web路由 (如果可用)
if HAS_WEB_HANDLER:
try:
setup_routes()
except Exception as e:
print(f"Error setting up web routes: {e}")
# 清理函數
import atexit
def cleanup():
global scheduler_manager
if scheduler_manager:
scheduler_manager.stop()
try:
scheduler_manager.stop()
except Exception as e:
print(f"Error during cleanup: {e}")
atexit.register(cleanup)
# ComfyUI節點映射
# 節點類別映射 - 這是關鍵!
NODE_CLASS_MAPPINGS = {
"TimeToSeedList": TimeToSeedList,
"DailyPromptScheduler": DailyPromptScheduler,
}
# 節點顯示名稱映射
NODE_DISPLAY_NAME_MAPPINGS = {
"TimeToSeedList": "Time to Seed List",
"DailyPromptScheduler": "Daily Prompt Scheduler",
}
# Web擴展映射 - 這是關鍵!
# Web擴展映射
WEB_DIRECTORY = "./web"
# 確保必要的匯出
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', 'WEB_DIRECTORY']
+12
View File
@@ -0,0 +1,12 @@
Ancient magical laboratory filled with glowing potions, floating books, crystal orbs, wooden shelves lined with mysterious bottles, candlelight illumination, mystical atmosphere, detailed fantasy art style
Wizard's underground laboratory with bubbling cauldrons, spell circles carved in stone floor, hanging herbs and ingredients, magical apparatus, dim torchlight, medieval fantasy setting
Modern witch's laboratory combining magical and scientific elements, glass beakers with swirling colorful potions, digital displays showing magical formulas, LED lighting mixed with candles, contemporary fantasy aesthetic
Alchemist's tower laboratory with spiral staircases, floating magical instruments, golden light streaming through stained glass windows, ancient tomes, transmutation circles, Renaissance-inspired magical workshop
Steampunk magical laboratory with brass pipes, mechanical magical devices, steam-powered cauldrons, copper and bronze equipment, Victorian-era magical engineering, gear-driven spell apparatus
Enchanted forest laboratory built inside a giant tree, bioluminescent plants as light sources, natural wooden surfaces, magical mushrooms, fairy lights, organic magical equipment, nature-integrated magic workshop
Crystal cave magical laboratory with towering crystal formations, prismatic light effects, levitating gemstones, magical energy coursing through crystal networks, ethereal glow, crystalline magical apparatus
Underwater magical laboratory in an air bubble, aquatic magical creatures swimming outside, coral-based equipment, bioluminescent sea plants, magical water manipulation devices, oceanic fantasy setting
Celestial magical observatory laboratory on a floating platform, star charts and astronomical instruments, magical telescopes, cosmic energy collectors, nebula views through windows, space-themed magical research facility
Miniature magical laboratory inside a snow globe, tiny detailed magical equipment, microscopic potions, doll-house scale magical apparatus, whimsical and intricate design, magical realism aesthetic
Dark gothic magical laboratory with ornate iron fixtures, dramatic shadows, purple and green magical flames, ancient grimoires bound in leather, gargoyle statues, gothic arches, ominous magical energy
Bright cheerful magical bakery laboratory, pastel-colored potions that look like cupcakes, magical mixing bowls, enchanted ovens, candy-colored magical ingredients, whimsical cartoon style, sweet magical atmosphere
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

+178
View File
@@ -14,6 +14,184 @@ import random
from datetime import datetime
import hashlib
class DailyPromptScheduler:
def __init__(self):
self.node_dir = os.path.dirname(os.path.abspath(__file__))
self.prompt_dir = os.path.join(self.node_dir, "Prompt")
# Ensure Prompt folder exists
if not os.path.exists(self.prompt_dir):
os.makedirs(self.prompt_dir)
def get_txt_files(self):
"""Get all txt files in the Prompt folder"""
if not os.path.exists(self.prompt_dir):
return ["Please place txt files in Prompt folder"]
# Only get .txt files, exclude .json files
txt_files = [f for f in os.listdir(self.prompt_dir)
if f.endswith('.txt') and not f.endswith('_time_seed.json')]
if not txt_files:
return ["Please place txt files in Prompt folder"]
return txt_files
def get_seed_file_path(self, txt_filename):
"""Get the seed file path for specific txt file"""
# Remove .txt extension and add _time_seed.json
base_name = os.path.splitext(txt_filename)[0]
seed_filename = f"{base_name}_time_seed.json"
return os.path.join(self.prompt_dir, seed_filename)
def load_time_seed(self, txt_filename):
"""Load time seed for specific txt file"""
seed_file = self.get_seed_file_path(txt_filename)
if os.path.exists(seed_file):
try:
with open(seed_file, 'r', encoding='utf-8') as f:
seed_data = json.load(f)
return seed_data.get('seed_date', None)
except:
return None
return None
def save_time_seed(self, txt_filename, seed_date):
"""Save time seed for specific txt file"""
seed_file = self.get_seed_file_path(txt_filename)
seed_data = {'seed_date': seed_date}
with open(seed_file, 'w', encoding='utf-8') as f:
json.dump(seed_data, f, ensure_ascii=False)
@classmethod
def INPUT_TYPES(cls):
instance = cls()
txt_files = instance.get_txt_files()
return {
"required": {
"txt_file": (txt_files, {"default": txt_files[0] if txt_files else ""}),
"daily_count": ("INT", {
"default": 3,
"min": 1,
"max": 5000,
"step": 1
}),
"scheduled": ("BOOLEAN", {
"default": False,
"label_on": "Set Time Seed",
"label_off": "Random Mode"
}),
}
}
RETURN_TYPES = ("STRING", "INT")
RETURN_NAMES = ("daily_prompts", "total_index")
OUTPUT_IS_LIST = (True, False)
FUNCTION = "get_daily_prompts"
CATEGORY = "text/scheduled"
def IS_CHANGED(self, txt_file, daily_count, scheduled):
"""Ensure node is not cached"""
current_time = datetime.now()
current_date = current_time.strftime("%Y%m%d")
# If file doesn't exist, return current timestamp
txt_path = os.path.join(self.prompt_dir, txt_file)
if not os.path.exists(txt_path):
return str(current_time.timestamp())
# Combine file modification time, current date and settings to generate identifier
try:
file_mtime = os.path.getmtime(txt_path)
unique_id = f"{file_mtime}_{current_date}_{daily_count}_{scheduled}_{txt_file}"
return hashlib.md5(unique_id.encode()).hexdigest()
except:
return str(current_time.timestamp())
def read_txt_file(self, filename):
"""Read and parse txt file"""
txt_path = os.path.join(self.prompt_dir, filename)
if not os.path.exists(txt_path):
return []
try:
with open(txt_path, 'r', encoding='utf-8') as f:
content = f.read()
# Split by newlines, filter empty lines
lines = [line.strip() for line in content.split('\n') if line.strip()]
return lines
except Exception as e:
logger.error(f"Error reading file: {e}")
return []
def get_daily_prompts(self, txt_file, daily_count, scheduled):
# Check if file exists
if txt_file == "Please place txt files in Prompt folder":
return (["Error: Please place txt files in Prompt folder"], 0)
# Read prompt list
all_prompts = self.read_txt_file(txt_file)
if not all_prompts:
error_msg = f"Error: Unable to read file {txt_file} or file is empty"
return ([error_msg], 0)
current_time = datetime.now()
current_date = current_time.strftime("%Y%m%d")
# Ensure not exceeding available prompts count
actual_count = min(daily_count, len(all_prompts))
# Handle scheduling logic
if scheduled:
# Scheduled mode with time seed
seed_date = self.load_time_seed(txt_file)
if seed_date is None:
# First time setting seed
self.save_time_seed(txt_file, current_date)
seed_date = current_date
status = f"Time seed set: {seed_date} for {txt_file}"
else:
status = f"Using time seed: {seed_date} for {txt_file}"
# Calculate days difference from seed date
try:
seed_datetime = datetime.strptime(seed_date, "%Y%m%d")
current_datetime = datetime.strptime(current_date, "%Y%m%d")
days_diff = (current_datetime - seed_datetime).days
except:
days_diff = 0
# Calculate starting index for sequential selection
total_prompts = len(all_prompts)
start_index = (days_diff * actual_count) % total_prompts
# Sequential selection with wrapping
selected_prompts = []
for i in range(actual_count):
index = (start_index + i) % total_prompts
selected_prompts.append(all_prompts[index])
else:
# Random mode: use different random seed each day
random_seed = int(current_date) + hash(txt_file)
random.seed(random_seed)
status = f"Random mode - Date seed: {current_date} for {txt_file}"
# Randomly select prompts without replacement
selected_prompts = random.sample(all_prompts, actual_count)
# Output debug information
logger.info(f"DailyPromptScheduler: {status}")
logger.info(f"DailyPromptScheduler: Selected {actual_count} from {len(all_prompts)} prompts")
logger.info(f"DailyPromptScheduler: Selected prompts: {selected_prompts}")
return (selected_prompts, actual_count)
class TimeToSeedList:
"""
Generate random seed list based on current time
+3 -3
View File
@@ -1,11 +1,11 @@
{
"schedules": [
{
"time": "09:09",
"workflow": "Exmaple.json",
"time": "23:22",
"workflow": "ExmapleDailyPrompt.json",
"enabled": true
}
],
"globalEnabled": true,
"updated_at": "2025-06-19T09:07:21.402267"
"updated_at": "2025-06-20T23:20:23.382801"
}
+136
View File
@@ -0,0 +1,136 @@
{
"3": {
"inputs": {
"seed": [
"31",
0
],
"steps": 20,
"cfg": 8,
"sampler_name": "euler",
"scheduler": "normal",
"denoise": 1,
"model": [
"4",
0
],
"positive": [
"6",
0
],
"negative": [
"7",
0
],
"latent_image": [
"5",
0
]
},
"class_type": "KSampler",
"_meta": {
"title": "KSampler"
}
},
"4": {
"inputs": {
"ckpt_name": "dreamshaper_8.safetensors"
},
"class_type": "CheckpointLoaderSimple",
"_meta": {
"title": "Load Checkpoint"
}
},
"5": {
"inputs": {
"width": 512,
"height": 512,
"batch_size": 1
},
"class_type": "EmptyLatentImage",
"_meta": {
"title": "Empty Latent Image"
}
},
"6": {
"inputs": {
"text": [
"30",
0
],
"clip": [
"4",
1
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP Text Encode (Prompt)"
}
},
"7": {
"inputs": {
"text": "text, watermark",
"clip": [
"4",
1
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP Text Encode (Prompt)"
}
},
"8": {
"inputs": {
"samples": [
"3",
0
],
"vae": [
"4",
2
]
},
"class_type": "VAEDecode",
"_meta": {
"title": "VAE Decode"
}
},
"9": {
"inputs": {
"filename_prefix": "ComfyUI",
"images": [
"8",
0
]
},
"class_type": "SaveImage",
"_meta": {
"title": "Save Image"
}
},
"30": {
"inputs": {
"txt_file": "Example.txt",
"daily_count": 3,
"scheduled": false
},
"class_type": "DailyPromptScheduler",
"_meta": {
"title": "Daily Prompt Scheduler"
}
},
"31": {
"inputs": {
"count": [
"30",
1
]
},
"class_type": "TimeToSeedList",
"_meta": {
"title": "Time to Seed List"
}
}
}