commit dbc5fa191dd56148342f4aa5f486f8f2bb0d29d4 Author: Drift Johnson Date: Sat Nov 22 13:19:18 2025 +0000 Add files via upload diff --git a/INSTALLATION.md b/INSTALLATION.md new file mode 100644 index 0000000..147cabf --- /dev/null +++ b/INSTALLATION.md @@ -0,0 +1,213 @@ +# ComfyUI Installation Guide - Prompt Orchestration Nodes + +## Quick Installation + +### Method 1: Manual Installation (Recommended) + +1. **Locate your ComfyUI custom_nodes directory** + ``` + ComfyUI/ + └── custom_nodes/ + └── (install here) + ``` + +2. **Copy the entire `comfyui_nodes` folder** + ```bash + # Copy the entire folder to ComfyUI/custom_nodes/ + cp -r comfyui_nodes ComfyUI/custom_nodes/comfyui_prompt_orchestration + ``` + +3. **Restart ComfyUI** + - Restart your ComfyUI instance + - Look for the loading message in console + - Nodes will appear under "Prompt Orchestration" category + +### Method 2: Git Clone (If using Git) + +```bash +cd ComfyUI/custom_nodes +git clone comfyui_prompt_orchestration +# Or manually copy the folder +``` + +### Method 3: Symlink (For Development) + +```bash +cd ComfyUI/custom_nodes +ln -s /path/to/comfyui_nodes comfyui_prompt_orchestration +``` + +--- + +## Directory Structure + +After installation, your structure should look like: + +``` +ComfyUI/ +└── custom_nodes/ + └── comfyui_prompt_orchestration/ + ├── __init__.py + ├── prompt_orchestration_nodes.py + ├── prompt_orchestrator.py + ├── batch_processor.py + └── README_COMFYUI.md +``` + +--- + +## Verification + +### 1. Check Console Output + +When ComfyUI starts, you should see: + +``` +================================================================================ +Prompt Orchestration Nodes v1.0.0 loaded successfully! + - 9 custom nodes available + - 20 lighting modifiers (DRY/WET categories) + - 10+ built-in style templates + - Category: 'Prompt Orchestration' +================================================================================ +``` + +### 2. Check Node Menu + +In ComfyUI: +1. Right-click on canvas → "Add Node" +2. Look for "Prompt Orchestration" category +3. You should see 9 nodes: + - Lighting Modifier (Single) + - Batch Variation Generator + - Style Transform + - Smart Variation (NLP) + - Preset Collection + - Modifier Info + - Prompt Combiner + - Random Modifier + - Multi-Style Layer + +--- + +## Troubleshooting + +### Nodes Don't Appear + +**Problem**: Nodes not showing in menu + +**Solutions**: +1. Check folder name: Must be in `custom_nodes/` directory +2. Check console for errors +3. Verify all files are present (especially `__init__.py`) +4. Restart ComfyUI completely (not just refresh) + +### Import Errors + +**Problem**: "Could not import prompt_orchestrator" error + +**Solutions**: +1. Ensure `prompt_orchestrator.py` is in the same folder as `__init__.py` +2. Ensure `batch_processor.py` is in the same folder +3. Check file permissions (should be readable) + +### Python Version Issues + +**Problem**: Syntax errors or import failures + +**Requirements**: +- Python 3.7 or higher (same as ComfyUI requirement) +- No additional packages needed (uses stdlib only) + +--- + +## Uninstallation + +To remove the nodes: + +```bash +rm -rf ComfyUI/custom_nodes/comfyui_prompt_orchestration +``` + +Then restart ComfyUI. + +--- + +## Updating + +To update to a new version: + +1. **Backup your workflows** (recommended) +2. Delete the old folder +3. Copy the new version +4. Restart ComfyUI + +--- + +## Platform-Specific Notes + +### Windows + +```batch +# Copy command +xcopy /E /I comfyui_nodes "C:\path\to\ComfyUI\custom_nodes\comfyui_prompt_orchestration" +``` + +### macOS / Linux + +```bash +# Copy command +cp -r comfyui_nodes ~/ComfyUI/custom_nodes/comfyui_prompt_orchestration +``` + +--- + +## File Checklist + +Make sure these files are present after installation: + +- [x] `__init__.py` - Package initialization +- [x] `prompt_orchestration_nodes.py` - Node definitions +- [x] `prompt_orchestrator.py` - Core engine +- [x] `batch_processor.py` - Batch operations +- [x] `README_COMFYUI.md` - Documentation +- [x] `INSTALLATION.md` - This file + +--- + +## Next Steps + +After successful installation: + +1. **Read the README**: Check `README_COMFYUI.md` for usage guide +2. **Load Example Workflow**: Import example JSON workflows +3. **Try Basic Node**: Start with "Lighting Modifier (Single)" +4. **Explore Presets**: Use "Preset Collection" for quick results + +--- + +## Support + +If you encounter issues: + +1. Check the console for error messages +2. Verify file structure matches expected layout +3. Ensure Python version compatibility +4. Check file permissions + +--- + +## Requirements + +- **ComfyUI**: Latest version recommended +- **Python**: 3.7 or higher +- **Dependencies**: None (uses Python stdlib only) +- **Platform**: Windows, macOS, Linux + +--- + +## Success! + +If you see the nodes in your ComfyUI interface under "Prompt Orchestration", you're ready to go! 🎉 + +Check `README_COMFYUI.md` for detailed usage instructions and examples. diff --git a/NODE_REFERENCE.md b/NODE_REFERENCE.md new file mode 100644 index 0000000..b89c69f --- /dev/null +++ b/NODE_REFERENCE.md @@ -0,0 +1,614 @@ +# Node Reference Guide - Technical Specifications + +Complete technical reference for all Prompt Orchestration nodes in ComfyUI. + +--- + +## Node Specifications + +### 1. PromptLightingModifier + +**Class**: `PromptLightingModifierNode` +**Display Name**: "Lighting Modifier (Single)" +**Category**: "Prompt Orchestration" + +#### Input Specification +```python +INPUT_TYPES = { + "required": { + "text": ("STRING", { + "multiline": True, + "default": "Subject description here" + }), + "modifier_id": ("INT", { + "default": 7, + "min": 1, + "max": 20, + "step": 1 + }), + "preserve_base": ("BOOLEAN", { + "default": True + }) + } +} +``` + +#### Output Specification +- **RETURN_TYPES**: `("STRING",)` +- **RETURN_NAMES**: `("modified_prompt",)` +- **OUTPUT_IS_LIST**: False + +#### Function +- **FUNCTION**: `"apply_modifier"` +- **Description**: Applies single lighting modifier from ID 1-20 + +#### Parameters +- `text` (STRING): Base prompt to modify +- `modifier_id` (INT 1-20): Which lighting modifier to apply +- `preserve_base` (BOOLEAN): + - True: Append modifier to text (recommended) + - False: Replace lighting info in text + +#### Usage Example +```python +Input: + text = "Sports car on highway" + modifier_id = 7 + preserve_base = True + +Output: + "Sports car on highway, golden hour, warm low-angle side-light, clear sky" +``` + +--- + +### 2. PromptBatchVariation + +**Class**: `PromptBatchVariationNode` +**Display Name**: "Batch Variation Generator" +**Category**: "Prompt Orchestration" + +#### Input Specification +```python +INPUT_TYPES = { + "required": { + "text": ("STRING", { + "multiline": True, + "default": "Subject description here" + }), + "category": (["all", "dry", "wet", "custom"],) + }, + "optional": { + "custom_ids": ("STRING", { + "default": "1,7,18", + "multiline": False + }) + } +} +``` + +#### Output Specification +- **RETURN_TYPES**: 10x `("STRING",)` +- **RETURN_NAMES**: `("var_1", "var_2", ..., "var_10")` +- **OUTPUT_IS_LIST**: (True, True, ..., True) - 10 times + +#### Function +- **FUNCTION**: `"generate_variations"` +- **Description**: Generates up to 10 prompt variations + +#### Parameters +- `text` (STRING): Base prompt +- `category` (ENUM): "all" (1-20), "dry" (1-16), "wet" (17-20), "custom" +- `custom_ids` (STRING): Comma-separated IDs when category="custom" + +#### Category Mapping +- **"all"**: Modifiers 1-20 (all modifiers) +- **"dry"**: Modifiers 1-16 (clear conditions) +- **"wet"**: Modifiers 17-20 (wet conditions) +- **"custom"**: User-specified IDs from custom_ids + +#### Output Behavior +- Returns list outputs (can connect to batch nodes) +- Pads to 10 outputs if fewer variations +- Padding uses original text + +--- + +### 3. PromptStyleTransform + +**Class**: `PromptStyleTransformNode` +**Display Name**: "Style Transform" +**Category**: "Prompt Orchestration" + +#### Input Specification +```python +INPUT_TYPES = { + "required": { + "text": ("STRING", multiline=True), + "style": ([ + "custom", "pop_art_1960s", "bauhaus", "art_deco", + "cyberpunk", "minimalist", "impressionist", "noir", + "futuristic", "vintage", "brutalist" + ],), + "maintain_composition": ("BOOLEAN", default=True) + }, + "optional": { + "custom_style": ("STRING", default="") + } +} +``` + +#### Output Specification +- **RETURN_TYPES**: `("STRING",)` +- **RETURN_NAMES**: `("styled_prompt",)` + +#### Function +- **FUNCTION**: `"apply_style"` + +#### Parameters +- `text`: Base prompt +- `style`: Pre-defined style or "custom" +- `maintain_composition`: + - True: Explicitly states to maintain composition + - False: Appends style without composition note +- `custom_style`: Used when style="custom" + +#### Built-in Styles +1. `pop_art_1960s`: "1960s pop art style with bright colors, bold graphics, and commercial aesthetics" +2. `bauhaus`: "Bauhaus style with geometric forms, primary colors, and modernist principles" +3. `art_deco`: "Art Deco style with geometric patterns, luxurious details, and streamlined forms" +4. `cyberpunk`: "cyberpunk aesthetic with neon lights, high-tech elements, and dystopian atmosphere" +5. `minimalist`: "minimalist style with clean lines, negative space, and essential elements only" +6. `impressionist`: "impressionist painting style with visible brushstrokes and emphasis on light" +7. `noir`: "film noir style with dramatic shadows, high contrast, and moody atmosphere" +8. `futuristic`: "futuristic design with sleek forms, advanced materials, and sci-fi elements" +9. `vintage`: "vintage aesthetic with retro colors, grain, and nostalgic atmosphere" +10. `brutalist`: "brutalist architecture style with raw concrete, geometric forms, and bold structure" + +--- + +### 4. PromptSmartVariation + +**Class**: `PromptSmartVariationNode` +**Display Name**: "Smart Variation (NLP)" +**Category**: "Prompt Orchestration" + +#### Input Specification +```python +INPUT_TYPES = { + "required": { + "text": ("STRING", multiline=True), + "intent": ("STRING", { + "multiline": False, + "default": "make it golden hour" + }) + } +} +``` + +#### Output Specification +- **RETURN_TYPES**: `("STRING",)` +- **RETURN_NAMES**: `("smart_prompt",)` + +#### Function +- **FUNCTION**: `"smart_vary"` + +#### Intent Recognition +System recognizes keywords in intent string: + +**Time-based intents**: +- "morning" → modifiers 1, 2, 3 +- "afternoon" → modifiers 5, 6, 14 +- "golden hour" → modifier 7 +- "sunset" → modifier 8 +- "night" → modifiers 11, 12 +- "blue hour" → modifiers 10, 17, 18 +- "wet" → modifiers 17, 18, 19, 20 + +**Style-based intents**: +- Detects style keywords → applies style transformation +- Falls back to custom style if no match + +--- + +### 5. PromptPresetCollection + +**Class**: `PromptPresetCollectionNode` +**Display Name**: "Preset Collection" +**Category**: "Prompt Orchestration" + +#### Input Specification +```python +INPUT_TYPES = { + "required": { + "text": ("STRING", multiline=True), + "preset": ([ + "key_moments", "commercial", "artistic", + "full_day_cycle", "wet_night" + ],) + } +} +``` + +#### Output Specification +- **RETURN_TYPES**: 8x `("STRING",)` +- **RETURN_NAMES**: `("var_1", ..., "var_8")` +- **OUTPUT_IS_LIST**: (True, ..., True) - 8 times + +#### Function +- **FUNCTION**: `"apply_preset"` + +#### Preset Definitions + +**"key_moments"**: +- Modifier IDs: [1, 7, 8, 10, 18, 20] +- Count: 6 variations +- Use: Dramatic lighting variety + +**"commercial"**: +- Modifier IDs: [3, 4, 7, 11] +- Count: 4 variations +- Use: Product photography + +**"artistic"**: +- Modifier IDs: [7, 8, 9, 10] +- Styles: ["impressionist", "minimalist", "art_deco"] +- Count: 7 variations (4 lighting + 3 styles) +- Use: Creative projects + +**"full_day_cycle"**: +- Modifier IDs: 1-16 (all DRY) +- Count: 16 variations +- Use: Time-lapse visualization + +**"wet_night"**: +- Modifier IDs: 17-20 (all WET) +- Count: 4 variations +- Use: Dramatic urban/night scenes + +--- + +### 6. PromptModifierInfo + +**Class**: `PromptModifierInfoNode` +**Display Name**: "Modifier Info" +**Category**: "Prompt Orchestration" + +#### Input Specification +```python +INPUT_TYPES = { + "required": { + "modifier_id": ("INT", { + "default": 7, + "min": 1, + "max": 20 + }) + } +} +``` + +#### Output Specification +- **RETURN_TYPES**: `("STRING", "STRING", "STRING")` +- **RETURN_NAMES**: `("description", "category", "tags")` + +#### Function +- **FUNCTION**: `"get_info"` + +#### Outputs +- `description`: Full modifier text (e.g., "golden hour, warm low-angle side-light, clear sky") +- `category`: "dry" or "wet" +- `tags`: Comma-separated tags (e.g., "golden-hour, warm, side-light, clear") + +--- + +### 7. PromptCombiner + +**Class**: `PromptCombinerNode` +**Display Name**: "Prompt Combiner" +**Category**: "Prompt Orchestration" + +#### Input Specification +```python +INPUT_TYPES = { + "required": { + "text_1": ("STRING", multiline=True) + }, + "optional": { + "text_2": ("STRING", multiline=True), + "text_3": ("STRING", multiline=True), + "text_4": ("STRING", multiline=True), + "separator": ("STRING", { + "default": ", ", + "multiline": False + }) + } +} +``` + +#### Output Specification +- **RETURN_TYPES**: `("STRING",)` +- **RETURN_NAMES**: `("combined_text",)` + +#### Function +- **FUNCTION**: `"combine"` + +#### Behavior +- Combines non-empty text inputs +- Joins with separator string +- Ignores empty inputs + +--- + +### 8. PromptRandomModifier + +**Class**: `PromptRandomModifierNode` +**Display Name**: "Random Modifier" +**Category**: "Prompt Orchestration" + +#### Input Specification +```python +INPUT_TYPES = { + "required": { + "text": ("STRING", multiline=True), + "category": (["all", "dry", "wet"],), + "seed": ("INT", { + "default": 0, + "min": 0, + "max": 0xffffffffffffffff + }) + } +} +``` + +#### Output Specification +- **RETURN_TYPES**: `("STRING", "INT")` +- **RETURN_NAMES**: `("modified_prompt", "used_modifier_id")` + +#### Function +- **FUNCTION**: `"apply_random"` + +#### Behavior +- Selects random modifier from category using seed +- Returns both modified prompt and which modifier was chosen +- Same seed = same modifier (reproducible) + +--- + +### 9. PromptMultiStyle + +**Class**: `PromptMultiStyleNode` +**Display Name**: "Multi-Style Layer" +**Category**: "Prompt Orchestration" + +#### Input Specification +```python +INPUT_TYPES = { + "required": { + "text": ("STRING", multiline=True), + "style_1": ([style_list],) + }, + "optional": { + "style_2": ([style_list],), + "style_3": ([style_list],) + } +} +``` + +#### Output Specification +- **RETURN_TYPES**: `("STRING",)` +- **RETURN_NAMES**: `("multi_styled_prompt",)` + +#### Function +- **FUNCTION**: `"apply_multi_style"` + +#### Behavior +- Applies up to 3 styles sequentially +- Each style builds on previous +- Set to "none" to skip a style slot + +--- + +## Data Types + +### STRING +- Multiline text input/output +- Used for prompts and descriptions +- Can be connected to CLIP Text Encode + +### INT +- Integer values +- Used for modifier IDs (1-20) and seeds +- Can be connected to math/logic nodes + +### BOOLEAN +- True/False toggle +- Used for behavior switches +- Displayed as checkbox in UI + +### ENUM (Dropdown) +- Fixed list of options +- Used for categories, styles, presets +- Displayed as dropdown menu + +--- + +## Connection Patterns + +### String → String +Most common: Text flows through nodes +``` +[Text Input] → [Node] → [CLIP Text Encode] +``` + +### String → Multiple Strings (Batch) +List outputs from batch nodes +``` +[Text] → [Batch Node] → [var_1] → [CLIP 1] + → [var_2] → [CLIP 2] + → [var_3] → [CLIP 3] +``` + +### Multiple → Single (Combiner) +``` +[Text 1] ─┐ +[Text 2] ─┤→ [Combiner] → [Output] +[Text 3] ─┘ +``` + +### Info Display +``` +[Modifier ID] → [Info Node] → [description] → [Show Text] + → [category] → [Display] + → [tags] → [Display] +``` + +--- + +## Node Compatibility + +### Compatible Input Nodes +- Primitive (String) +- Text Input +- Load Text +- Any node outputting STRING type + +### Compatible Output Nodes +- CLIP Text Encode +- Save Text +- Show Text +- Any node accepting STRING input + +### Batch Processing Compatible +- Batch Variation Generator +- Preset Collection +- Any node with OUTPUT_IS_LIST=True + +--- + +## Performance Characteristics + +### Execution Speed +- All nodes: <10ms typically +- No network calls +- In-memory processing only + +### Memory Usage +- Minimal: ~1-2MB per node +- Scales linearly with text length +- No persistent state between executions + +### Caching +- Nodes don't cache results +- Re-execute on each workflow run +- ComfyUI handles workflow-level caching + +--- + +## Error Handling + +### Invalid Modifier ID +- Returns error message in output +- Does not break workflow +- Check console for details + +### Empty Inputs +- Returns original/default text +- Continues execution +- No workflow interruption + +### Invalid Custom IDs +- Skips invalid IDs +- Uses valid ones only +- Falls back to default if all invalid + +--- + +## Thread Safety + +- All nodes are thread-safe +- Can run in parallel workflows +- No shared state between instances + +--- + +## Version Compatibility + +- **ComfyUI**: Latest version recommended +- **Python**: 3.7+ required +- **Backward Compatible**: Yes (JSON workflows) +- **Forward Compatible**: Should be (minimal dependencies) + +--- + +## Extension Points + +### Custom Modifiers +Add to `prompt_orchestrator.py`: +```python +custom = LightingModifier( + id=21, + description="your modifier", + category=ModifierCategory.DRY, + tags=["tag1", "tag2"] +) +orchestrator.modifiers[21] = custom +``` + +### Custom Styles +Add to style templates: +```python +orchestrator.custom_style_templates["my_style"] = "description" +``` + +--- + +## Debugging + +### Enable Console Logging +Check ComfyUI console for: +- Node initialization messages +- Error messages +- Input/output values (if debugging enabled) + +### Test Nodes Individually +1. Create minimal workflow +2. Add single node +3. Connect Show Text to output +4. Verify results + +### Validate Inputs +Use Modifier Info node to check modifier descriptions before using. + +--- + +## API Reference + +All nodes implement ComfyUI's standard node interface: + +```python +class CustomNode: + @classmethod + def INPUT_TYPES(cls): + # Define inputs + + RETURN_TYPES = () + RETURN_NAMES = () + FUNCTION = "method_name" + CATEGORY = "category" + + def method_name(self, **kwargs): + # Implementation + return outputs +``` + +--- + +## License + +MIT License - Same as core system + +--- + +**Last Updated**: v1.0.0 +**For**: ComfyUI Custom Nodes +**Compatibility**: ComfyUI Latest diff --git a/QUICKSTART_COMFYUI.md b/QUICKSTART_COMFYUI.md new file mode 100644 index 0000000..e07ea63 --- /dev/null +++ b/QUICKSTART_COMFYUI.md @@ -0,0 +1,417 @@ +# ComfyUI Quick Start - Prompt Orchestration Nodes + +Get started with prompt orchestration in ComfyUI in under 5 minutes! + +--- + +## 🚀 Installation (30 seconds) + +1. **Copy folder to ComfyUI**: + ``` + Copy comfyui_nodes → ComfyUI/custom_nodes/comfyui_prompt_orchestration + ``` + +2. **Restart ComfyUI** + +3. **Verify**: Look for "Prompt Orchestration" category in Add Node menu + +✅ **Done!** If you see the category, you're ready to go. + +--- + +## 🎯 Your First Workflow (2 minutes) + +### Step 1: Create Basic Workflow + +1. **Right-click canvas** → Add Node +2. **Navigate**: Prompt Orchestration → Lighting Modifier (Single) +3. **Connect**: + ``` + [Your Text Input] → [Lighting Modifier] → [CLIP Text Encode] → [Your Pipeline] + ``` + +### Step 2: Configure Node + +In the Lighting Modifier node: +- **text**: Enter your base prompt (e.g., "sports car on highway") +- **modifier_id**: Set to `7` (golden hour - most popular) +- **preserve_base**: Keep as `True` + +### Step 3: Generate + +Run your workflow! The prompt will automatically get golden hour lighting. + +**Result**: +``` +Input: "sports car on highway" +Output: "sports car on highway, golden hour, warm low-angle side-light, clear sky" +``` + +--- + +## 🌟 Top 5 Most Useful Nodes + +### 1️⃣ Lighting Modifier (Single) - START HERE +**Use when**: You want specific lighting on one prompt +**Best setting**: modifier_id = 7 (golden hour) +**Connects to**: CLIP Text Encode + +### 2️⃣ Batch Variation Generator +**Use when**: You want multiple lighting options to compare +**Best setting**: category = "key_moments" +**Outputs**: 10 variations ready to render + +### 3️⃣ Preset Collection +**Use when**: You want optimized sets for specific use cases +**Best setting**: preset = "commercial" (for products) or "key_moments" (for variety) +**Outputs**: 8 professional variations + +### 4️⃣ Style Transform +**Use when**: You want to add artistic style +**Best setting**: style = "cyberpunk" or "noir" +**Tip**: Apply AFTER Lighting Modifier + +### 5️⃣ Smart Variation (NLP) +**Use when**: You want quick natural language edits +**Best setting**: intent = "make it golden hour" or "add wet street" +**Great for**: Fast iterations + +--- + +## 💡 Common Workflows + +### Workflow A: Single Perfect Shot + +**Goal**: Get one image with perfect lighting + +``` +┌─────────────┐ +│ Text Input │ +│ "car scene" │ +└──────┬──────┘ + │ +┌──────▼─────────────────┐ +│ Lighting Modifier │ +│ modifier_id: 7 │ +│ (golden hour) │ +└──────┬─────────────────┘ + │ +┌──────▼──────────┐ +│ CLIP Text Encode│ +└──────┬──────────┘ + │ + [REST OF PIPELINE] +``` + +**Time**: 30 seconds +**Output**: 1 perfectly lit image + +--- + +### Workflow B: Compare 4 Lighting Options + +**Goal**: See different times of day + +``` +┌─────────────┐ +│ Text Input │ +└──────┬──────┘ + │ +┌──────▼───────────────────┐ +│ Batch Variation Generator│ +│ category: custom │ +│ custom_ids: "1,7,8,10" │ +│ (sunrise,golden,sunset, │ +│ blue hour) │ +└─┬──┬──┬──┬───────────────┘ + │ │ │ │ + │ │ │ └→ CLIP → KSampler → Image 4 + │ │ └───→ CLIP → KSampler → Image 3 + │ └──────→ CLIP → KSampler → Image 2 + └─────────→ CLIP → KSampler → Image 1 +``` + +**Time**: 1 minute setup +**Output**: 4 comparison images + +--- + +### Workflow C: Product Photography + +**Goal**: Professional product shots + +``` +┌──────────────────┐ +│ Text Input │ +│ "watch on marble"│ +└────────┬─────────┘ + │ +┌────────▼────────────┐ +│ Preset Collection │ +│ preset: commercial │ +└─┬─┬─┬─┬────────────┘ + │ │ │ │ + │ │ │ └→ CLIP → KSampler → Shot 4 + │ │ └──→ CLIP → KSampler → Shot 3 + │ └────→ CLIP → KSampler → Shot 2 + └──────→ CLIP → KSampler → Shot 1 +``` + +**Time**: 30 seconds +**Output**: 4 optimized product shots + +--- + +### Workflow D: Styled Variation + +**Goal**: Same subject, different aesthetic + +``` +┌─────────────┐ +│ Text Input │ +└──────┬──────┘ + │ +┌──────▼──────────┐ +│ Lighting Modifier│ +│ modifier_id: 11 │ +│ (night) │ +└──────┬──────────┘ + │ +┌──────▼──────────┐ +│ Style Transform │ +│ style: cyberpunk│ +└──────┬──────────┘ + │ +┌──────▼──────┐ +│ CLIP Encode │ +└──────┬──────┘ + [PIPELINE] +``` + +**Time**: 1 minute +**Output**: 1 cyberpunk night scene + +--- + +## 🎨 The 20 Modifiers - Quick Reference + +### Most Popular (Use These First) + +| ID | Name | When to Use | +|----|------|-------------| +| **7** | Golden Hour | 🌟 DEFAULT - Use for everything | +| **18** | Blue Hour Wet | Dramatic urban/car scenes | +| **4** | Midday | Product photography | +| **8** | Sunset | Romantic/warm scenes | +| **10** | Blue Hour | Moody atmospheres | + +### Complete List + +**DRY (Clear Sky, 1-16)**: +- 1-3: Morning (sunrise → late morning) +- 4-6: Afternoon (midday → late afternoon) +- 7-9: Evening (golden hour → twilight) +- 10-12: Night (blue hour → moonlit) +- 13-16: Specialized angles + +**WET (Reflections, 17-20)**: +- 17-20: Wet road variations (predawn → night) + +📋 **Full list**: Add "Modifier Info" node and connect to "Show Text" + +--- + +## 🔧 Settings Guide + +### Lighting Modifier Settings + +**preserve_base**: +- ✅ **True** (recommended): Adds lighting to your prompt +- ❌ **False**: Replaces lighting keywords in prompt + +**When to use False**: If your prompt already has lighting terms you want to replace + +--- + +### Batch Variation Settings + +**category**: +- **"all"**: All 20 modifiers (use for full exploration) +- **"dry"**: 16 modifiers (clear weather only) +- **"wet"**: 4 modifiers (dramatic wet scenes only) +- **"custom"**: Your specific IDs + +**custom_ids** (when category="custom"): +``` +Examples: + "7" → Just golden hour + "1,7,18" → Sunrise, golden hour, blue hour wet + "3,4,11" → Late morning, midday, night +``` + +--- + +### Preset Collection Settings + +**preset**: +- **"key_moments"**: Best variety (6 vars) - 🌟 START HERE +- **"commercial"**: Product photos (4 vars) +- **"artistic"**: Creative projects (7 vars) +- **"full_day_cycle"**: Complete day (16 vars) +- **"wet_night"**: Dramatic night (4 vars) + +--- + +## 💰 Cost Saving Tips + +### Tip 1: Preview First +``` +[Text] → [Lighting Modifier] → [Show Text Node] +``` +See the prompt BEFORE rendering to avoid wasted generations. + +### Tip 2: Use Key Moments +Instead of all 20 modifiers (expensive!), use "key_moments" preset (6 variations). + +### Tip 3: Test with Low Steps +Use your batch variations with low step count first, then re-render winners at high quality. + +--- + +## ⚡ Pro Tips + +### Tip 1: Chain Modifiers for Complex Lighting +``` +[Text] → [Modifier 7: golden hour] → [Modifier 19: wet road] → [CLIP] +``` +Result: Golden hour with wet reflections! + +### Tip 2: Use Random Modifier for Exploration +``` +[Text] → [Random Modifier] → [Show "used_modifier_id"] → [CLIP] +``` +Let the system surprise you, then see which modifier worked! + +### Tip 3: Combine with Wildcards +``` +[Wildcard: "__car__"] → [Lighting Modifier] → [CLIP] +``` +Different cars, same perfect lighting! + +### Tip 4: Save Preset Workflows +Save workflow with your favorite modifier settings, load when needed. + +### Tip 5: Use Combiner for Complex Prompts +``` +[Subject] ──┐ +[Location] ─┤→ [Combiner] → [Lighting] → [CLIP] +[Details] ──┘ +``` + +--- + +## 🐛 Troubleshooting Quick Fixes + +### Problem: Nodes don't appear +**Fix**: Restart ComfyUI completely (not just refresh) + +### Problem: Output is empty +**Fix**: Check that text input is actually connected + +### Problem: All variations are the same +**Fix**: Use category="all" or specify more custom_ids + +### Problem: Style not working +**Fix**: Make sure you selected a style (not left on "custom" without custom_style text) + +--- + +## 📚 Learning Resources + +1. **This guide** - Basic usage +2. **NODE_REFERENCE.md** - Technical details +3. **README_COMFYUI.md** - Complete documentation +4. **Example workflows** - In `example_workflows/` folder + +--- + +## 🎓 5-Day Learning Plan + +### Day 1: Single Modifier +- Use Lighting Modifier node +- Try modifier 7 (golden hour) +- Generate 1-5 images + +### Day 2: Batch Comparison +- Use Batch Variation node +- Try category="key_moments" +- Compare results + +### Day 3: Styles +- Add Style Transform node +- Try "cyberpunk", "noir", "minimalist" +- Combine with lighting + +### Day 4: Presets +- Use Preset Collection +- Try "commercial" and "artistic" +- Find your favorite preset + +### Day 5: Advanced +- Chain multiple nodes +- Use Random Modifier +- Build custom workflows + +--- + +## 🏆 Success Checklist + +After this guide, you should be able to: + +- [ ] Install the nodes in ComfyUI +- [ ] Add Lighting Modifier to workflow +- [ ] Generate image with golden hour (modifier 7) +- [ ] Create batch variations +- [ ] Apply style transformations +- [ ] Use preset collections +- [ ] Combine multiple nodes +- [ ] Build your own workflows + +--- + +## 🚀 Ready to Start? + +### Absolute Beginner Path: +1. Install nodes +2. Add "Lighting Modifier (Single)" +3. Set modifier_id = 7 +4. Connect to CLIP +5. Generate! + +### Intermediate Path: +1. Install nodes +2. Add "Batch Variation Generator" +3. Set category = "key_moments" +4. Connect outputs to CLIPs +5. Compare 6 variations! + +### Advanced Path: +1. Install nodes +2. Chain Lighting → Style → Multi-Style +3. Create complex transformations +4. Build reusable workflows! + +--- + +## 📞 Next Steps + +- ✅ **Installed?** → Try your first workflow above +- ✅ **Working?** → Check the example workflows folder +- ✅ **Advanced?** → Read NODE_REFERENCE.md +- ✅ **Issues?** → Check INSTALLATION.md troubleshooting + +--- + +**Have fun creating amazing variations! 🎨** + +_Tip: Bookmark this page and refer back when learning new nodes._ diff --git a/README.md b/README.md new file mode 100644 index 0000000..9377582 --- /dev/null +++ b/README.md @@ -0,0 +1,621 @@ +# Image Prompt Orchestration System + +A powerful Python system for automating image prompt variations with 20 plug-and-play lighting modifiers and unlimited style transformations. + +## Features + +- **20 Pre-defined Lighting Modifiers** (anchor-agnostic, DRY/WET categories) +- **Style Transformation Engine** with 10+ built-in templates +- **Batch Processing** for multiple prompts +- **Smart Variation** with natural language intents +- **Multiple Export Formats** (JSON, CSV, TXT) +- **CLI Interface** for quick automation +- **Preset Collections** for common use cases + +--- + +## Quick Start + +### Installation + +```bash +# No external dependencies required - uses Python standard library +python prompt_orchestrator.py # Run demo +``` + +### Basic Usage (Python API) + +```python +from prompt_orchestrator import PromptOrchestrator + +orchestrator = PromptOrchestrator() + +# Apply a lighting modifier +base = "Sports car on coastal highway" +golden_hour = orchestrator.apply_lighting_modifier(base, 7) +print(golden_hour) +# → "Sports car on coastal highway, golden hour, warm low-angle side-light, clear sky" + +# Apply style transformation +cyberpunk = orchestrator.apply_style_transformation(base, "cyberpunk") +print(cyberpunk) +# → "Transform to cyberpunk aesthetic with neon lights... Base scene: Sports car..." + +# Smart variation with natural language +result = orchestrator.smart_variation(base, "make it a wet night scene") +print(result) +``` + +### Command Line Interface + +```bash +# Single modifier +python cli.py --prompt "Luxury sedan in urban setting" --modifier 7 + +# All DRY modifiers +python cli.py --prompt "Modern architecture" --category dry --output results.json + +# Style transformation +python cli.py --prompt "Vintage motorcycle" --style noir + +# Preset collection +python cli.py --prompt "Product photography" --preset commercial + +# Batch process from file +python cli.py --input-file prompts.txt --category all --output batch.csv + +# Smart variation +python cli.py --prompt "Car on street" --smart "golden hour with warm lighting" +``` + +--- + +## Lighting Modifiers Reference + +### DRY Category (1-16): Universal, Clear Conditions + +| ID | Description | +|----|-------------| +| 1 | sunrise, car-friendly side-light, clear sky | +| 2 | early morning, subtle back-light along the road axis, clear sky | +| 3 | late morning, high-sun hard light for crisp body lines, clear sky | +| 4 | midday, crisp hard sunlight, clear sky | +| 5 | early afternoon, gentle side-light, clear sky | +| 6 | late afternoon, long shadow geometry, clear sky | +| 7 | golden hour, warm low-angle side-light, clear sky | +| 8 | sunset, soft rim-lit edges, clear sky | +| 9 | twilight, residual glow, clear sky | +| 10 | blue hour, deep clear sky | +| 11 | night, clear sky with clean highlights | +| 12 | moonlit night, clear sky | +| 13 | early morning, three-quarter back-light, clear sky | +| 14 | late afternoon, subtle back-light along the road axis, clear sky | +| 15 | evening, car-friendly side-light, clear sky | +| 16 | dawn, clean low-haze air, clear sky | + +### WET Category (17-20): Low-Light, Wet Conditions + +| ID | Description | +|----|-------------| +| 17 | predawn blue hour, wet road, subtle back-light along the road axis | +| 18 | blue hour, wet road, car-friendly side-light | +| 19 | twilight, wet road, residual glow, clear sky | +| 20 | night, wet street, clear sky (buildings visible → warm interior lights glowing) | + +--- + +## Style Templates + +Built-in style transformations: + +- **pop_art_1960s** - Bright colors, bold graphics, commercial aesthetics +- **bauhaus** - Geometric forms, primary colors, modernist principles +- **art_deco** - Geometric patterns, luxurious details, streamlined forms +- **cyberpunk** - Neon lights, high-tech elements, dystopian atmosphere +- **minimalist** - Clean lines, negative space, essential elements only +- **impressionist** - Visible brushstrokes, emphasis on light +- **noir** - Dramatic shadows, high contrast, moody atmosphere +- **futuristic** - Sleek forms, advanced materials, sci-fi elements +- **vintage** - Retro colors, grain, nostalgic atmosphere +- **brutalist** - Raw concrete, geometric forms, bold structure + +*Custom styles can be added using free-form descriptions.* + +--- + +## API Documentation + +### PromptOrchestrator Class + +#### Core Methods + +**`apply_lighting_modifier(base_prompt, modifier_id, preserve_base=True)`** +```python +result = orchestrator.apply_lighting_modifier( + "Modern building exterior", + modifier_id=7, # Golden hour + preserve_base=True # Append vs replace +) +``` + +**`apply_style_transformation(base_prompt, style, maintain_composition=True)`** +```python +result = orchestrator.apply_style_transformation( + "Vintage car in garage", + style="cyberpunk", + maintain_composition=True +) +``` + +**`generate_variation_batch(base_prompt, modifier_ids=None, include_styles=None)`** +```python +variations = orchestrator.generate_variation_batch( + "Sports car on highway", + modifier_ids=[1, 7, 18], # Specific modifiers + include_styles=["noir", "cyberpunk"] +) +# Returns list of dicts with keys: id, type, category, description, prompt +``` + +**`smart_variation(base_prompt, intent)`** +```python +result = orchestrator.smart_variation( + "Car on street", + intent="make it golden hour with dramatic lighting" +) +``` + +#### Utility Methods + +**`get_modifiers_by_category(category: ModifierCategory)`** +```python +from prompt_orchestrator import ModifierCategory + +dry_mods = orchestrator.get_modifiers_by_category(ModifierCategory.DRY) +wet_mods = orchestrator.get_modifiers_by_category(ModifierCategory.WET) +``` + +**`get_modifiers_by_tags(tags: List[str])`** +```python +night_mods = orchestrator.get_modifiers_by_tags(["night", "moonlit"]) +``` + +**`export_variations(variations, format="txt")`** +```python +output = orchestrator.export_variations(variations, format="json") +print(output) +``` + +--- + +### BatchProcessor Class + +#### Methods + +**`process_single_prompt(base_prompt, config)`** +```python +from batch_processor import BatchProcessor + +processor = BatchProcessor(output_dir="my_prompts") + +config = { + "lighting_ids": "all", # or [1,7,18] or "dry" or "wet" + "styles": ["cyberpunk", "noir"], + "export_format": "json" +} + +variations = processor.process_single_prompt("Base prompt here", config) +``` + +**`process_multiple_prompts(prompts, config, output_filename)`** +```python +prompts = [ + "Sports car on highway", + "Modern building exterior", + "Product photography setup" +] + +output_path = processor.process_multiple_prompts( + prompts=prompts, + config=config, + output_filename="batch_output" +) +``` + +**`create_preset_collection(base_prompt, preset)`** +```python +# Available presets: +# - "full_day_cycle": All DRY modifiers +# - "wet_night": All WET modifiers +# - "key_moments": Selected dramatic moments +# - "commercial": Best for commercial photography +# - "artistic": Artistic lighting + style variations + +variations = processor.create_preset_collection( + "Product shot", + preset="commercial" +) +``` + +**`generate_comparison_set(base_prompt, modifier_pairs)`** +```python +comparisons = processor.generate_comparison_set( + "Architecture shot", + modifier_pairs=[ + (4, 7), # Midday vs Golden Hour + (11, 18) # Night dry vs Night wet + ] +) +``` + +--- + +## Use Cases & Examples + +### 1. Automotive Photography Variations + +```python +orchestrator = PromptOrchestrator() + +base = "Luxury sports car on mountain road, dramatic landscape" + +# Generate complete day cycle +variations = orchestrator.generate_variation_batch( + base, + modifier_ids=list(range(1, 17)) # All DRY modifiers +) + +# Add wet night variations for drama +wet_variations = orchestrator.generate_variation_batch( + base, + modifier_ids=[17, 18, 19, 20] +) +``` + +### 2. Product Photography Set + +```python +processor = BatchProcessor() + +products = [ + "Smartwatch on minimal white surface", + "Luxury perfume bottle with reflections", + "Designer sunglasses, clean background" +] + +# Use commercial preset for all +for product in products: + vars = processor.create_preset_collection(product, "commercial") + # vars contains optimal lighting for product shots +``` + +### 3. Architectural Visualization + +```python +base = "Modern glass office building, urban context" + +# Morning through evening progression +morning_to_evening = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + +variations = orchestrator.generate_variation_batch( + base, + modifier_ids=morning_to_evening +) + +# Add style variations +style_vars = orchestrator.generate_variation_batch( + base, + modifier_ids=[7], # Just golden hour + include_styles=["brutalist", "minimalist", "futuristic"] +) +``` + +### 4. Batch Processing from File + +Create `prompts.txt`: +``` +Sports car on coastal highway +Modern architecture, glass facade +Vintage motorcycle in garage +Product photography, minimal setup +``` + +Then process: +```bash +python cli.py --input-file prompts.txt --category all --output results.csv +``` + +### 5. Smart Variation Examples + +```python +base = "Car parked on city street" + +# Natural language variations +variants = [ + orchestrator.smart_variation(base, "make it golden hour"), + orchestrator.smart_variation(base, "convert to wet night scene"), + orchestrator.smart_variation(base, "cyberpunk style"), + orchestrator.smart_variation(base, "morning sunrise lighting") +] +``` + +--- + +## Advanced Features + +### Custom Pipelines + +Create custom transformation sequences: + +```python +def add_fog(prompt): + return f"{prompt}, atmospheric fog, reduced visibility" + +def add_motion_blur(prompt): + return f"{prompt}, motion blur effect, dynamic" + +# Apply custom pipeline +result = orchestrator.create_custom_pipeline( + "Sports car on track", + transformations=[ + lambda p: orchestrator.apply_lighting_modifier(p, 7), + add_fog, + add_motion_blur + ] +) +``` + +### Filtering and Querying + +```python +# Get all night modifiers +night_mods = orchestrator.get_modifiers_by_tags(["night"]) + +# Get all side-light modifiers +side_light = orchestrator.get_modifiers_by_tags(["side-light"]) + +# Combine multiple tags +dramatic = orchestrator.get_modifiers_by_tags(["golden-hour", "sunset", "blue-hour"]) +``` + +### Comparison Sets for A/B Testing + +```python +processor = BatchProcessor() + +# Generate comparison pairs +comparisons = processor.generate_comparison_set( + "Product on pedestal", + modifier_pairs=[ + (3, 7), # Hard light vs Golden hour + (4, 11), # Midday vs Night + (7, 18) # Golden hour dry vs Blue hour wet + ] +) + +# Each comparison contains variant_a and variant_b with full prompts +``` + +--- + +## CLI Command Reference + +### List Commands +```bash +# List all modifiers +python cli.py --list-modifiers + +# List all styles +python cli.py --list-styles +``` + +### Single Prompt Commands +```bash +# Apply specific modifier +python cli.py -p "Your prompt here" -m 7 + +# Apply category +python cli.py -p "Your prompt" -c dry + +# Apply multiple specific modifiers +python cli.py -p "Your prompt" --modifiers 1,7,18 + +# Apply style +python cli.py -p "Your prompt" -s cyberpunk + +# Use preset +python cli.py -p "Your prompt" --preset key_moments + +# Smart variation +python cli.py -p "Your prompt" --smart "make it golden hour" +``` + +### Batch Commands +```bash +# Process file with all modifiers +python cli.py -i prompts.txt -c all -o results.json + +# Process file with DRY only +python cli.py -i prompts.txt -c dry -f csv -o dry_only.csv + +# Process with preset +python cli.py -i prompts.txt --preset commercial -o commercial.txt +``` + +### Output Options +```bash +# Print to stdout +python cli.py -p "Prompt" -m 7 --print-only + +# Save to specific file +python cli.py -p "Prompt" -c all -o my_results.json + +# Choose format explicitly +python cli.py -p "Prompt" -c all -f csv -o output.csv +``` + +--- + +## Export Formats + +### JSON Format +```json +[ + { + "base_prompt": "Sports car on highway", + "variations": [ + { + "id": "lighting_7", + "type": "lighting", + "category": "dry", + "description": "golden hour, warm low-angle side-light, clear sky", + "prompt": "Sports car on highway, golden hour, warm low-angle side-light, clear sky" + } + ] + } +] +``` + +### CSV Format +```csv +base_prompt,variation_id,type,category,description,prompt +"Sports car","lighting_7","lighting","dry","golden hour...","Sports car, golden hour..." +``` + +### TXT Format +``` +BASE PROMPT: Sports car on highway +================================================================================ + +[lighting_7] LIGHTING - dry +Description: golden hour, warm low-angle side-light, clear sky +Prompt: Sports car on highway, golden hour, warm low-angle side-light, clear sky +-------------------------------------------------------------------------------- +``` + +--- + +## Best Practices + +### 1. Choose the Right Modifier Category + +- **DRY modifiers (1-16)**: Universal, work with any subject +- **WET modifiers (17-20)**: Dramatic, best for urban/street scenes with reflections + +### 2. Layering Transformations + +```python +# Good: Layer lighting + style +base = orchestrator.apply_lighting_modifier("Base prompt", 7) +final = orchestrator.apply_style_transformation(base, "cyberpunk", maintain_composition=False) + +# Better: Use batch generation +variations = orchestrator.generate_variation_batch( + "Base prompt", + modifier_ids=[7], + include_styles=["cyberpunk"] +) +``` + +### 3. Preset Selection + +- **Commercial**: Product shots, crisp lighting (modifiers 3, 4, 7, 11) +- **Key Moments**: Portfolios, dramatic variety (1, 7, 8, 10, 18, 20) +- **Full Day Cycle**: Time-lapse visualization (all DRY modifiers) +- **Artistic**: Creative projects (golden hour + styles) + +### 4. Batch Processing Efficiency + +```python +# Efficient: Process all prompts with same config +processor.process_multiple_prompts( + prompts=large_prompt_list, + config=single_config, + output_filename="bulk_output" +) + +# Less efficient: Loop with individual processing +``` + +--- + +## Contributing & Customization + +### Adding Custom Modifiers + +```python +from prompt_orchestrator import LightingModifier, ModifierCategory + +custom_mod = LightingModifier( + id=21, + description="stormy weather, dramatic dark clouds, side-light", + category=ModifierCategory.DRY, + tags=["storm", "dramatic", "weather"] +) + +orchestrator.modifiers[21] = custom_mod +``` + +### Adding Custom Styles + +```python +orchestrator.custom_style_templates["my_style"] = \ + "custom aesthetic with specific characteristics" + +# Use it +result = orchestrator.apply_style_transformation(base, "my_style") +``` + +--- + +## Performance Notes + +- **In-memory processing**: All operations are fast, no external API calls +- **Batch operations**: Optimized for processing hundreds of prompts +- **No dependencies**: Uses only Python standard library + +--- + +## Troubleshooting + +**Q: My prompts are too long** +```python +# Use preserve_base=False to replace instead of append +result = orchestrator.apply_lighting_modifier(base, 7, preserve_base=False) +``` + +**Q: I need specific lighting combinations** +```python +# Create custom pipeline +def custom_combo(p): + # Your custom logic + return modified_prompt + +result = orchestrator.create_custom_pipeline(base, [custom_combo, ...]) +``` + +**Q: Smart variation not working as expected** +```python +# Smart variation uses keyword matching - be specific +# Good: "make it golden hour with warm side light" +# Less good: "make it nice" +``` + +--- + +## License + +MIT License - Free for commercial and personal use + +## Support + +For issues or feature requests, please provide: +1. Your base prompt +2. Desired modifier/transformation +3. Expected vs actual output + +--- + +## Version History + +- **v1.0** - Initial release with 20 modifiers, style system, batch processing, CLI diff --git a/README_COMFYUI.md b/README_COMFYUI.md new file mode 100644 index 0000000..f61e8fb --- /dev/null +++ b/README_COMFYUI.md @@ -0,0 +1,538 @@ +# Prompt Orchestration Nodes for ComfyUI + +Advanced prompt manipulation system integrated into ComfyUI workflows. Control lighting, style, and variations with precision using 20 pre-defined modifiers and unlimited transformations. + +--- + +## 🎯 What This Does + +Brings professional prompt engineering capabilities directly into your ComfyUI workflows: + +- **20 Lighting Modifiers**: Sunrise to moonlit night, dry and wet conditions +- **Style Transformations**: Cyberpunk, noir, minimalist, and custom styles +- **Smart Variation**: Natural language prompt modification +- **Batch Generation**: Multiple variations from single input +- **Preset Collections**: Optimized modifier sets for common use cases + +--- + +## 📦 Available Nodes (9 Total) + +All nodes appear under the **"Prompt Orchestration"** category. + +### 1. Lighting Modifier (Single) +**Purpose**: Apply one lighting modifier to a prompt + +**Inputs**: +- `text`: Your base prompt +- `modifier_id`: Number 1-20 (see modifier reference below) +- `preserve_base`: Append vs replace (default: True) + +**Output**: Modified prompt with lighting applied + +**Example Use**: +``` +Input: "Sports car on highway" +Modifier: 7 (golden hour) +Output: "Sports car on highway, golden hour, warm low-angle side-light, clear sky" +``` + +### 2. Batch Variation Generator +**Purpose**: Generate multiple variations at once + +**Inputs**: +- `text`: Base prompt +- `category`: "all", "dry", "wet", or "custom" +- `custom_ids`: Comma-separated IDs if using "custom" (e.g., "1,7,18") + +**Outputs**: Up to 10 variation strings (var_1 through var_10) + +**Example Use**: +Connect each output to a different CLIP Text Encode for batch image generation. + +### 3. Style Transform +**Purpose**: Apply artistic style to prompt + +**Inputs**: +- `text`: Base prompt +- `style`: Choose from 11 built-in styles or "custom" +- `maintain_composition`: Keep original composition (default: True) +- `custom_style`: Your custom style description (if style="custom") + +**Output**: Style-transformed prompt + +**Built-in Styles**: +- cyberpunk, noir, minimalist, pop_art_1960s +- bauhaus, art_deco, impressionist, futuristic +- vintage, brutalist + +### 4. Smart Variation (NLP) +**Purpose**: Natural language prompt modification + +**Inputs**: +- `text`: Base prompt +- `intent`: What you want (e.g., "make it golden hour", "add wet street") + +**Output**: Intelligently modified prompt + +**Example Intents**: +- "make it golden hour" +- "convert to wet night scene" +- "add cyberpunk style" +- "morning sunrise lighting" + +### 5. Preset Collection +**Purpose**: Apply optimized modifier sets + +**Inputs**: +- `text`: Base prompt +- `preset`: Choose from 5 presets + +**Outputs**: 8 variations optimized for the use case + +**Available Presets**: +- `key_moments`: Dramatic lighting variety (6 variations) +- `commercial`: Product photography optimized (4 variations) +- `artistic`: Creative with styles (7 variations) +- `full_day_cycle`: Complete day progression (16 variations) +- `wet_night`: Dramatic wet conditions (4 variations) + +### 6. Modifier Info +**Purpose**: Display information about a modifier + +**Inputs**: +- `modifier_id`: Number 1-20 + +**Outputs**: +- `description`: Full modifier description +- `category`: "dry" or "wet" +- `tags`: Comma-separated tags + +**Use**: Connect to a text display to see what modifiers do. + +### 7. Prompt Combiner +**Purpose**: Merge multiple text inputs + +**Inputs**: +- `text_1` through `text_4`: Text pieces to combine +- `separator`: String to join with (default: ", ") + +**Output**: Combined text + +**Example**: +``` +text_1: "Sports car" +text_2: "on mountain road" +text_3: "golden hour" +separator: ", " +Output: "Sports car, on mountain road, golden hour" +``` + +### 8. Random Modifier +**Purpose**: Apply random modifier from category + +**Inputs**: +- `text`: Base prompt +- `category`: "all", "dry", or "wet" +- `seed`: Random seed for reproducibility + +**Outputs**: +- `modified_prompt`: Result with random modifier +- `used_modifier_id`: Which modifier was selected + +**Use**: Create diverse variations with randomness. + +### 9. Multi-Style Layer +**Purpose**: Apply multiple styles sequentially + +**Inputs**: +- `text`: Base prompt +- `style_1`, `style_2`, `style_3`: Up to 3 styles + +**Output**: Prompt with layered styles + +**Use**: Create complex aesthetic combinations. + +--- + +## 🌟 The 20 Lighting Modifiers + +### DRY Category (1-16): Clear Conditions + +| ID | Time/Condition | Description | +|----|---------------|-------------| +| 1 | Sunrise | car-friendly side-light, clear sky | +| 2 | Early Morning | subtle back-light along road axis, clear sky | +| 3 | Late Morning | high-sun hard light for crisp body lines | +| 4 | Midday | crisp hard sunlight, clear sky | +| 5 | Early Afternoon | gentle side-light, clear sky | +| 6 | Late Afternoon | long shadow geometry, clear sky | +| 7 | **Golden Hour** ⭐ | warm low-angle side-light (most popular) | +| 8 | Sunset | soft rim-lit edges, clear sky | +| 9 | Twilight | residual glow, clear sky | +| 10 | Blue Hour | deep clear sky | +| 11 | Night | clear sky with clean highlights | +| 12 | Moonlit Night | clear sky | +| 13 | Early Morning | three-quarter back-light, clear sky | +| 14 | Late Afternoon | subtle back-light along road axis | +| 15 | Evening | car-friendly side-light, clear sky | +| 16 | Dawn | clean low-haze air, clear sky | + +### WET Category (17-20): Dramatic Wet Conditions + +| ID | Condition | Description | +|----|-----------|-------------| +| 17 | Predawn Blue Hour | wet road, subtle back-light | +| 18 | **Blue Hour Wet** ⭐ | wet road, car-friendly side-light (dramatic) | +| 19 | Twilight Wet | wet road, residual glow | +| 20 | Night Wet | wet street, warm interior lights glowing | + +--- + +## 🔧 Common Workflow Patterns + +### Pattern 1: Single Image with Lighting + +``` +[Text Input] → [Lighting Modifier] → [CLIP Text Encode] → [KSampler] +``` + +Set modifier_id to 7 for golden hour (most versatile). + +### Pattern 2: Batch Variations + +``` +[Text Input] → [Batch Variation Generator] → [Multiple CLIP Text Encode nodes] + → [KSamplers for each variation] +``` + +Connect each var_output to a separate CLIP encoder. + +### Pattern 3: Style + Lighting Combo + +``` +[Text Input] → [Lighting Modifier] → [Style Transform] → [CLIP Text Encode] +``` + +Apply lighting first, then style for best results. + +### Pattern 4: Smart Creative Workflow + +``` +[Text Input] → [Smart Variation] → [CLIP Text Encode] +``` + +Use natural language like "make it cyberpunk golden hour". + +### Pattern 5: Preset Collection for Comparison + +``` +[Text Input] → [Preset Collection] → [8 separate render paths] +``` + +Generate 8 variations optimized for a specific use case. + +### Pattern 6: Random Exploration + +``` +[Text Input] → [Random Modifier] → [CLIP Text Encode] +[Seed Control] ↓ + [Display Used ID] +``` + +Generate different lighting each run, track which modifier was used. + +--- + +## 💡 Usage Tips + +### Best Practices + +1. **Start with Golden Hour (7)**: Most universally flattering +2. **Wet modifiers (17-20)** for dramatic urban/automotive scenes +3. **Use presets** for quick, professional results +4. **Layer styles carefully**: Apply lighting before styles +5. **Batch testing**: Use Batch Variation Generator for exploration + +### Modifier Selection Guide + +**For Products**: +- Use modifiers: 3, 4, 7, 11 +- Or use "commercial" preset + +**For Automotive**: +- Morning/evening: 1, 2, 7, 8 +- Dramatic: 18, 20 (wet conditions) + +**For Architecture**: +- Full day: 1-16 (all DRY) +- Dramatic: 7, 8, 10, 18 + +**For Portraits**: +- Flattering: 7, 8, 15 +- Dramatic: 10, 18 + +### Performance Tips + +1. **Batch node outputs**: Can connect to batch processing nodes +2. **Seed control**: Use fixed seeds with Random Modifier for reproducibility +3. **Text preview**: Use Show Text nodes to preview before rendering +4. **Save presets**: Save workflow variations for different modifier sets + +--- + +## 📋 Example Workflows + +### Example 1: Golden Hour Car Photography + +``` +[Prompt: "luxury sports car mountain road"] + ↓ +[Lighting Modifier: modifier_id=7] + ↓ +[CLIP Text Encode] + ↓ +[KSampler] +``` + +**Result**: Car with warm golden hour lighting + +### Example 2: Cyberpunk Style Variation + +``` +[Prompt: "city street night scene"] + ↓ +[Lighting Modifier: modifier_id=11 (night)] + ↓ +[Style Transform: style=cyberpunk] + ↓ +[CLIP Text Encode] +``` + +**Result**: Night scene with cyberpunk aesthetic + +### Example 3: Multi-Variation Comparison + +``` +[Prompt: "product on pedestal"] + ↓ +[Preset Collection: preset=commercial] + ↓ +[var_1] → [CLIP] → [KSampler 1] +[var_2] → [CLIP] → [KSampler 2] +[var_3] → [CLIP] → [KSampler 3] +[var_4] → [CLIP] → [KSampler 4] +``` + +**Result**: 4 product shots with optimal lighting + +### Example 4: Smart Natural Language + +``` +[Prompt: "architectural exterior"] + ↓ +[Smart Variation: intent="make it golden hour with dramatic shadows"] + ↓ +[CLIP Text Encode] +``` + +**Result**: Automatically applies golden hour + emphasis on shadows + +### Example 5: Random Daily Inspiration + +``` +[Prompt: "creative scene"] + ↓ +[Random Modifier: category=all, seed=] + ↓ +[Modifier Info: ] + ↓ +[CLIP Text Encode] +``` + +**Result**: Different lighting each day, see which modifier was chosen + +--- + +## 🔄 Integration with Other Nodes + +### With Wildcard Nodes + +``` +[Wildcard: "__subject__"] → [Lighting Modifier] → [CLIP] +``` + +Combine dynamic subjects with lighting control. + +### With Lora/Checkpoint Switching + +``` +[Text] → [Batch Variation] → [CLIP per variation] + → [Different LoRAs per output] +``` + +Test same prompt with different lighting and models. + +### With Conditioning Combine + +``` +[Base Prompt] → [Lighting Modifier] → [CLIP] ─┐ +[Detail Prompt] → [CLIP] ────────────────────┬→ [Conditioning Combine] +``` + +Layer lighting-modified base with detailed conditioning. + +--- + +## 🎨 Advanced Techniques + +### Technique 1: Progressive Lighting + +Use multiple Lighting Modifier nodes in sequence to build complex lighting: + +``` +[Base] → [Modifier 7: golden hour] → [Modifier 19: wet road] → [Result] +``` + +Creates golden hour with wet reflections. + +### Technique 2: Style Layering + +``` +[Base] → [Style: noir] → [Style: minimalist] → [Multi-layered aesthetic] +``` + +Use Multi-Style Layer node or chain Style Transform nodes. + +### Technique 3: Conditional Lighting + +``` + ┌→ [Modifier 7] → [Day scene] +[Base] → [Switch] ──┤ + └→ [Modifier 18] → [Night scene] +``` + +Use switch nodes to choose lighting based on conditions. + +### Technique 4: Batch A/B Testing + +``` +[Prompt] → [Batch Variation: custom_ids="7,18"] + ↓ +[var_1: golden hour] → [Render] +[var_2: blue hour wet] → [Render] +``` + +Compare specific modifiers side-by-side. + +--- + +## 📊 Node Reference Quick Chart + +| Node | Input Type | Output Type | Best For | +|------|-----------|-------------|----------| +| Lighting Modifier | Single text | Single text | Precise control | +| Batch Variation | Single text | 10 texts | Exploration | +| Style Transform | Single text | Single text | Aesthetic change | +| Smart Variation | Single text | Single text | Quick natural edits | +| Preset Collection | Single text | 8 texts | Professional sets | +| Modifier Info | Number | 3 texts | Understanding modifiers | +| Prompt Combiner | 4 texts | Single text | Building prompts | +| Random Modifier | Text + seed | Text + number | Randomization | +| Multi-Style | Text + styles | Single text | Complex aesthetics | + +--- + +## 🐛 Troubleshooting + +### Issue: Node outputs empty text + +**Cause**: Invalid modifier ID or missing inputs +**Solution**: Check modifier_id is 1-20, ensure text input is connected + +### Issue: Batch outputs are all the same + +**Cause**: Not enough modifiers in selected category +**Solution**: Use "all" category or specify more custom_ids + +### Issue: Style not applying + +**Cause**: Style name typo or "custom" selected without custom_style input +**Solution**: Double-check style dropdown selection + +### Issue: Smart Variation not working as expected + +**Cause**: Intent too vague or unrecognized keywords +**Solution**: Use specific terms like "golden hour", "wet", "night", "cyberpunk" + +--- + +## 🎓 Learning Path + +1. **Start Simple**: Try Lighting Modifier with modifier_id=7 +2. **Explore**: Use Batch Variation with category="all" +3. **Style**: Add Style Transform node after Lighting Modifier +4. **Optimize**: Use Preset Collection for your use case +5. **Advanced**: Combine multiple nodes for complex effects + +--- + +## 💾 Workflow JSON Examples + +See the `example_workflows/` folder for: +- `basic_lighting.json` - Simple lighting modifier workflow +- `batch_comparison.json` - Multi-variation comparison +- `style_transform.json` - Style transformation workflow +- `commercial_product.json` - Product photography setup +- `random_exploration.json` - Random modifier with seed control + +--- + +## 🔗 Related Resources + +- **Core System Docs**: `../README.md` +- **Installation Guide**: `INSTALLATION.md` +- **Node Reference**: `NODE_REFERENCE.md` +- **Python API**: `../prompt_orchestrator.py` + +--- + +## 📝 Version Info + +- **Version**: 1.0.0 +- **ComfyUI Compatibility**: Latest version +- **Python**: 3.7+ +- **Dependencies**: None (stdlib only) + +--- + +## 🚀 Quick Start Workflow + +**Create your first workflow in 3 steps:** + +1. **Add nodes**: + - Add Node → Prompt Orchestration → Lighting Modifier (Single) + - Connect your text input + +2. **Configure**: + - Set modifier_id: 7 (golden hour) + - Keep preserve_base: True + +3. **Connect**: + - Output → CLIP Text Encode → Your image generation pipeline + +**Result**: Prompt with professional golden hour lighting! + +--- + +## 📄 License + +MIT License - Free for commercial and personal use + +--- + +**Need Help?** +Check `INSTALLATION.md` for setup issues or the core `README.md` for modifier details. + +**Ready to use?** +Start with the Lighting Modifier node and modifier #7 (golden hour)! 🌅 diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..0706482 --- /dev/null +++ b/__init__.py @@ -0,0 +1,22 @@ +""" +Prompt Orchestration System - ComfyUI Custom Nodes +Integration package for advanced prompt manipulation in ComfyUI workflows +""" + +from .prompt_orchestration_nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS + +__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] + +# Version info +__version__ = "1.0.0" +__author__ = "Prompt Orchestration System" +__description__ = "Advanced prompt manipulation with 20 lighting modifiers and unlimited style transformations" + +# Print loading confirmation +print(f"\n{'='*80}") +print(f"Prompt Orchestration Nodes v{__version__} loaded successfully!") +print(f" - 9 custom nodes available") +print(f" - 20 lighting modifiers (DRY/WET categories)") +print(f" - 10+ built-in style templates") +print(f" - Category: 'Prompt Orchestration'") +print(f"{'='*80}\n") \ No newline at end of file diff --git a/batch_processor.py b/batch_processor.py new file mode 100644 index 0000000..b79d533 --- /dev/null +++ b/batch_processor.py @@ -0,0 +1,311 @@ +""" +Batch Processing Script for Image Prompt Variations +Automates generation of multiple prompt variations from input templates +""" + +import json +from pathlib import Path +from typing import List, Dict, Optional +from prompt_orchestrator import PromptOrchestrator, ModifierCategory + + +class BatchProcessor: + """Handles batch processing of image prompts with various automation features""" + + def __init__(self, output_dir: str = "output"): + """ + Initialize batch processor. + + Args: + output_dir: Directory to save output files + """ + self.orchestrator = PromptOrchestrator() + self.output_dir = Path(output_dir) + self.output_dir.mkdir(exist_ok=True) + + def process_single_prompt(self, base_prompt: str, config: Dict) -> List[Dict]: + """ + Process a single prompt with specified configuration. + + Args: + base_prompt: The base prompt to vary + config: Configuration dict with keys: + - lighting_ids: List of modifier IDs (or "all", "dry", "wet") + - styles: List of style names to apply + - export_format: Output format (txt, json, csv) + + Returns: + List of variation dictionaries + """ + # Determine which lighting modifiers to use + if config.get("lighting_ids") == "all": + modifier_ids = list(range(1, 21)) + elif config.get("lighting_ids") == "dry": + dry_mods = self.orchestrator.get_modifiers_by_category(ModifierCategory.DRY) + modifier_ids = [m.id for m in dry_mods] + elif config.get("lighting_ids") == "wet": + wet_mods = self.orchestrator.get_modifiers_by_category(ModifierCategory.WET) + modifier_ids = [m.id for m in wet_mods] + else: + modifier_ids = config.get("lighting_ids", []) + + # Generate variations + variations = self.orchestrator.generate_variation_batch( + base_prompt=base_prompt, + modifier_ids=modifier_ids if modifier_ids else None, + include_styles=config.get("styles", None) + ) + + return variations + + def process_multiple_prompts(self, prompts: List[str], config: Dict, + output_filename: str = "batch_output") -> str: + """ + Process multiple base prompts and save results. + + Args: + prompts: List of base prompts + config: Configuration dictionary + output_filename: Name for output file (without extension) + + Returns: + Path to output file + """ + all_results = [] + + for idx, prompt in enumerate(prompts, 1): + variations = self.process_single_prompt(prompt, config) + all_results.append({ + "base_prompt_id": idx, + "base_prompt": prompt, + "variations": variations + }) + + # Export results + export_format = config.get("export_format", "json") + output_path = self.output_dir / f"{output_filename}.{export_format}" + + if export_format == "json": + with open(output_path, "w", encoding="utf-8") as f: + json.dump(all_results, f, indent=2) + elif export_format == "txt": + with open(output_path, "w", encoding="utf-8") as f: + for result in all_results: + f.write(f"\n{'=' * 80}\n") + f.write(f"BASE PROMPT #{result['base_prompt_id']}: {result['base_prompt']}\n") + f.write(f"{'=' * 80}\n\n") + for var in result['variations']: + f.write(f"[{var['id']}] {var['type'].upper()} - {var['category']}\n") + f.write(f" Description: {var['description']}\n") + f.write(f" Prompt: {var['prompt']}\n\n") + elif export_format == "csv": + with open(output_path, "w", encoding="utf-8") as f: + f.write("base_prompt_id,base_prompt,variation_id,type,category,description,prompt\n") + for result in all_results: + for var in result['variations']: + f.write(f"{result['base_prompt_id']},\"{result['base_prompt']}\",") + f.write(f"{var['id']},{var['type']},{var['category']},") + f.write(f"\"{var['description']}\",\"{var['prompt']}\"\n") + + return str(output_path) + + def create_preset_collection(self, base_prompt: str, preset: str) -> List[Dict]: + """ + Apply a preset collection of variations. + + Presets: + - "full_day_cycle": All DRY modifiers (complete day cycle) + - "wet_night": All WET modifiers (dramatic night scenes) + - "key_moments": Selected dramatic lighting moments + - "commercial": Best modifiers for commercial photography + + Args: + base_prompt: The base prompt + preset: Preset name + + Returns: + List of variations + """ + preset_configs = { + "full_day_cycle": { + "lighting_ids": "dry", + "styles": None + }, + "wet_night": { + "lighting_ids": "wet", + "styles": None + }, + "key_moments": { + "lighting_ids": [1, 7, 8, 10, 18, 20], # Sunrise, golden hour, sunset, blue hours, wet night + "styles": None + }, + "commercial": { + "lighting_ids": [3, 4, 7, 11], # Crisp lighting for product shots + "styles": None + }, + "artistic": { + "lighting_ids": [7, 8, 9, 10], + "styles": ["impressionist", "minimalist", "art_deco"] + } + } + + if preset not in preset_configs: + raise ValueError(f"Unknown preset: {preset}. Available: {list(preset_configs.keys())}") + + return self.process_single_prompt(base_prompt, preset_configs[preset]) + + def generate_comparison_set(self, base_prompt: str, + modifier_pairs: List[tuple]) -> List[Dict]: + """ + Generate side-by-side comparison prompts. + + Args: + base_prompt: Base prompt + modifier_pairs: List of (modifier_id_1, modifier_id_2) tuples + + Returns: + List of comparison variation sets + """ + comparisons = [] + + for mod_a, mod_b in modifier_pairs: + prompt_a = self.orchestrator.apply_lighting_modifier(base_prompt, mod_a) + prompt_b = self.orchestrator.apply_lighting_modifier(base_prompt, mod_b) + + comparisons.append({ + "comparison_type": f"modifier_{mod_a}_vs_{mod_b}", + "variant_a": { + "modifier_id": mod_a, + "description": self.orchestrator.modifiers[mod_a].description, + "prompt": prompt_a + }, + "variant_b": { + "modifier_id": mod_b, + "description": self.orchestrator.modifiers[mod_b].description, + "prompt": prompt_b + } + }) + + return comparisons + + +def example_workflows(): + """Demonstrate various workflow scenarios""" + processor = BatchProcessor(output_dir="prompt_variations") + + print("BATCH PROCESSING WORKFLOWS\n") + print("=" * 80) + + # Workflow 1: Single prompt with all lighting variations + print("\n1. FULL LIGHTING CYCLE GENERATION") + print("-" * 80) + base_prompt_1 = "Luxury sedan in urban environment, architectural background" + + config_full = { + "lighting_ids": "all", + "export_format": "json" + } + + output_1 = processor.process_multiple_prompts( + prompts=[base_prompt_1], + config=config_full, + output_filename="full_lighting_cycle" + ) + print(f"✓ Generated full lighting cycle → {output_1}") + + # Workflow 2: Multiple prompts with preset + print("\n2. KEY MOMENTS PRESET (Multiple Prompts)") + print("-" * 80) + base_prompts = [ + "Sports car on mountain road, scenic vista", + "Classic convertible on beach, sunset backdrop", + "Electric SUV in modern city, glass buildings" + ] + + variations = [] + for prompt in base_prompts: + vars = processor.create_preset_collection(prompt, "key_moments") + variations.extend(vars) + + print(f"✓ Generated {len(variations)} variations across {len(base_prompts)} base prompts") + + # Workflow 3: Style transformations + print("\n3. STYLE TRANSFORMATION SET") + print("-" * 80) + base_prompt_3 = "Vintage motorcycle in garage, mechanical details" + + config_styles = { + "lighting_ids": [7], # Golden hour only + "styles": ["cyberpunk", "art_deco", "minimalist", "noir"], + "export_format": "txt" + } + + output_3 = processor.process_multiple_prompts( + prompts=[base_prompt_3], + config=config_styles, + output_filename="style_variations" + ) + print(f"✓ Generated style variations → {output_3}") + + # Workflow 4: Comparison set + print("\n4. COMPARISON SET GENERATION") + print("-" * 80) + base_prompt_4 = "Modern architecture, geometric forms" + + comparison_pairs = [ + (4, 7), # Midday vs Golden Hour + (11, 18), # Night dry vs Night wet + (1, 8) # Sunrise vs Sunset + ] + + comparisons = processor.generate_comparison_set(base_prompt_4, comparison_pairs) + + # Save comparisons + output_4 = processor.output_dir / "comparisons.json" + with open(output_4, "w", encoding="utf-8") as f: + json.dump(comparisons, f, indent=2) + + print(f"✓ Generated {len(comparisons)} comparison sets → {output_4}") + + # Workflow 5: Commercial photography set + print("\n5. COMMERCIAL PHOTOGRAPHY PRESET") + print("-" * 80) + base_prompt_5 = "Product shot of smartwatch, clean background" + + commercial_vars = processor.create_preset_collection(base_prompt_5, "commercial") + print(f"✓ Generated {len(commercial_vars)} commercial-grade variations") + + print("\n" + "=" * 80) + print("All workflows completed successfully!") + print(f"Output directory: {processor.output_dir}") + + +def quick_generation_example(): + """Quick example for immediate use""" + orchestrator = PromptOrchestrator() + + # Your base prompt + base = "Ferrari on coastal highway at sunset" + + print("\nQUICK GENERATION EXAMPLES") + print("=" * 80) + + # Generate 5 quick variations + variations = [ + orchestrator.apply_lighting_modifier(base, 1), # Sunrise + orchestrator.apply_lighting_modifier(base, 7), # Golden hour + orchestrator.apply_lighting_modifier(base, 10), # Blue hour + orchestrator.apply_lighting_modifier(base, 18), # Blue hour wet + orchestrator.apply_style_transformation(base, "cyberpunk") + ] + + for i, var in enumerate(variations, 1): + print(f"\n{i}. {var}") + + +if __name__ == "__main__": + # Run example workflows + example_workflows() + + # Quick generation + quick_generation_example() diff --git a/prompt_orchestration_nodes.py b/prompt_orchestration_nodes.py new file mode 100644 index 0000000..9d027be --- /dev/null +++ b/prompt_orchestration_nodes.py @@ -0,0 +1,520 @@ +""" +ComfyUI Custom Nodes - Image Prompt Orchestration System +Integrates the 20-modifier prompt orchestration system into ComfyUI workflows +""" + +import sys +import os +from pathlib import Path + +# Add parent directory to path to import orchestrator +current_dir = Path(__file__).parent +sys.path.insert(0, str(current_dir)) + +try: + from prompt_orchestrator import PromptOrchestrator, ModifierCategory + from batch_processor import BatchProcessor +except ImportError: + print("ERROR: Could not import prompt_orchestrator. Make sure it's in the same directory.") + raise + + +class PromptLightingModifierNode: + """ + Apply a single lighting modifier to a text prompt. + Choose from 20 pre-defined lighting conditions. + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "text": ("STRING", { + "multiline": True, + "default": "Subject description here" + }), + "modifier_id": ("INT", { + "default": 7, + "min": 1, + "max": 20, + "step": 1, + "display": "number" + }), + "preserve_base": ("BOOLEAN", { + "default": True + }) + } + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("modified_prompt",) + FUNCTION = "apply_modifier" + CATEGORY = "Prompt Orchestration" + + def __init__(self): + self.orchestrator = PromptOrchestrator() + + def apply_modifier(self, text, modifier_id, preserve_base): + """Apply lighting modifier to the input text""" + result = self.orchestrator.apply_lighting_modifier( + text, + modifier_id, + preserve_base=preserve_base + ) + return (result,) + + +class PromptBatchVariationNode: + """ + Generate multiple prompt variations from a base prompt. + Outputs multiple strings for batch processing. + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "text": ("STRING", { + "multiline": True, + "default": "Subject description here" + }), + "category": (["all", "dry", "wet", "custom"],), + }, + "optional": { + "custom_ids": ("STRING", { + "default": "1,7,18", + "multiline": False + }) + } + } + + RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING", "STRING", + "STRING", "STRING", "STRING", "STRING") + RETURN_NAMES = ("var_1", "var_2", "var_3", "var_4", "var_5", + "var_6", "var_7", "var_8", "var_9", "var_10") + FUNCTION = "generate_variations" + CATEGORY = "Prompt Orchestration" + OUTPUT_IS_LIST = (True, True, True, True, True, True, True, True, True, True) + + def __init__(self): + self.orchestrator = PromptOrchestrator() + + def generate_variations(self, text, category, custom_ids=""): + """Generate multiple variations based on category""" + + # Determine modifier IDs + if category == "all": + modifier_ids = list(range(1, 21)) + elif category == "dry": + dry_mods = self.orchestrator.get_modifiers_by_category(ModifierCategory.DRY) + modifier_ids = [m.id for m in dry_mods] + elif category == "wet": + wet_mods = self.orchestrator.get_modifiers_by_category(ModifierCategory.WET) + modifier_ids = [m.id for m in wet_mods] + else: # custom + if custom_ids: + modifier_ids = [int(x.strip()) for x in custom_ids.split(",") if x.strip()] + else: + modifier_ids = [7] # Default to golden hour + + # Generate variations + variations = self.orchestrator.generate_variation_batch( + text, + modifier_ids=modifier_ids + ) + + # Extract prompts + prompts = [v['prompt'] for v in variations] + + # Pad to 10 outputs (ComfyUI requirement for fixed outputs) + while len(prompts) < 10: + prompts.append(text) # Use original if not enough variations + + # Return as lists (OUTPUT_IS_LIST=True) + return tuple([prompts[i:i+1] for i in range(10)]) + + +class PromptStyleTransformNode: + """ + Apply a style transformation to a prompt. + Choose from built-in styles or provide custom description. + """ + + @classmethod + def INPUT_TYPES(cls): + styles = [ + "custom", + "pop_art_1960s", + "bauhaus", + "art_deco", + "cyberpunk", + "minimalist", + "impressionist", + "noir", + "futuristic", + "vintage", + "brutalist" + ] + + return { + "required": { + "text": ("STRING", { + "multiline": True, + "default": "Subject description here" + }), + "style": (styles,), + "maintain_composition": ("BOOLEAN", { + "default": True + }) + }, + "optional": { + "custom_style": ("STRING", { + "default": "", + "multiline": False + }) + } + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("styled_prompt",) + FUNCTION = "apply_style" + CATEGORY = "Prompt Orchestration" + + def __init__(self): + self.orchestrator = PromptOrchestrator() + + def apply_style(self, text, style, maintain_composition, custom_style=""): + """Apply style transformation""" + + if style == "custom" and custom_style: + style_to_use = custom_style + else: + style_to_use = style + + result = self.orchestrator.apply_style_transformation( + text, + style_to_use, + maintain_composition=maintain_composition + ) + return (result,) + + +class PromptSmartVariationNode: + """ + Smart variation using natural language intent. + Example: "make it golden hour" or "add cyberpunk style" + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "text": ("STRING", { + "multiline": True, + "default": "Subject description here" + }), + "intent": ("STRING", { + "multiline": False, + "default": "make it golden hour" + }) + } + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("smart_prompt",) + FUNCTION = "smart_vary" + CATEGORY = "Prompt Orchestration" + + def __init__(self): + self.orchestrator = PromptOrchestrator() + + def smart_vary(self, text, intent): + """Apply smart variation based on natural language intent""" + result = self.orchestrator.smart_variation(text, intent) + return (result,) + + +class PromptPresetCollectionNode: + """ + Apply a preset collection of modifiers. + Optimized sets for common use cases. + """ + + @classmethod + def INPUT_TYPES(cls): + presets = [ + "key_moments", + "commercial", + "artistic", + "full_day_cycle", + "wet_night" + ] + + return { + "required": { + "text": ("STRING", { + "multiline": True, + "default": "Subject description here" + }), + "preset": (presets,) + } + } + + RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING", + "STRING", "STRING", "STRING") + RETURN_NAMES = ("var_1", "var_2", "var_3", "var_4", + "var_5", "var_6", "var_7", "var_8") + FUNCTION = "apply_preset" + CATEGORY = "Prompt Orchestration" + OUTPUT_IS_LIST = (True, True, True, True, True, True, True, True) + + def __init__(self): + self.processor = BatchProcessor() + + def apply_preset(self, text, preset): + """Apply preset collection""" + + variations = self.processor.create_preset_collection(text, preset) + prompts = [v['prompt'] for v in variations] + + # Pad to 8 outputs + while len(prompts) < 8: + prompts.append(text) + + # Return as lists + return tuple([prompts[i:i+1] for i in range(8)]) + + +class PromptModifierInfoNode: + """ + Display information about a specific modifier. + Useful for understanding what each modifier does. + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "modifier_id": ("INT", { + "default": 7, + "min": 1, + "max": 20, + "step": 1, + "display": "number" + }) + } + } + + RETURN_TYPES = ("STRING", "STRING", "STRING") + RETURN_NAMES = ("description", "category", "tags") + FUNCTION = "get_info" + CATEGORY = "Prompt Orchestration" + + def __init__(self): + self.orchestrator = PromptOrchestrator() + + def get_info(self, modifier_id): + """Get information about a modifier""" + + if modifier_id in self.orchestrator.modifiers: + modifier = self.orchestrator.modifiers[modifier_id] + description = modifier.description + category = modifier.category.value + tags = ", ".join(modifier.tags) + else: + description = "Invalid modifier ID" + category = "unknown" + tags = "" + + return (description, category, tags) + + +class PromptCombinerNode: + """ + Combine multiple text inputs with optional separator. + Useful for building complex prompts from parts. + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "text_1": ("STRING", { + "multiline": True, + "default": "" + }) + }, + "optional": { + "text_2": ("STRING", { + "multiline": True, + "default": "" + }), + "text_3": ("STRING", { + "multiline": True, + "default": "" + }), + "text_4": ("STRING", { + "multiline": True, + "default": "" + }), + "separator": ("STRING", { + "default": ", ", + "multiline": False + }) + } + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("combined_text",) + FUNCTION = "combine" + CATEGORY = "Prompt Orchestration" + + def combine(self, text_1, text_2="", text_3="", text_4="", separator=", "): + """Combine multiple text inputs""" + + texts = [t for t in [text_1, text_2, text_3, text_4] if t] + result = separator.join(texts) + return (result,) + + +class PromptRandomModifierNode: + """ + Apply a random modifier from a category. + Useful for generating diverse variations. + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "text": ("STRING", { + "multiline": True, + "default": "Subject description here" + }), + "category": (["all", "dry", "wet"],), + "seed": ("INT", { + "default": 0, + "min": 0, + "max": 0xffffffffffffffff + }) + } + } + + RETURN_TYPES = ("STRING", "INT") + RETURN_NAMES = ("modified_prompt", "used_modifier_id") + FUNCTION = "apply_random" + CATEGORY = "Prompt Orchestration" + + def __init__(self): + self.orchestrator = PromptOrchestrator() + + def apply_random(self, text, category, seed): + """Apply a random modifier from the selected category""" + + import random + random.seed(seed) + + # Get modifiers from category + if category == "all": + modifier_ids = list(range(1, 21)) + elif category == "dry": + dry_mods = self.orchestrator.get_modifiers_by_category(ModifierCategory.DRY) + modifier_ids = [m.id for m in dry_mods] + else: # wet + wet_mods = self.orchestrator.get_modifiers_by_category(ModifierCategory.WET) + modifier_ids = [m.id for m in wet_mods] + + # Select random modifier + selected_id = random.choice(modifier_ids) + + # Apply it + result = self.orchestrator.apply_lighting_modifier(text, selected_id) + + return (result, selected_id) + + +class PromptMultiStyleNode: + """ + Apply multiple styles sequentially. + Creates layered style transformations. + """ + + @classmethod + def INPUT_TYPES(cls): + styles = [ + "none", + "pop_art_1960s", + "bauhaus", + "art_deco", + "cyberpunk", + "minimalist", + "impressionist", + "noir", + "futuristic", + "vintage", + "brutalist" + ] + + return { + "required": { + "text": ("STRING", { + "multiline": True, + "default": "Subject description here" + }), + "style_1": (styles,), + }, + "optional": { + "style_2": (styles,), + "style_3": (styles,), + } + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("multi_styled_prompt",) + FUNCTION = "apply_multi_style" + CATEGORY = "Prompt Orchestration" + + def __init__(self): + self.orchestrator = PromptOrchestrator() + + def apply_multi_style(self, text, style_1, style_2="none", style_3="none"): + """Apply multiple styles sequentially""" + + result = text + + for style in [style_1, style_2, style_3]: + if style != "none": + result = self.orchestrator.apply_style_transformation( + result, + style, + maintain_composition=False + ) + + return (result,) + + +# Node class mappings for ComfyUI +NODE_CLASS_MAPPINGS = { + "PromptLightingModifier": PromptLightingModifierNode, + "PromptBatchVariation": PromptBatchVariationNode, + "PromptStyleTransform": PromptStyleTransformNode, + "PromptSmartVariation": PromptSmartVariationNode, + "PromptPresetCollection": PromptPresetCollectionNode, + "PromptModifierInfo": PromptModifierInfoNode, + "PromptCombiner": PromptCombinerNode, + "PromptRandomModifier": PromptRandomModifierNode, + "PromptMultiStyle": PromptMultiStyleNode, +} + +# Display name mappings +NODE_DISPLAY_NAME_MAPPINGS = { + "PromptLightingModifier": "Lighting Modifier (Single)", + "PromptBatchVariation": "Batch Variation Generator", + "PromptStyleTransform": "Style Transform", + "PromptSmartVariation": "Smart Variation (NLP)", + "PromptPresetCollection": "Preset Collection", + "PromptModifierInfo": "Modifier Info", + "PromptCombiner": "Prompt Combiner", + "PromptRandomModifier": "Random Modifier", + "PromptMultiStyle": "Multi-Style Layer", +} diff --git a/prompt_orchestrator.py b/prompt_orchestrator.py new file mode 100644 index 0000000..b7df1a8 --- /dev/null +++ b/prompt_orchestrator.py @@ -0,0 +1,377 @@ +""" +Image Prompt Orchestration System +Automated text input manipulation for image generation variations +""" + +from typing import List, Dict, Optional, Callable +from dataclasses import dataclass +from enum import Enum + + +class ModifierCategory(Enum): + """Categories for lighting/environment modifiers""" + DRY = "dry" + WET = "wet" + + +@dataclass +class LightingModifier: + """Represents a lighting/time-of-day modifier""" + id: int + description: str + category: ModifierCategory + tags: List[str] + + +class PromptOrchestrator: + """ + Main orchestration class for image prompt variation automation. + Handles text input manipulation and modifier application. + """ + + # 20 plug-and-play USER_MODIFIERS (anchor-agnostic) + MODIFIERS = [ + # DRY (universal, no location assumptions) + LightingModifier(1, "sunrise, car-friendly side-light, clear sky", ModifierCategory.DRY, + ["sunrise", "morning", "side-light", "clear"]), + LightingModifier(2, "early morning, subtle back-light along the road axis, clear sky", ModifierCategory.DRY, + ["early-morning", "back-light", "clear"]), + LightingModifier(3, "late morning, high-sun hard light for crisp body lines, clear sky", ModifierCategory.DRY, + ["late-morning", "hard-light", "crisp", "clear"]), + LightingModifier(4, "midday, crisp hard sunlight, clear sky", ModifierCategory.DRY, + ["midday", "hard-light", "clear"]), + LightingModifier(5, "early afternoon, gentle side-light, clear sky", ModifierCategory.DRY, + ["afternoon", "side-light", "clear"]), + LightingModifier(6, "late afternoon, long shadow geometry, clear sky", ModifierCategory.DRY, + ["afternoon", "shadows", "clear"]), + LightingModifier(7, "golden hour, warm low-angle side-light, clear sky", ModifierCategory.DRY, + ["golden-hour", "warm", "side-light", "clear"]), + LightingModifier(8, "sunset, soft rim-lit edges, clear sky", ModifierCategory.DRY, + ["sunset", "rim-light", "clear"]), + LightingModifier(9, "twilight, residual glow, clear sky", ModifierCategory.DRY, + ["twilight", "glow", "clear"]), + LightingModifier(10, "blue hour, deep clear sky", ModifierCategory.DRY, + ["blue-hour", "clear"]), + LightingModifier(11, "night, clear sky with clean highlights", ModifierCategory.DRY, + ["night", "clear", "highlights"]), + LightingModifier(12, "moonlit night, clear sky", ModifierCategory.DRY, + ["night", "moonlit", "clear"]), + LightingModifier(13, "early morning, three-quarter back-light, clear sky", ModifierCategory.DRY, + ["morning", "back-light", "clear"]), + LightingModifier(14, "late afternoon, subtle back-light along the road axis, clear sky", ModifierCategory.DRY, + ["afternoon", "back-light", "clear"]), + LightingModifier(15, "evening, car-friendly side-light, clear sky", ModifierCategory.DRY, + ["evening", "side-light", "clear"]), + LightingModifier(16, "dawn, clean low-haze air, clear sky", ModifierCategory.DRY, + ["dawn", "haze", "clear"]), + + # WET (low-light only; triggers wet mode) + LightingModifier(17, "predawn blue hour, wet road, subtle back-light along the road axis", ModifierCategory.WET, + ["predawn", "blue-hour", "wet", "back-light"]), + LightingModifier(18, "blue hour, wet road, car-friendly side-light", ModifierCategory.WET, + ["blue-hour", "wet", "side-light"]), + LightingModifier(19, "twilight, wet road, residual glow, clear sky", ModifierCategory.WET, + ["twilight", "wet", "glow"]), + LightingModifier(20, "night, wet street, clear sky (buildings visible → warm interior lights glowing)", ModifierCategory.WET, + ["night", "wet", "interior-lights"]), + ] + + def __init__(self): + """Initialize the orchestrator with default modifiers""" + self.modifiers = {m.id: m for m in self.MODIFIERS} + self.custom_style_templates = self._load_style_templates() + + def _load_style_templates(self) -> Dict[str, str]: + """Load predefined style transformation templates""" + return { + "pop_art_1960s": "1960s pop art style with bright colors, bold graphics, and commercial aesthetics", + "bauhaus": "Bauhaus style with geometric forms, primary colors, and modernist principles", + "art_deco": "Art Deco style with geometric patterns, luxurious details, and streamlined forms", + "cyberpunk": "cyberpunk aesthetic with neon lights, high-tech elements, and dystopian atmosphere", + "minimalist": "minimalist style with clean lines, negative space, and essential elements only", + "impressionist": "impressionist painting style with visible brushstrokes and emphasis on light", + "noir": "film noir style with dramatic shadows, high contrast, and moody atmosphere", + "futuristic": "futuristic design with sleek forms, advanced materials, and sci-fi elements", + "vintage": "vintage aesthetic with retro colors, grain, and nostalgic atmosphere", + "brutalist": "brutalist architecture style with raw concrete, geometric forms, and bold structure", + } + + def apply_lighting_modifier(self, base_prompt: str, modifier_id: int, + preserve_base: bool = True) -> str: + """ + Apply a lighting modifier to a base prompt. + + Args: + base_prompt: The original prompt text + modifier_id: ID of the modifier to apply (1-20) + preserve_base: If True, append modifier; if False, replace lighting info + + Returns: + Modified prompt string + """ + if modifier_id not in self.modifiers: + raise ValueError(f"Invalid modifier_id: {modifier_id}. Must be 1-20.") + + modifier = self.modifiers[modifier_id] + + if preserve_base: + # Append the modifier to maintain original composition + return f"{base_prompt}, {modifier.description}" + else: + # Replace lighting-related content + cleaned_prompt = self._strip_lighting_keywords(base_prompt) + return f"{cleaned_prompt}, {modifier.description}" + + def _strip_lighting_keywords(self, prompt: str) -> str: + """Remove common lighting/time-of-day keywords from prompt""" + lighting_keywords = [ + "sunrise", "sunset", "morning", "afternoon", "evening", "night", "twilight", + "blue hour", "golden hour", "dawn", "dusk", "midday", "noon", + "clear sky", "cloudy", "overcast", "back-light", "side-light", "rim-light", + "wet road", "wet street", "dry", "moonlit", "sunlit" + ] + + cleaned = prompt + for keyword in lighting_keywords: + # Case-insensitive removal + cleaned = cleaned.replace(keyword, "") + cleaned = cleaned.replace(keyword.title(), "") + cleaned = cleaned.replace(keyword.upper(), "") + + # Clean up extra commas and spaces + cleaned = " ".join(cleaned.split()) + cleaned = cleaned.replace(" ,", ",").replace(",,", ",") + cleaned = cleaned.strip(", ") + + return cleaned + + def apply_style_transformation(self, base_prompt: str, style: str, + maintain_composition: bool = True) -> str: + """ + Apply a style transformation to a prompt. + + Args: + base_prompt: The original prompt text + style: Style name (from templates) or custom style description + maintain_composition: Whether to explicitly state composition preservation + + Returns: + Style-transformed prompt string + """ + # Check if it's a predefined style + if style.lower().replace(" ", "_") in self.custom_style_templates: + style_desc = self.custom_style_templates[style.lower().replace(" ", "_")] + else: + style_desc = style + + if maintain_composition: + return f"Transform to {style_desc} while maintaining the original composition. Base scene: {base_prompt}" + else: + return f"{base_prompt}, {style_desc}" + + def generate_variation_batch(self, base_prompt: str, + modifier_ids: Optional[List[int]] = None, + include_styles: Optional[List[str]] = None) -> List[Dict[str, str]]: + """ + Generate a batch of prompt variations. + + Args: + base_prompt: The original prompt text + modifier_ids: List of modifier IDs to apply (None = all 20) + include_styles: List of style transformations to include (None = none) + + Returns: + List of dictionaries with 'id', 'type', 'description', and 'prompt' keys + """ + variations = [] + + # Generate lighting variations + if modifier_ids is None: + modifier_ids = list(range(1, 21)) + + for mod_id in modifier_ids: + modifier = self.modifiers[mod_id] + prompt = self.apply_lighting_modifier(base_prompt, mod_id) + variations.append({ + "id": f"lighting_{mod_id}", + "type": "lighting", + "category": modifier.category.value, + "description": modifier.description, + "prompt": prompt + }) + + # Generate style variations + if include_styles: + for idx, style in enumerate(include_styles, 1): + prompt = self.apply_style_transformation(base_prompt, style) + variations.append({ + "id": f"style_{idx}", + "type": "style", + "category": "transformation", + "description": style, + "prompt": prompt + }) + + return variations + + def get_modifiers_by_category(self, category: ModifierCategory) -> List[LightingModifier]: + """Get all modifiers of a specific category (DRY/WET)""" + return [m for m in self.modifiers.values() if m.category == category] + + def get_modifiers_by_tags(self, tags: List[str]) -> List[LightingModifier]: + """Get modifiers that match any of the specified tags""" + return [m for m in self.modifiers.values() + if any(tag in m.tags for tag in tags)] + + def create_custom_pipeline(self, base_prompt: str, + transformations: List[Callable]) -> str: + """ + Apply a custom sequence of transformations to a prompt. + + Args: + base_prompt: The original prompt text + transformations: List of transformation functions to apply in sequence + + Returns: + Final transformed prompt string + """ + result = base_prompt + for transform in transformations: + result = transform(result) + return result + + def export_variations(self, variations: List[Dict[str, str]], + format: str = "txt") -> str: + """ + Export variations to a formatted string. + + Args: + variations: List of variation dictionaries + format: Export format ('txt', 'json', 'csv') + + Returns: + Formatted string representation + """ + if format == "json": + import json + return json.dumps(variations, indent=2) + + elif format == "csv": + lines = ["id,type,category,description,prompt"] + for v in variations: + # Escape commas in fields + line = f"{v['id']},{v['type']},{v['category']},\"{v['description']}\",\"{v['prompt']}\"" + lines.append(line) + return "\n".join(lines) + + else: # txt format + lines = [] + for v in variations: + lines.append(f"[{v['id']}] {v['type'].upper()} - {v['category']}") + lines.append(f"Description: {v['description']}") + lines.append(f"Prompt: {v['prompt']}") + lines.append("-" * 80) + return "\n".join(lines) + + def smart_variation(self, base_prompt: str, intent: str) -> str: + """ + Apply intelligent variation based on natural language intent. + + Args: + base_prompt: The original prompt text + intent: Natural language description of desired variation + + Returns: + Modified prompt string + """ + intent_lower = intent.lower() + + # Detect time-of-day intents + time_mappings = { + "morning": [1, 2, 3], + "sunrise": [1], + "afternoon": [5, 6, 14], + "golden hour": [7], + "sunset": [8], + "evening": [15], + "night": [11, 12], + "twilight": [9], + "blue hour": [10, 17, 18], + "wet": [17, 18, 19, 20] + } + + for keyword, mod_ids in time_mappings.items(): + if keyword in intent_lower: + # Use first matching modifier + return self.apply_lighting_modifier(base_prompt, mod_ids[0]) + + # Detect style intents + if "style" in intent_lower or "aesthetic" in intent_lower: + for style_name in self.custom_style_templates.keys(): + if style_name.replace("_", " ") in intent_lower: + return self.apply_style_transformation(base_prompt, style_name) + + # Default: treat as custom style description + return self.apply_style_transformation(base_prompt, intent) + + +def demo_usage(): + """Demonstration of the orchestrator capabilities""" + orchestrator = PromptOrchestrator() + + # Base prompt example + base_prompt = "Modern sports car on coastal highway, dramatic ocean backdrop" + + print("=" * 80) + print("IMAGE PROMPT ORCHESTRATION SYSTEM - DEMO") + print("=" * 80) + print(f"\nBase Prompt: {base_prompt}\n") + + # Example 1: Apply single lighting modifier + print("EXAMPLE 1: Applying Golden Hour Lighting (Modifier #7)") + print("-" * 80) + golden_hour = orchestrator.apply_lighting_modifier(base_prompt, 7) + print(golden_hour) + print() + + # Example 2: Apply wet condition + print("EXAMPLE 2: Applying Blue Hour Wet Road (Modifier #18)") + print("-" * 80) + wet_night = orchestrator.apply_lighting_modifier(base_prompt, 18) + print(wet_night) + print() + + # Example 3: Style transformation + print("EXAMPLE 3: Style Transformation - Cyberpunk") + print("-" * 80) + cyberpunk = orchestrator.apply_style_transformation(base_prompt, "cyberpunk") + print(cyberpunk) + print() + + # Example 4: Generate batch variations + print("EXAMPLE 4: Batch Generation (First 5 Modifiers)") + print("-" * 80) + batch = orchestrator.generate_variation_batch(base_prompt, modifier_ids=[1, 2, 3, 4, 5]) + for variation in batch: + print(f"[{variation['id']}] {variation['description']}") + print(f" → {variation['prompt'][:100]}...") + print() + + # Example 5: Smart variation with natural language + print("EXAMPLE 5: Smart Variation with Natural Language") + print("-" * 80) + smart_result = orchestrator.smart_variation(base_prompt, "make it golden hour with warm lighting") + print(smart_result) + print() + + # Example 6: Get modifiers by category + print("EXAMPLE 6: All WET Category Modifiers") + print("-" * 80) + wet_mods = orchestrator.get_modifiers_by_category(ModifierCategory.WET) + for mod in wet_mods: + print(f"#{mod.id}: {mod.description}") + print() + + +if __name__ == "__main__": + demo_usage() diff --git a/test_nodes.py b/test_nodes.py new file mode 100644 index 0000000..93eeae6 --- /dev/null +++ b/test_nodes.py @@ -0,0 +1,324 @@ +#!/usr/bin/env python3 +""" +Test Suite for ComfyUI Prompt Orchestration Nodes +Validates that all nodes work correctly before deployment +""" + +import sys +from pathlib import Path + +# Add current directory to path +sys.path.insert(0, str(Path(__file__).parent)) + +from prompt_orchestration_nodes import ( + PromptLightingModifierNode, + PromptBatchVariationNode, + PromptStyleTransformNode, + PromptSmartVariationNode, + PromptPresetCollectionNode, + PromptModifierInfoNode, + PromptCombinerNode, + PromptRandomModifierNode, + PromptMultiStyleNode +) + + +def test_lighting_modifier(): + """Test single lighting modifier node""" + print("\n" + "="*80) + print("TEST 1: Lighting Modifier Node") + print("="*80) + + node = PromptLightingModifierNode() + + # Test with golden hour + result = node.apply_modifier( + text="Sports car on highway", + modifier_id=7, + preserve_base=True + ) + + print(f"Input: 'Sports car on highway'") + print(f"Modifier: 7 (golden hour)") + print(f"Output: {result[0]}") + + # Verify output contains expected elements + assert "Sports car on highway" in result[0], "Base text missing" + assert "golden hour" in result[0], "Modifier text missing" + + print("✅ PASSED") + return True + + +def test_batch_variation(): + """Test batch variation generator node""" + print("\n" + "="*80) + print("TEST 2: Batch Variation Generator") + print("="*80) + + node = PromptBatchVariationNode() + + # Test with custom IDs + results = node.generate_variations( + text="Modern building", + category="custom", + custom_ids="1,7,18" + ) + + print(f"Input: 'Modern building'") + print(f"Category: custom") + print(f"Custom IDs: 1,7,18") + print(f"Generated {len(results)} outputs") + + # Check first few variations + for i in range(min(3, len(results))): + print(f" var_{i+1}: {results[i][0][:80]}...") + + assert len(results) == 10, "Should return 10 outputs" + + print("✅ PASSED") + return True + + +def test_style_transform(): + """Test style transformation node""" + print("\n" + "="*80) + print("TEST 3: Style Transform Node") + print("="*80) + + node = PromptStyleTransformNode() + + result = node.apply_style( + text="Vintage car in garage", + style="cyberpunk", + maintain_composition=True + ) + + print(f"Input: 'Vintage car in garage'") + print(f"Style: cyberpunk") + print(f"Output: {result[0][:100]}...") + + assert "cyberpunk" in result[0].lower(), "Style not applied" + + print("✅ PASSED") + return True + + +def test_smart_variation(): + """Test smart variation with natural language""" + print("\n" + "="*80) + print("TEST 4: Smart Variation Node") + print("="*80) + + node = PromptSmartVariationNode() + + result = node.smart_vary( + text="Car on street", + intent="make it golden hour" + ) + + print(f"Input: 'Car on street'") + print(f"Intent: 'make it golden hour'") + print(f"Output: {result[0]}") + + assert "golden hour" in result[0].lower() or "warm" in result[0].lower(), "Intent not recognized" + + print("✅ PASSED") + return True + + +def test_preset_collection(): + """Test preset collection node""" + print("\n" + "="*80) + print("TEST 5: Preset Collection Node") + print("="*80) + + node = PromptPresetCollectionNode() + + results = node.apply_preset( + text="Product on pedestal", + preset="commercial" + ) + + print(f"Input: 'Product on pedestal'") + print(f"Preset: commercial") + print(f"Generated {len(results)} outputs") + + for i in range(min(4, len(results))): + print(f" var_{i+1}: {results[i][0][:60]}...") + + assert len(results) == 8, "Should return 8 outputs" + + print("✅ PASSED") + return True + + +def test_modifier_info(): + """Test modifier info node""" + print("\n" + "="*80) + print("TEST 6: Modifier Info Node") + print("="*80) + + node = PromptModifierInfoNode() + + desc, category, tags = node.get_info(modifier_id=7) + + print(f"Modifier ID: 7") + print(f"Description: {desc}") + print(f"Category: {category}") + print(f"Tags: {tags}") + + assert "golden hour" in desc.lower(), "Wrong modifier info" + assert category in ["dry", "wet"], "Invalid category" + + print("✅ PASSED") + return True + + +def test_combiner(): + """Test prompt combiner node""" + print("\n" + "="*80) + print("TEST 7: Prompt Combiner Node") + print("="*80) + + node = PromptCombinerNode() + + result = node.combine( + text_1="Sports car", + text_2="on mountain road", + text_3="golden hour", + separator=", " + ) + + print(f"Input 1: 'Sports car'") + print(f"Input 2: 'on mountain road'") + print(f"Input 3: 'golden hour'") + print(f"Output: {result[0]}") + + assert "Sports car" in result[0], "Missing text_1" + assert "mountain road" in result[0], "Missing text_2" + assert "golden hour" in result[0], "Missing text_3" + + print("✅ PASSED") + return True + + +def test_random_modifier(): + """Test random modifier node""" + print("\n" + "="*80) + print("TEST 8: Random Modifier Node") + print("="*80) + + node = PromptRandomModifierNode() + + result, modifier_id = node.apply_random( + text="Architecture scene", + category="dry", + seed=42 + ) + + print(f"Input: 'Architecture scene'") + print(f"Category: dry") + print(f"Seed: 42") + print(f"Selected Modifier: {modifier_id}") + print(f"Output: {result[:80]}...") + + assert 1 <= modifier_id <= 16, "Invalid modifier ID for DRY category" + + # Test reproducibility + result2, modifier_id2 = node.apply_random( + text="Architecture scene", + category="dry", + seed=42 + ) + + assert modifier_id == modifier_id2, "Same seed should give same modifier" + + print("✅ PASSED (reproducible)") + return True + + +def test_multi_style(): + """Test multi-style layer node""" + print("\n" + "="*80) + print("TEST 9: Multi-Style Layer Node") + print("="*80) + + node = PromptMultiStyleNode() + + result = node.apply_multi_style( + text="Urban scene", + style_1="cyberpunk", + style_2="noir", + style_3="none" + ) + + print(f"Input: 'Urban scene'") + print(f"Style 1: cyberpunk") + print(f"Style 2: noir") + print(f"Output: {result[0][:100]}...") + + # Should contain elements of both styles + assert len(result[0]) > len("Urban scene"), "Styles not applied" + + print("✅ PASSED") + return True + + +def run_all_tests(): + """Run all tests and report results""" + print("\n" + "█"*80) + print("COMFYUI PROMPT ORCHESTRATION NODES - TEST SUITE") + print("█"*80) + + tests = [ + ("Lighting Modifier", test_lighting_modifier), + ("Batch Variation", test_batch_variation), + ("Style Transform", test_style_transform), + ("Smart Variation", test_smart_variation), + ("Preset Collection", test_preset_collection), + ("Modifier Info", test_modifier_info), + ("Prompt Combiner", test_combiner), + ("Random Modifier", test_random_modifier), + ("Multi-Style Layer", test_multi_style) + ] + + results = [] + + for test_name, test_func in tests: + try: + success = test_func() + results.append((test_name, True, None)) + except Exception as e: + print(f"\n❌ FAILED: {e}") + results.append((test_name, False, str(e))) + + # Summary + print("\n" + "█"*80) + print("TEST SUMMARY") + print("█"*80) + + passed = sum(1 for _, success, _ in results if success) + total = len(results) + + for test_name, success, error in results: + status = "✅ PASS" if success else "❌ FAIL" + print(f"{status} - {test_name}") + if error: + print(f" Error: {error}") + + print("\n" + "="*80) + print(f"Results: {passed}/{total} tests passed") + print("="*80) + + if passed == total: + print("\n🎉 ALL TESTS PASSED! Nodes are ready for ComfyUI.") + return True + else: + print(f"\n⚠️ {total - passed} test(s) failed. Please review errors above.") + return False + + +if __name__ == "__main__": + success = run_all_tests() + sys.exit(0 if success else 1)