Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9adf51c55 | ||
|
|
57f511c06f | ||
|
|
69450cf7c6 | ||
|
|
dde9ca4d1e | ||
|
|
409c3044e3 | ||
|
|
62714a22b8 | ||
|
|
06b7d8f6a1 | ||
|
|
6e4c67457f | ||
|
|
b6f815deff |
@@ -0,0 +1,28 @@
|
||||
name: Publish to Comfy registry
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
paths:
|
||||
- "pyproject.toml"
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
publish-node:
|
||||
name: Publish Custom Node to registry
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository_owner == 'shin131002' }}
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- name: Publish Custom Node
|
||||
uses: Comfy-Org/publish-node-action@v1
|
||||
with:
|
||||
## Add your own personal access token to your Github Repository secrets and reference it here.
|
||||
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
|
||||
@@ -4,13 +4,41 @@ __pycache__/
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
*.egg-info/
|
||||
dist/
|
||||
build/
|
||||
|
||||
# IDE
|
||||
# IDEs
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
Thumbs.db
|
||||
desktop.ini
|
||||
|
||||
# Personal folders
|
||||
old/
|
||||
backup/
|
||||
test/
|
||||
tmp/
|
||||
temp/
|
||||
archive/
|
||||
_old/
|
||||
_backup/
|
||||
_test/
|
||||
|
||||
# Development files
|
||||
DEVELOPMENT_SUMMARY.md
|
||||
note_article.md
|
||||
release_notes_*.md
|
||||
*_backup.md
|
||||
*_fixed.toml
|
||||
__init___no_advanced.py
|
||||
|
||||
# User data
|
||||
*.log
|
||||
*.cache
|
||||
|
||||
@@ -2,6 +2,62 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [1.2.0] - 2026-01-13
|
||||
|
||||
### Added
|
||||
|
||||
#### New Node: Filtered Random LoRA Loader (LBW)
|
||||
- **LoRA Block Weight (LBW) support** for precise effect control
|
||||
- **Automatic SD1.5/SDXL detection** - no manual model type selection needed
|
||||
- **4 preset modes**: Style Focused, Character Focused, Structure/Composition Only, Balanced/Soft
|
||||
- **Preset: Random mode** - randomly selects one of the 4 presets
|
||||
- **Direct Input mode** - custom weight specification with auto-adjustment
|
||||
- **Automatic weight adjustment** - handles mismatched element counts
|
||||
- Too few elements: pads with 1.0 at the end
|
||||
- Too many elements: truncates from the end
|
||||
- U-Net block structure support:
|
||||
- SDXL: 20 elements (BASE + IN:9 + MID:1 + OUT:9)
|
||||
- SD1.5: 17 elements (BASE + IN:6 + MID:1 + OUT:9)
|
||||
- LBW syntax output in `positive_text` for Wildcard Encode compatibility
|
||||
- Includes all features from Filtered Random LoRA Loader
|
||||
|
||||
#### Video Preview Support (All Nodes)
|
||||
- Video file preview support (.mp4, .webm, .avi, .mov)
|
||||
- Extracts first frame as static image
|
||||
- Requires optional `opencv-python` dependency
|
||||
- Graceful fallback to black screen with warning if opencv-python not installed
|
||||
- Priority order: static images → animated images → video files
|
||||
|
||||
### Changed
|
||||
|
||||
#### Model Compatibility
|
||||
- ⚠️ **Explicitly SD1.5/SDXL only** - other architectures (Flux, SD3, etc.) not supported
|
||||
- LBW feature specifically designed for SD1.5/SDXL U-Net structure
|
||||
|
||||
#### Dependencies
|
||||
- Added optional dependency: `opencv-python` (for video preview only)
|
||||
- Core functionality works without opencv-python
|
||||
|
||||
### Technical Details
|
||||
|
||||
#### LBW Implementation
|
||||
- Direct tensor weight manipulation for precise block control
|
||||
- Automatic LoRA type detection via output_blocks analysis
|
||||
- Compatible with serial node chaining (different LBW per node)
|
||||
- Zero weight (all 0) = complete LoRA disable
|
||||
|
||||
#### Preview Image Processing
|
||||
- Pillow-only support: .png, .jpg, .jpeg, .gif, .webp (always works)
|
||||
- opencv-python support: .mp4, .webm, .avi, .mov (optional)
|
||||
- First-frame extraction for animated/video formats
|
||||
- Consistent 1240px preprocessing
|
||||
|
||||
### Performance
|
||||
- No performance impact when LBW not used (Normal mode)
|
||||
- Metadata caching unchanged from v1.1.0
|
||||
|
||||
---
|
||||
|
||||
## [1.1.0] - 2026-01-04
|
||||
|
||||
### Added
|
||||
@@ -63,7 +119,7 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
---
|
||||
|
||||
## [1.0.0] - 2025-12-XX
|
||||
## [1.0.0] - 2025-12-30
|
||||
|
||||
### Initial Release
|
||||
|
||||
|
||||
@@ -0,0 +1,382 @@
|
||||
# Filtered Random LoRA Loader (LBW)
|
||||
|
||||
**LoRA Block Weight (LBW) support with automatic SD1.5/SDXL detection**
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### **Core Features (from Filtered Random LoRA Loader)**
|
||||
- ✅ Random LoRA selection from a single folder
|
||||
- ✅ Keyword filtering (AND/OR modes)
|
||||
- ✅ Metadata search (filename or embedded metadata)
|
||||
- ✅ Preview images (static/animated/video support)
|
||||
- ✅ Trigger word extraction
|
||||
- ✅ Duplicate removal by filename
|
||||
- ✅ Direct serial connection support
|
||||
|
||||
### **Advanced Features (LBW)**
|
||||
- ✅ **LoRA Block Weight (LBW) support**
|
||||
- ✅ **Automatic SD1.5/SDXL detection**
|
||||
- ✅ **4 preset modes + custom input**
|
||||
- ✅ **Automatic weight adjustment**
|
||||
|
||||
---
|
||||
|
||||
## LoRA Block Weight (LBW)
|
||||
|
||||
### **What is LBW?**
|
||||
|
||||
LoRA Block Weight allows you to control which parts of the U-Net model are affected by the LoRA:
|
||||
|
||||
- **INPUT blocks**: Structure, composition, layout
|
||||
- **MIDDLE block**: Overall features
|
||||
- **OUTPUT blocks**: Style, details, fine-tuning
|
||||
|
||||
By adjusting weights for each block, you can precisely control the LoRA's effect.
|
||||
|
||||
---
|
||||
|
||||
## Weight Modes
|
||||
|
||||
### **1. Normal (All 1.0)** - Default
|
||||
All blocks weighted at 1.0 (standard LoRA application, no LBW).
|
||||
|
||||
---
|
||||
|
||||
### **2. Style Focused**
|
||||
Emphasizes OUTPUT blocks only.
|
||||
|
||||
**Best for:**
|
||||
- Style LoRAs
|
||||
- Art style transfer
|
||||
- Visual aesthetic changes
|
||||
|
||||
**SDXL:** `1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1`
|
||||
**SD1.5:** `1,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1`
|
||||
|
||||
**Effect:**
|
||||
- ✅ Strong style changes
|
||||
- ❌ Minimal structure/character changes
|
||||
|
||||
---
|
||||
|
||||
### **3. Character Focused**
|
||||
Emphasizes INPUT (early) + MIDDLE + OUTPUT blocks.
|
||||
|
||||
**Best for:**
|
||||
- Character LoRAs
|
||||
- Face/feature preservation
|
||||
- Balanced application
|
||||
|
||||
**SDXL:** `1,1,1,1,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1`
|
||||
**SD1.5:** `1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1`
|
||||
|
||||
**Effect:**
|
||||
- ✅ Strong character features
|
||||
- ✅ Balanced overall effect
|
||||
|
||||
---
|
||||
|
||||
### **4. Structure/Composition Only**
|
||||
Emphasizes INPUT + MIDDLE blocks only.
|
||||
|
||||
**Best for:**
|
||||
- Pose LoRAs
|
||||
- Composition LoRAs
|
||||
- Layout/structure control
|
||||
|
||||
**SDXL:** `1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0`
|
||||
**SD1.5:** `1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0`
|
||||
|
||||
**Effect:**
|
||||
- ✅ Strong structure/pose changes
|
||||
- ❌ Minimal style changes
|
||||
|
||||
---
|
||||
|
||||
### **5. Balanced / Soft**
|
||||
Soft application across selected blocks.
|
||||
|
||||
**Best for:**
|
||||
- Subtle effects
|
||||
- Multi-LoRA workflows
|
||||
- General-purpose LoRAs
|
||||
|
||||
**SDXL:** `1,1,1,1,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0`
|
||||
**SD1.5:** `1,1,1,1,0,0,0,1,1,1,1,1,0,0,0,0,0`
|
||||
|
||||
**Effect:**
|
||||
- ✅ Balanced, gentle application
|
||||
- ✅ Good for stacking multiple LoRAs
|
||||
|
||||
---
|
||||
|
||||
### **6. Preset: Random**
|
||||
Randomly selects one of the 4 presets above.
|
||||
|
||||
**Best for:**
|
||||
- Experimentation
|
||||
- Variation generation
|
||||
- Random effects
|
||||
|
||||
---
|
||||
|
||||
### **7. Direct Input**
|
||||
Custom weight specification.
|
||||
|
||||
**Format:**
|
||||
- SDXL: 20 comma-separated values
|
||||
- SD1.5: 17 comma-separated values
|
||||
|
||||
**Example (SDXL):**
|
||||
```
|
||||
1,0.5,0.5,0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.7,0.7,0.7,1,1,1,1,1,1,1
|
||||
```
|
||||
|
||||
**Auto-adjustment:**
|
||||
- If too few elements: pads with 1.0 at the end
|
||||
- If too many elements: truncates from the end
|
||||
|
||||
---
|
||||
|
||||
## Block Structure
|
||||
|
||||
### **SDXL (20 elements)**
|
||||
```
|
||||
BASE, IN00-IN08 (9), MID (1), OUT00-OUT08 (9)
|
||||
```
|
||||
|
||||
### **SD1.5 (17 elements)**
|
||||
```
|
||||
BASE, IN01,IN02,IN04,IN05,IN07,IN08 (6), MID (1), OUT03-OUT11 (9)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Automatic Detection
|
||||
|
||||
### **LoRA Type Detection**
|
||||
|
||||
The node automatically detects whether a LoRA is SD1.5 or SDXL by analyzing the model structure:
|
||||
|
||||
- **SD1.5**: `output_blocks_0` to `output_blocks_11` (12 blocks)
|
||||
- **SDXL**: `output_blocks_0` to `output_blocks_8` (9 blocks)
|
||||
|
||||
**You don't need to specify the model type manually!** ✅
|
||||
|
||||
---
|
||||
|
||||
### **Weight Adjustment**
|
||||
|
||||
If the number of weights doesn't match the detected LoRA type:
|
||||
|
||||
**Example 1: Too few weights**
|
||||
```
|
||||
Input: 1,0,0 (3 elements)
|
||||
Detected: SDXL (requires 20)
|
||||
→ Auto-adjusted: 1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
|
||||
```
|
||||
|
||||
**Example 2: Too many weights**
|
||||
```
|
||||
Input: 1,0,0,...,0,0,0 (25 elements)
|
||||
Detected: SDXL (requires 20)
|
||||
→ Auto-adjusted: 1,0,0,...,0,0,0 (first 20 only)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
### **Basic Workflow**
|
||||
|
||||
```
|
||||
[Filtered Random LoRA Loader (LBW)]
|
||||
MODEL → [KSampler]
|
||||
CLIP → [CLIP Text Encode]
|
||||
positive_text → [Show Text] (optional, for debugging)
|
||||
```
|
||||
|
||||
### **Parameters**
|
||||
|
||||
#### **LoRA Settings**
|
||||
- `lora_folder_path`: Path to LoRA folder
|
||||
- `num_loras`: Number of LoRAs to apply (0-20)
|
||||
- `model_strength`: LoRA strength for MODEL (e.g., "1.0" or "0.6-0.9")
|
||||
- `clip_strength`: LoRA strength for CLIP (e.g., "1.0" or "0.6-0.9")
|
||||
|
||||
#### **Keyword Filter**
|
||||
- `keyword_filter`: Space-separated keywords (e.g., `style anime` or `"anime style" red`)
|
||||
- `filter_mode`: AND / OR
|
||||
- `search_in_metadata`: Search in JSON/embedded metadata (slower)
|
||||
|
||||
#### **LBW Settings** (New!)
|
||||
- `weight_mode`: Normal / 4 Presets / Random / Direct Input
|
||||
- `lbw_input`: Custom weights (only for Direct Input mode)
|
||||
|
||||
#### **Other**
|
||||
- `trigger_word_source`: Trigger word extraction method
|
||||
- `seed`: Random seed
|
||||
|
||||
---
|
||||
|
||||
## Output Format
|
||||
|
||||
### **LBW Syntax**
|
||||
|
||||
When LBW is applied, the `positive_text` output includes LBW syntax:
|
||||
|
||||
**Normal (All 1.0):**
|
||||
```
|
||||
<lora:style_anime:0.8:0.8>
|
||||
```
|
||||
|
||||
**With LBW (Style Focused, SDXL):**
|
||||
```
|
||||
<lora:style_anime:0.8:0.8:lbw=1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1>
|
||||
```
|
||||
|
||||
**Note:** The LBW is applied internally to the MODEL. The syntax in `positive_text` is for reference/re-use with other nodes.
|
||||
|
||||
---
|
||||
|
||||
## Tips
|
||||
|
||||
### **1. Preset Selection Guide**
|
||||
|
||||
| LoRA Type | Recommended Preset | Why |
|
||||
|-----------|-------------------|-----|
|
||||
| Style/Art | Style Focused | OUTPUT blocks control style |
|
||||
| Character | Character Focused | Balanced IN+MID+OUT for features |
|
||||
| Pose/Composition | Structure/Composition Only | INPUT blocks control layout |
|
||||
| General | Balanced / Soft | Gentle, stackable effect |
|
||||
|
||||
---
|
||||
|
||||
### **2. Effect Visibility**
|
||||
|
||||
The difference between presets is **more visible** with:
|
||||
- ✅ Strong LoRAs (high model_strength)
|
||||
- ✅ Single LoRA application (num_loras=1)
|
||||
- ✅ Style or concept LoRAs
|
||||
|
||||
The difference is **less visible** with:
|
||||
- ⚠️ Weak LoRAs (low model_strength)
|
||||
- ⚠️ Multiple LoRAs applied simultaneously
|
||||
- ⚠️ LoRAs with evenly distributed training
|
||||
|
||||
---
|
||||
|
||||
### **3. Zero Weight = No Effect**
|
||||
|
||||
Setting all weights to 0 completely disables the LoRA effect:
|
||||
```
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
```
|
||||
This is equivalent to not applying the LoRA at all.
|
||||
|
||||
---
|
||||
|
||||
### **4. Serial Connection**
|
||||
|
||||
For applying different LBW settings to different LoRAs:
|
||||
```
|
||||
[Filtered Random LoRA Loader (LBW)] (Style Focused)
|
||||
↓
|
||||
[Filtered Random LoRA Loader (LBW)] (Character Focused)
|
||||
↓
|
||||
[KSampler]
|
||||
```
|
||||
|
||||
All LoRAs within a single node instance use the same LBW setting.
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
### **⚠️ Supported Models**
|
||||
**This node is designed for SD1.5 and SDXL models ONLY.**
|
||||
|
||||
- ✅ **Supported:** Stable Diffusion 1.5, Stable Diffusion XL (SDXL)
|
||||
- ❌ **NOT Supported:** Flux, SD3, SDXL Turbo, Pony, or other architectures
|
||||
|
||||
The LBW feature specifically targets SD1.5/SDXL U-Net architecture. Other model types will not work correctly.
|
||||
|
||||
---
|
||||
|
||||
### **Dependencies**
|
||||
- ComfyUI
|
||||
- Pillow (included with ComfyUI)
|
||||
- torch (included with ComfyUI)
|
||||
|
||||
### **⚠️ Optional: Video Preview**
|
||||
**For video preview support (.mp4, .webm, .avi, .mov), opencv-python is REQUIRED:**
|
||||
```bash
|
||||
pip install opencv-python
|
||||
```
|
||||
|
||||
**Without opencv-python:**
|
||||
- ✅ Static/animated images work
|
||||
- ❌ Video files show black screen
|
||||
|
||||
---
|
||||
|
||||
## Preview Images
|
||||
|
||||
Supports the following formats (in priority order):
|
||||
|
||||
1. **Static images** (.png, .jpg, .jpeg)
|
||||
2. **Animated images** (.gif, .webp) - first frame
|
||||
3. **Video files** (.mp4, .webm, .avi, .mov) - first frame (requires opencv-python)
|
||||
|
||||
The node automatically searches for preview files matching the LoRA filename.
|
||||
|
||||
---
|
||||
|
||||
## Disclaimer
|
||||
|
||||
- No technical support provided
|
||||
- No warranty or guarantee of functionality
|
||||
- No guaranteed compatibility with future ComfyUI updates
|
||||
- Bug reports and feature requests may not be addressed
|
||||
- Use at your own risk
|
||||
|
||||
---
|
||||
|
||||
## Changelog
|
||||
|
||||
### v1.2.0 (2026-01-13)
|
||||
- ✅ Added LoRA Block Weight (LBW) support
|
||||
- ✅ Automatic SD1.5/SDXL detection
|
||||
- ✅ 4 preset modes + custom input
|
||||
- ✅ Automatic weight adjustment
|
||||
- ✅ Video preview support (.mp4, .webm, etc.)
|
||||
|
||||
### v1.1.0 (2026-01-04)
|
||||
- ✅ Added Filtered Random LoRA Loader
|
||||
- ✅ Keyword filtering with AND/OR modes
|
||||
- ✅ Metadata search support
|
||||
- ✅ Preview image display
|
||||
|
||||
### v1.0.0 (2025-12-30)
|
||||
- ✅ Initial release
|
||||
- ✅ Random LoRA Loader (3 groups)
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
MIT License
|
||||
|
||||
---
|
||||
|
||||
## Author
|
||||
|
||||
Your Name
|
||||
|
||||
---
|
||||
|
||||
**Enjoy precise LoRA control with Block Weights!** 🎨✨
|
||||
@@ -0,0 +1,494 @@
|
||||
# Filtered Random LoRA Loader (LBW) - 詳細ガイド
|
||||
|
||||
**LoRAブロックウェイト(LBW)対応版**
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## 概要
|
||||
|
||||
Filtered Random LoRA Loader (LBW) は、LoRAブロックウェイト(LBW)機能を追加した高度なノードです。U-Netのどの部分にLoRAを適用するかを細かく制御できます。
|
||||
|
||||
---
|
||||
|
||||
## 主な機能
|
||||
|
||||
### **✅ LoRAブロックウェイト(LBW)対応**
|
||||
- U-Netのブロックごとに個別の強度を設定
|
||||
- 画風、キャラクター、構図などを分離制御
|
||||
|
||||
### **✅ 自動SD1.5/SDXL検出**
|
||||
- LoRAファイルを自動判定
|
||||
- ウェイト数を自動調整
|
||||
|
||||
### **✅ 4つのプリセットモード**
|
||||
- Style Focused(画風特化)
|
||||
- Character Focused(キャラ重視)
|
||||
- Structure/Composition Only(構造・構図のみ)
|
||||
- Balanced / Soft(バランス・穏やか)
|
||||
|
||||
### **✅ カスタム入力**
|
||||
- Direct Inputモードで任意のウェイトを指定
|
||||
- Preset: Randomモードでランダムプリセット選択
|
||||
|
||||
---
|
||||
|
||||
## LBWとは?
|
||||
|
||||
### **LoRAブロックウェイトの仕組み**
|
||||
|
||||
Stable DiffusionのU-Netは以下の3つの部分で構成されています:
|
||||
|
||||
```
|
||||
INPUT blocks → 構造、構図、レイアウト
|
||||
↓
|
||||
MIDDLE block → 全体的な特徴
|
||||
↓
|
||||
OUTPUT blocks → 画風、ディテール、細部調整
|
||||
```
|
||||
|
||||
LBWを使うと、**各ブロックに異なるLoRA強度を設定**できます。
|
||||
|
||||
---
|
||||
|
||||
## ブロック構造
|
||||
|
||||
### **SDXL(20要素)**
|
||||
|
||||
| インデックス | ブロック | 役割 |
|
||||
|------------|---------|------|
|
||||
| 0 | BASE | 全体ベース |
|
||||
| 1-9 | INPUT (IN00-08) | 構造・構図 |
|
||||
| 10 | MIDDLE (M00) | 中間特徴 |
|
||||
| 11-19 | OUTPUT (OUT00-08) | 画風・ディテール |
|
||||
|
||||
**ウェイト例:**
|
||||
```
|
||||
1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1
|
||||
BASE,IN00-08 (OFF),M00,OUT00-08 (ON)
|
||||
→ OUTPUTブロックのみLoRA適用(画風特化)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **SD1.5(17要素)**
|
||||
|
||||
| インデックス | ブロック | 役割 |
|
||||
|------------|---------|------|
|
||||
| 0 | BASE | 全体ベース |
|
||||
| 1-6 | INPUT (IN01,02,04,05,07,08) | 構造・構図 |
|
||||
| 7 | MIDDLE (M00) | 中間特徴 |
|
||||
| 8-16 | OUTPUT (OUT03-11) | 画風・ディテール |
|
||||
|
||||
**ウェイト例:**
|
||||
```
|
||||
1,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1
|
||||
BASE,IN (OFF),M00,OUT (ON)
|
||||
→ OUTPUTブロックのみLoRA適用(画風特化)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## プリセット詳細
|
||||
|
||||
### **1. Style Focused(画風特化)**
|
||||
|
||||
**SDXL:** `1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1`
|
||||
**SD1.5:** `1,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1`
|
||||
|
||||
**特徴:**
|
||||
- OUTPUTブロックのみ有効
|
||||
- 構図を変えず画風のみ適用
|
||||
- 既存の構図を保ちたい場合に最適
|
||||
|
||||
**用途:**
|
||||
- 画風LoRA(アニメ風、水彩風など)
|
||||
- スタイルLoRA
|
||||
- エフェクトLoRA
|
||||
|
||||
---
|
||||
|
||||
### **2. Character Focused(キャラ重視)**
|
||||
|
||||
**SDXL:** `1,1,1,1,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1`
|
||||
**SD1.5:** `1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1`
|
||||
|
||||
**特徴:**
|
||||
- INPUT前半 + MIDDLE + OUTPUT有効
|
||||
- キャラクターの特徴を保持
|
||||
- バランスの取れた適用
|
||||
|
||||
**用途:**
|
||||
- キャラクターLoRA
|
||||
- 人物LoRA
|
||||
- コンセプトLoRA
|
||||
|
||||
---
|
||||
|
||||
### **3. Structure/Composition Only(構造・構図のみ)**
|
||||
|
||||
**SDXL:** `1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0`
|
||||
**SD1.5:** `1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0`
|
||||
|
||||
**特徴:**
|
||||
- INPUT + MIDDLEブロックのみ有効
|
||||
- 画風を変えず構図のみ変更
|
||||
- ポーズや構図調整に最適
|
||||
|
||||
**用途:**
|
||||
- ポーズLoRA
|
||||
- 構図LoRA
|
||||
- レイアウトLoRA
|
||||
|
||||
---
|
||||
|
||||
### **4. Balanced / Soft(バランス・穏やか)**
|
||||
|
||||
**SDXL:** `1,1,1,1,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0`
|
||||
**SD1.5:** `1,1,1,1,0,0,0,1,1,1,1,1,0,0,0,0,0`
|
||||
|
||||
**特徴:**
|
||||
- 全体的に穏やかな適用
|
||||
- OUTPUT後半を弱めて過剰適用を防止
|
||||
- 自然な仕上がり
|
||||
|
||||
**用途:**
|
||||
- 汎用LoRA
|
||||
- 微調整用
|
||||
- 複数LoRA併用時
|
||||
|
||||
---
|
||||
|
||||
## 使い方
|
||||
|
||||
### **基本的な使い方**
|
||||
|
||||
1. **ノードを配置**
|
||||
```
|
||||
[Filtered Random LoRA Loader (LBW)]
|
||||
```
|
||||
|
||||
2. **フォルダとキーワードを設定**
|
||||
```
|
||||
lora_folder_path: "path/to/loras"
|
||||
keyword_filter: "anime style"
|
||||
num_loras: 1
|
||||
```
|
||||
|
||||
3. **LBWモードを選択**
|
||||
```
|
||||
weight_mode: "Style Focused"
|
||||
```
|
||||
|
||||
4. **MODEL/CLIPを接続**
|
||||
```
|
||||
MODEL → [KSampler]
|
||||
CLIP → [CLIP Text Encode]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **Direct Inputモード**
|
||||
|
||||
カスタムウェイトを直接入力:
|
||||
|
||||
```
|
||||
weight_mode: "Direct Input"
|
||||
lbw_input: "1,0.5,0.5,0,0,0,0,0,0,0,1,1,1,1,1,1,0.5,0.5,0.5,0.5"
|
||||
```
|
||||
|
||||
**ルール:**
|
||||
- カンマ区切り
|
||||
- SDXL: 20要素、SD1.5: 17要素
|
||||
- 要素数が合わない場合は自動調整
|
||||
|
||||
---
|
||||
|
||||
### **Preset: Randomモード**
|
||||
|
||||
毎回異なるプリセットをランダム選択:
|
||||
|
||||
```
|
||||
weight_mode: "Preset: Random"
|
||||
→ 実行ごとに4つのプリセットから1つ選択
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用例
|
||||
|
||||
### **例1: 画風のみ変更**
|
||||
|
||||
```
|
||||
[Load Checkpoint] → [Filtered Random LoRA Loader (LBW)]
|
||||
weight_mode: "Style Focused"
|
||||
keyword_filter: "anime"
|
||||
↓
|
||||
[KSampler]
|
||||
```
|
||||
|
||||
**結果:** 構図はそのまま、画風のみアニメ風に
|
||||
|
||||
---
|
||||
|
||||
### **例2: 複数LoRAで段階適用**
|
||||
|
||||
```
|
||||
[Load Checkpoint]
|
||||
↓
|
||||
[Filtered Random LoRA Loader (LBW)] (Structure/Composition Only)
|
||||
keyword_filter: "pose"
|
||||
↓
|
||||
[Filtered Random LoRA Loader (LBW)] (Style Focused)
|
||||
keyword_filter: "watercolor"
|
||||
↓
|
||||
[KSampler]
|
||||
```
|
||||
|
||||
**結果:** ポーズ調整 → 水彩画風適用
|
||||
|
||||
---
|
||||
|
||||
### **例3: キャラ + 画風**
|
||||
|
||||
```
|
||||
[Load Checkpoint]
|
||||
↓
|
||||
[Filtered Random LoRA Loader (LBW)] (Character Focused)
|
||||
keyword_filter: "character_name"
|
||||
↓
|
||||
[Filtered Random LoRA Loader (LBW)] (Style Focused)
|
||||
keyword_filter: "art_style"
|
||||
↓
|
||||
[KSampler]
|
||||
```
|
||||
|
||||
**結果:** キャラ特徴保持 + 画風変更
|
||||
|
||||
---
|
||||
|
||||
## ヒント・Tips
|
||||
|
||||
### **1. プリセット選択ガイド**
|
||||
|
||||
| LoRAタイプ | 推奨プリセット |
|
||||
|-----------|--------------|
|
||||
| 画風・スタイル | Style Focused |
|
||||
| キャラクター | Character Focused |
|
||||
| ポーズ・構図 | Structure/Composition Only |
|
||||
| 汎用・微調整 | Balanced / Soft |
|
||||
|
||||
---
|
||||
|
||||
### **2. 直列接続のコツ**
|
||||
|
||||
- **構造 → 画風の順**が基本
|
||||
- 各ノードで異なるLBW設定可能
|
||||
- 強度も個別に調整可能
|
||||
|
||||
---
|
||||
|
||||
### **3. ウェイト調整のコツ**
|
||||
|
||||
- **0**: 完全無効
|
||||
- **0.5**: 弱め
|
||||
- **1.0**: 標準
|
||||
- **1.5以上**: 強め(過剰に注意)
|
||||
|
||||
---
|
||||
|
||||
### **4. トラブルシューティング**
|
||||
|
||||
**効果が出ない場合:**
|
||||
- すべて0になっていないか確認
|
||||
- LoRAが正しく読み込まれているか確認
|
||||
- コンソールでLBW適用メッセージを確認
|
||||
|
||||
**効果が強すぎる場合:**
|
||||
- model_strengthを下げる
|
||||
- Balanced / Softプリセットを試す
|
||||
- ウェイトを0.5~0.8に調整
|
||||
|
||||
---
|
||||
|
||||
## 自動検出
|
||||
|
||||
### **SD1.5 / SDXL自動判定**
|
||||
|
||||
ノードは自動的にLoRAタイプを検出:
|
||||
|
||||
```
|
||||
SDXL検出 → 20要素ウェイト使用
|
||||
SD1.5検出 → 17要素ウェイト使用
|
||||
```
|
||||
|
||||
**ユーザーは何も設定不要** ✨
|
||||
|
||||
---
|
||||
|
||||
### **ウェイト数自動調整**
|
||||
|
||||
入力したウェイト数が合わない場合、自動調整:
|
||||
|
||||
```
|
||||
入力: 15要素(不足)
|
||||
検出: SDXL(20要素必要)
|
||||
→ 末尾に1.0を5個追加
|
||||
|
||||
入力: 25要素(過剰)
|
||||
検出: SDXL(20要素必要)
|
||||
→ 末尾から5個カット
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 要件
|
||||
|
||||
### **⚠️ 対応モデル**
|
||||
**本ノードはSD1.5とSDXLモデル専用です。**
|
||||
|
||||
- ✅ **対応:** Stable Diffusion 1.5、Stable Diffusion XL(SDXL)
|
||||
- ❌ **非対応:** Flux、SD3、SDXL Turbo、Pony、その他のアーキテクチャ
|
||||
|
||||
LBW機能は、SD1.5/SDXLのU-Netアーキテクチャ専用に設計されています。他のモデルタイプではLBWが正常に動作しません。
|
||||
|
||||
---
|
||||
|
||||
### **依存関係**
|
||||
- ComfyUI
|
||||
- Pillow(ComfyUIに含まれる)
|
||||
- torch(ComfyUIに含まれる)
|
||||
|
||||
### **⚠️ オプション: 動画プレビュー**
|
||||
**動画ファイルプレビュー(.mp4, .webm, .avi, .mov)にはopencv-pythonが必須です:**
|
||||
```bash
|
||||
pip install opencv-python
|
||||
```
|
||||
|
||||
**opencv-pythonなしの場合:**
|
||||
- ✅ 静止画/アニメ画像は動作
|
||||
- ❌ 動画ファイルは黒画面
|
||||
|
||||
---
|
||||
|
||||
## プレビュー画像
|
||||
|
||||
以下の形式に対応(優先順位順):
|
||||
|
||||
1. **静止画像**(.png, .jpg, .jpeg) - 常に動作
|
||||
2. **アニメ画像**(.gif, .webp) - 1フレーム目、常に動作
|
||||
3. **動画ファイル**(.mp4, .webm, .avi, .mov) - 1フレーム目、opencv-python必要
|
||||
|
||||
**ファイルマッチング:**
|
||||
- LoRAファイル名で始まるファイル(大文字小文字無視)
|
||||
- 例: `style_anime.safetensors` にマッチ:
|
||||
- `style_anime.png` ✅
|
||||
- `style_anime_preview.jpg` ✅
|
||||
- `STYLE_ANIME.PNG` ✅
|
||||
|
||||
---
|
||||
|
||||
## 上級者向け: カスタムプリセット
|
||||
|
||||
独自のプリセットを作成したり、既存のプリセットを変更したい場合は、ソースファイルを直接編集できます。
|
||||
|
||||
### **編集方法**
|
||||
|
||||
**ファイル位置:**
|
||||
```
|
||||
ComfyUI/custom_nodes/RandomLoRALoader/filtered_random_lora_loader_lbw.py
|
||||
```
|
||||
|
||||
**編集箇所:** 29~41行目
|
||||
|
||||
### **例1: 独自プリセット追加**
|
||||
|
||||
```python
|
||||
SDXL_PRESETS = {
|
||||
"Style Focused": "1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1",
|
||||
"Character Focused": "1,1,1,1,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1",
|
||||
"Structure/Composition Only": "1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0",
|
||||
"Balanced / Soft": "1,1,1,1,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0",
|
||||
# ↓ 独自プリセット
|
||||
"My Custom Mix": "0.5,0.5,0.5,0.5,0,0,0,0,0,0,1,0.8,0.8,0.8,0.8,0.8,0.5,0.5,0.5,0.5"
|
||||
}
|
||||
```
|
||||
|
||||
### **例2: 既存プリセット調整**
|
||||
|
||||
```python
|
||||
SDXL_PRESETS = {
|
||||
# Style Focusedをより強く
|
||||
"Style Focused": "1,0,0,0,0,0,0,0,0,0,0,1.2,1.2,1.2,1.2,1.2,1.2,1.2,1.2,1.2",
|
||||
|
||||
# Character Focusedをマイルドに
|
||||
"Character Focused": "0.8,0.8,0.8,0.8,0,0,0,0,0,0,0.8,0.8,0.8,0.8,0.8,0.8,0.8,0.8,0.8,0.8",
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### **例3: プリセット名変更**
|
||||
|
||||
```python
|
||||
SDXL_PRESETS = {
|
||||
"画風特化": "1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1",
|
||||
"キャラ重視": "1,1,1,1,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1",
|
||||
"構図のみ": "1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0",
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### **重要な注意事項**
|
||||
|
||||
⚠️ **編集前に:**
|
||||
- 元ファイルのバックアップを作成
|
||||
- Python構文(カンマ、引用符)に注意
|
||||
|
||||
⚠️ **ウェイト数:**
|
||||
- SDXLプリセット: 必ず20要素
|
||||
- SD1.5プリセット: 必ず17要素
|
||||
- 要素数が合わない場合は自動調整されますが、正確に指定する方が良いです
|
||||
|
||||
⚠️ **編集後:**
|
||||
- ComfyUIを完全に再起動して変更を適用
|
||||
- コンソールで構文エラーを確認
|
||||
|
||||
💡 **ヒント:** ファイル編集が面倒な場合は、「Direct Input」モードを使ってください!
|
||||
|
||||
---
|
||||
|
||||
## 更新履歴
|
||||
|
||||
### v1.2.0(2026-01-13)
|
||||
- ✅ LoRAブロックウェイト(LBW)対応追加
|
||||
- ✅ SD1.5/SDXL自動検出
|
||||
- ✅ 4つのプリセットモード+カスタム入力
|
||||
- ✅ ウェイト数自動調整
|
||||
- ✅ 動画プレビュー対応(.mp4, .webm等)
|
||||
|
||||
### v1.1.0(2026-01-04)
|
||||
- ✅ Filtered Random LoRA Loader追加
|
||||
- ✅ キーワードフィルタリング(AND/ORモード)
|
||||
- ✅ メタデータ検索対応
|
||||
- ✅ プレビュー画像表示
|
||||
|
||||
### v1.0.0(2025-12-30)
|
||||
- ✅ 初回リリース
|
||||
- ✅ Random LoRA Loader(3グループ)
|
||||
|
||||
---
|
||||
|
||||
## ライセンス
|
||||
|
||||
MIT License
|
||||
|
||||
---
|
||||
|
||||
## 作者
|
||||
|
||||
Your Name
|
||||
|
||||
---
|
||||
|
||||
**ブロックウェイトで精密なLoRA制御を楽しんでください!** 🎨✨
|
||||
@@ -6,14 +6,27 @@ RandomLoRALoader Custom Node for ComfyUI
|
||||
from .random_lora_loader import RandomLoRALoader
|
||||
from .filtered_random_lora_loader import FilteredRandomLoRALoader
|
||||
|
||||
# LBW版は任意(読み込めない場合はスキップ)
|
||||
try:
|
||||
from .filtered_random_lora_loader_lbw import FilteredRandomLoRALoaderLBW
|
||||
HAS_LBW = True
|
||||
except Exception as e:
|
||||
print(f"[RandomLoRALoader] Failed to load LBW version: {e}")
|
||||
HAS_LBW = False
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"RandomLoRALoader": RandomLoRALoader,
|
||||
"FilteredRandomLoRALoader": FilteredRandomLoRALoader
|
||||
"FilteredRandomLoRALoader": FilteredRandomLoRALoader,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"RandomLoRALoader": "Random LoRA Loader",
|
||||
"FilteredRandomLoRALoader": "Filtered Random LoRA Loader"
|
||||
"FilteredRandomLoRALoader": "Filtered Random LoRA Loader",
|
||||
}
|
||||
|
||||
# LBW版が利用可能な場合のみ追加
|
||||
if HAS_LBW:
|
||||
NODE_CLASS_MAPPINGS["FilteredRandomLoRALoaderLBW"] = FilteredRandomLoRALoaderLBW
|
||||
NODE_DISPLAY_NAME_MAPPINGS["FilteredRandomLoRALoaderLBW"] = "Filtered Random LoRA Loader (LBW)"
|
||||
|
||||
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
|
||||
|
||||
@@ -27,6 +27,7 @@ class FilteredRandomLoRALoader:
|
||||
|
||||
# クラス変数(全インスタンスで共有するメタデータキャッシュ)
|
||||
_metadata_cache = {}
|
||||
_opencv_warning_shown = False # opencv警告表示フラグ
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -662,18 +663,20 @@ class FilteredRandomLoRALoader:
|
||||
プレビュー画像をTensorとして読み込み(部分一致、長辺1240px)
|
||||
|
||||
検索方法:
|
||||
LoRAファイル名(拡張子除く)で始まる画像ファイルを検索
|
||||
LoRAファイル名(拡張子除く)で始まるファイルを検索
|
||||
優先順位:
|
||||
1. 静止画像 (.png, .jpg, .jpeg, .webp)
|
||||
2. 動画ファイル (.gif, .webp, .mp4, .webm) の1フレーム目
|
||||
|
||||
例: style_anime_v1.safetensors
|
||||
→ style_anime_v1.png
|
||||
→ style_anime_v1_preview.png
|
||||
→ style_anime_v1_001.jpg
|
||||
→ style_anime_v1_preview.jpg
|
||||
→ style_anime_v1.gif (1フレーム目)
|
||||
→ style_anime_v1.mp4 (1フレーム目、opencv-pythonが必要)
|
||||
→ STYLE_ANIME_V1.PNG (大文字小文字無視)
|
||||
|
||||
リサイズ:
|
||||
長辺を1240pxに統一(アスペクト比保持)
|
||||
例: 1024x768 → 1240x930
|
||||
512x512 → 1240x1240
|
||||
800x1200 → 826x1240
|
||||
|
||||
Returns:
|
||||
torch.Tensor: (H, W, C) 形式、見つからない場合はNone
|
||||
@@ -690,70 +693,207 @@ class FilteredRandomLoRALoader:
|
||||
base_name = os.path.splitext(os.path.basename(lora_path))[0]
|
||||
folder = os.path.dirname(lora_path)
|
||||
|
||||
# 画像拡張子
|
||||
image_extensions = ('.png', '.jpg', '.jpeg', '.webp', '.gif')
|
||||
# ファイル拡張子(優先順位順)
|
||||
static_image_exts = ('.png', '.jpg', '.jpeg')
|
||||
animated_image_exts = ('.gif', '.webp') # Pillowで対応可能
|
||||
video_exts = ('.mp4', '.webm', '.avi', '.mov') # opencv-python必要
|
||||
|
||||
try:
|
||||
# 同じフォルダ内のファイルを検索
|
||||
for file in os.listdir(folder):
|
||||
# 部分一致(大文字小文字無視)
|
||||
files = os.listdir(folder)
|
||||
|
||||
# 部分一致するファイルを収集
|
||||
matched_files = []
|
||||
for file in files:
|
||||
if file.lower().startswith(base_name.lower()):
|
||||
# 画像ファイルか確認
|
||||
if file.lower().endswith(image_extensions):
|
||||
preview_path = os.path.join(folder, file)
|
||||
try:
|
||||
img = Image.open(preview_path).convert('RGB')
|
||||
|
||||
# 長辺を1240pxにリサイズ(アスペクト比保持)
|
||||
width, height = img.size
|
||||
max_size = 1240
|
||||
|
||||
if max(width, height) > max_size:
|
||||
# 長辺が1240pxを超える場合はリサイズ
|
||||
if width > height:
|
||||
new_width = max_size
|
||||
new_height = int(height * (max_size / width))
|
||||
else:
|
||||
new_height = max_size
|
||||
new_width = int(width * (max_size / height))
|
||||
|
||||
img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
|
||||
elif max(width, height) < max_size:
|
||||
# 長辺が1240px未満の場合は拡大
|
||||
if width > height:
|
||||
new_width = max_size
|
||||
new_height = int(height * (max_size / width))
|
||||
else:
|
||||
new_height = max_size
|
||||
new_width = int(width * (max_size / height))
|
||||
|
||||
img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
|
||||
|
||||
# numpy配列に変換 (H, W, C)
|
||||
img_array = np.array(img).astype(np.float32) / 255.0
|
||||
# Tensor化
|
||||
return torch.from_numpy(img_array)
|
||||
except Exception as e:
|
||||
print(f"[FilteredRandomLoRALoader] Preview load error ({preview_path}): {e}")
|
||||
continue
|
||||
matched_files.append(file)
|
||||
|
||||
if not matched_files:
|
||||
return None
|
||||
|
||||
# 優先順位でソート
|
||||
def get_priority(filename):
|
||||
lower = filename.lower()
|
||||
if any(lower.endswith(ext) for ext in static_image_exts):
|
||||
return 0 # 最優先
|
||||
elif any(lower.endswith(ext) for ext in animated_image_exts):
|
||||
return 1 # 次優先
|
||||
elif any(lower.endswith(ext) for ext in video_exts):
|
||||
return 2 # 最後
|
||||
else:
|
||||
return 999 # その他
|
||||
|
||||
matched_files.sort(key=get_priority)
|
||||
|
||||
# 各ファイルを試す
|
||||
for file in matched_files:
|
||||
preview_path = os.path.join(folder, file)
|
||||
lower_file = file.lower()
|
||||
|
||||
# 静止画像
|
||||
if any(lower_file.endswith(ext) for ext in static_image_exts):
|
||||
img = self._load_static_image(preview_path)
|
||||
if img is not None:
|
||||
return img
|
||||
|
||||
# アニメーション画像(GIF/WebP)
|
||||
elif any(lower_file.endswith(ext) for ext in animated_image_exts):
|
||||
img = self._load_animated_image_first_frame(preview_path)
|
||||
if img is not None:
|
||||
return img
|
||||
|
||||
# 動画ファイル(opencv-python必要)
|
||||
elif any(lower_file.endswith(ext) for ext in video_exts):
|
||||
img = self._load_video_first_frame(preview_path)
|
||||
if img is not None:
|
||||
return img
|
||||
|
||||
except Exception as e:
|
||||
print(f"[FilteredRandomLoRALoader] Folder read error: {e}")
|
||||
|
||||
return None
|
||||
|
||||
def _load_static_image(self, image_path):
|
||||
"""静止画像を読み込み"""
|
||||
try:
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
img = Image.open(image_path).convert('RGB')
|
||||
return self._resize_and_convert_image(img)
|
||||
except Exception as e:
|
||||
print(f"[FilteredRandomLoRALoader] Static image load error ({image_path}): {e}")
|
||||
return None
|
||||
|
||||
def _load_animated_image_first_frame(self, image_path):
|
||||
"""アニメーション画像(GIF/WebP)の1フレーム目を読み込み"""
|
||||
try:
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
img = Image.open(image_path)
|
||||
|
||||
# 1フレーム目に移動
|
||||
if hasattr(img, 'seek'):
|
||||
img.seek(0)
|
||||
|
||||
img = img.convert('RGB')
|
||||
return self._resize_and_convert_image(img)
|
||||
except Exception as e:
|
||||
print(f"[FilteredRandomLoRALoader] Animated image load error ({image_path}): {e}")
|
||||
return None
|
||||
|
||||
def _load_video_first_frame(self, video_path):
|
||||
"""動画ファイルの1フレーム目を読み込み(opencv-python必要)"""
|
||||
try:
|
||||
import cv2
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
# OpenCVで動画を開く
|
||||
cap = cv2.VideoCapture(video_path)
|
||||
|
||||
if not cap.isOpened():
|
||||
return None
|
||||
|
||||
# 1フレーム目を読み込み
|
||||
ret, frame = cap.read()
|
||||
cap.release()
|
||||
|
||||
if not ret:
|
||||
return None
|
||||
|
||||
# BGR → RGB 変換
|
||||
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
||||
|
||||
# PIL Imageに変換
|
||||
img = Image.fromarray(frame_rgb)
|
||||
|
||||
return self._resize_and_convert_image(img)
|
||||
|
||||
except ImportError:
|
||||
# opencv-pythonがインストールされていない(初回のみ警告)
|
||||
if not FilteredRandomLoRALoader._opencv_warning_shown:
|
||||
print("=" * 60)
|
||||
print("[FilteredRandomLoRALoader] Video Preview Support")
|
||||
print("=" * 60)
|
||||
print(f"Video file detected: {os.path.basename(video_path)}")
|
||||
print("opencv-python is not installed.")
|
||||
print("")
|
||||
print("To enable video preview support, install:")
|
||||
print(" pip install opencv-python")
|
||||
print("")
|
||||
print("Static images (.png/.jpg) and animated images (.gif/.webp)")
|
||||
print("will continue to work without opencv-python.")
|
||||
print("=" * 60)
|
||||
FilteredRandomLoRALoader._opencv_warning_shown = True
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"[FilteredRandomLoRALoader] Video load error ({video_path}): {e}")
|
||||
return None
|
||||
|
||||
def _resize_and_convert_image(self, img):
|
||||
"""画像をリサイズしてTensorに変換"""
|
||||
try:
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
# 長辺を1240pxにリサイズ(アスペクト比保持)
|
||||
width, height = img.size
|
||||
max_size = 1240
|
||||
|
||||
if max(width, height) > max_size:
|
||||
# 長辺が1240pxを超える場合はリサイズ
|
||||
if width > height:
|
||||
new_width = max_size
|
||||
new_height = int(height * (max_size / width))
|
||||
else:
|
||||
new_height = max_size
|
||||
new_width = int(width * (max_size / height))
|
||||
|
||||
img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
|
||||
elif max(width, height) < max_size:
|
||||
# 長辺が1240px未満の場合は拡大
|
||||
if width > height:
|
||||
new_width = max_size
|
||||
new_height = int(height * (max_size / width))
|
||||
else:
|
||||
new_height = max_size
|
||||
new_width = int(width * (max_size / height))
|
||||
|
||||
img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
|
||||
|
||||
# numpy配列に変換 (H, W, C)
|
||||
img_array = np.array(img).astype(np.float32) / 255.0
|
||||
# Tensor化
|
||||
return torch.from_numpy(img_array)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[FilteredRandomLoRALoader] Image resize/convert error: {e}")
|
||||
return None
|
||||
|
||||
def _get_random_strength(self, strength_str):
|
||||
"""強度をランダム取得(範囲指定対応)"""
|
||||
"""強度をランダム取得(範囲指定対応、マイナス値・マイナス範囲対応)"""
|
||||
import re
|
||||
strength_str = strength_str.strip()
|
||||
|
||||
if '-' in strength_str:
|
||||
# 範囲指定のパターンマッチ
|
||||
range_pattern = r'^(-?\d+\.?\d*)\s*-\s*(-?\d+\.?\d*)$'
|
||||
match = re.match(range_pattern, strength_str)
|
||||
|
||||
if match:
|
||||
try:
|
||||
parts = strength_str.split('-')
|
||||
min_val = float(parts[0].strip())
|
||||
max_val = float(parts[1].strip())
|
||||
min_val = float(match.group(1))
|
||||
max_val = float(match.group(2))
|
||||
return str(round(random.uniform(min_val, max_val), 1))
|
||||
except:
|
||||
return "1.0"
|
||||
else:
|
||||
# 単一値(マイナス含む)
|
||||
try:
|
||||
float(strength_str)
|
||||
return strength_str
|
||||
|
||||
|
After Width: | Height: | Size: 203 KiB |
|
After Width: | Height: | Size: 246 KiB |
|
After Width: | Height: | Size: 294 KiB |
|
After Width: | Height: | Size: 228 KiB |
|
After Width: | Height: | Size: 237 KiB |
|
After Width: | Height: | Size: 242 KiB |
|
Before Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 80 KiB |
@@ -0,0 +1,37 @@
|
||||
[project]
|
||||
name = "comfyui-random-lora-loader"
|
||||
version = "1.2.0"
|
||||
description = "Randomly select and apply LoRAs with keyword filtering, LoRA Block Weight support, and preview images for ComfyUI"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.9"
|
||||
license = {text = "MIT"}
|
||||
authors = [
|
||||
{name = "Your Name", email = "your.email@example.com"} # 必要に応じて変更
|
||||
]
|
||||
|
||||
keywords = ["comfyui", "lora", "stable-diffusion", "ai", "image-generation", "block-weight", "lbw"]
|
||||
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
video = ["opencv-python>=4.0.0"]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/YOUR_USERNAME/RandomLoRALoader"
|
||||
Repository = "https://github.com/YOUR_USERNAME/RandomLoRALoader"
|
||||
Issues = "https://github.com/YOUR_USERNAME/RandomLoRALoader/issues"
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=61.0"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.setuptools]
|
||||
packages = ["RandomLoRALoader"]
|
||||
@@ -77,6 +77,9 @@ except ImportError:
|
||||
class RandomLoRALoader:
|
||||
"""ランダムLoRA選択・適用ノード(3グループ対応)"""
|
||||
|
||||
# クラス変数(opencv警告表示フラグ)
|
||||
_opencv_warning_shown = False
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
@@ -311,6 +314,7 @@ class RandomLoRALoader:
|
||||
- "0.55" → 0.55(そのまま)
|
||||
- "0.4-0.8" → 0.4, 0.5, 0.6, 0.7, 0.8からランダム(0.1刻み)
|
||||
- "0.44-0.82" → 0.4, 0.5, 0.6, 0.7, 0.8からランダム(範囲を1桁に丸める)
|
||||
- "-0.8--0.3" → -0.8, -0.7, -0.6, ..., -0.3からランダム(マイナス範囲対応)
|
||||
|
||||
Args:
|
||||
strength_str: 強度文字列
|
||||
@@ -318,18 +322,19 @@ class RandomLoRALoader:
|
||||
Returns:
|
||||
float: 実際に使用する強度値
|
||||
"""
|
||||
import re
|
||||
strength_str = str(strength_str).strip()
|
||||
|
||||
# ハイフンがあればランダム範囲指定
|
||||
if '-' in strength_str:
|
||||
# 範囲指定のパターンマッチ(マイナス値対応)
|
||||
# 例: "0.6-0.9", "-0.8--0.3", "-0.5-0.5", "0.3--0.7"
|
||||
range_pattern = r'^(-?\d+\.?\d*)\s*-\s*(-?\d+\.?\d*)$'
|
||||
match = re.match(range_pattern, strength_str)
|
||||
|
||||
if match:
|
||||
try:
|
||||
parts = strength_str.split('-')
|
||||
if len(parts) != 2:
|
||||
raise ValueError("範囲指定は 'min-max' 形式で入力してください")
|
||||
|
||||
# 範囲の上限下限を1桁に丸める
|
||||
min_val = round(float(parts[0].strip()), 1)
|
||||
max_val = round(float(parts[1].strip()), 1)
|
||||
min_val = round(float(match.group(1)), 1)
|
||||
max_val = round(float(match.group(2)), 1)
|
||||
|
||||
# バリデーション
|
||||
if min_val < -10.0 or max_val > 10.0:
|
||||
@@ -747,18 +752,20 @@ class RandomLoRALoader:
|
||||
プレビュー画像をTensorとして読み込み(部分一致、長辺1240px)
|
||||
|
||||
検索方法:
|
||||
LoRAファイル名(拡張子除く)で始まる画像ファイルを検索
|
||||
LoRAファイル名(拡張子除く)で始まるファイルを検索
|
||||
優先順位:
|
||||
1. 静止画像 (.png, .jpg, .jpeg, .webp)
|
||||
2. 動画ファイル (.gif, .webp, .mp4, .webm) の1フレーム目
|
||||
|
||||
例: style_anime_v1.safetensors
|
||||
→ style_anime_v1.png
|
||||
→ style_anime_v1_preview.png
|
||||
→ style_anime_v1_001.jpg
|
||||
→ style_anime_v1_preview.jpg
|
||||
→ style_anime_v1.gif (1フレーム目)
|
||||
→ style_anime_v1.mp4 (1フレーム目、opencv-pythonが必要)
|
||||
→ STYLE_ANIME_V1.PNG (大文字小文字無視)
|
||||
|
||||
リサイズ:
|
||||
長辺を1240pxに統一(アスペクト比保持)
|
||||
例: 1024x768 → 1240x930
|
||||
512x512 → 1240x1240
|
||||
800x1200 → 826x1240
|
||||
|
||||
Returns:
|
||||
torch.Tensor: (H, W, C) 形式、見つからない場合はNone
|
||||
@@ -774,57 +781,189 @@ class RandomLoRALoader:
|
||||
base_name = os.path.splitext(os.path.basename(lora_path))[0]
|
||||
folder = os.path.dirname(lora_path)
|
||||
|
||||
# 画像拡張子
|
||||
image_extensions = ('.png', '.jpg', '.jpeg', '.webp', '.gif')
|
||||
# ファイル拡張子(優先順位順)
|
||||
static_image_exts = ('.png', '.jpg', '.jpeg')
|
||||
animated_image_exts = ('.gif', '.webp') # Pillowで対応可能
|
||||
video_exts = ('.mp4', '.webm', '.avi', '.mov') # opencv-python必要
|
||||
|
||||
try:
|
||||
# 同じフォルダ内のファイルを検索
|
||||
for file in os.listdir(folder):
|
||||
# 部分一致(大文字小文字無視)
|
||||
files = os.listdir(folder)
|
||||
|
||||
# 部分一致するファイルを収集
|
||||
matched_files = []
|
||||
for file in files:
|
||||
if file.lower().startswith(base_name.lower()):
|
||||
# 画像ファイルか確認
|
||||
if file.lower().endswith(image_extensions):
|
||||
preview_path = os.path.join(folder, file)
|
||||
try:
|
||||
img = Image.open(preview_path).convert('RGB')
|
||||
|
||||
# 長辺を1240pxにリサイズ(アスペクト比保持)
|
||||
width, height = img.size
|
||||
max_size = 1240
|
||||
|
||||
if max(width, height) > max_size:
|
||||
# 長辺が1240pxを超える場合はリサイズ
|
||||
if width > height:
|
||||
new_width = max_size
|
||||
new_height = int(height * (max_size / width))
|
||||
else:
|
||||
new_height = max_size
|
||||
new_width = int(width * (max_size / height))
|
||||
|
||||
img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
|
||||
elif max(width, height) < max_size:
|
||||
# 長辺が1240px未満の場合は拡大
|
||||
if width > height:
|
||||
new_width = max_size
|
||||
new_height = int(height * (max_size / width))
|
||||
else:
|
||||
new_height = max_size
|
||||
new_width = int(width * (max_size / height))
|
||||
|
||||
img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
|
||||
|
||||
# numpy配列に変換 (H, W, C)
|
||||
img_array = np.array(img).astype(np.float32) / 255.0
|
||||
# Tensor化
|
||||
return torch.from_numpy(img_array)
|
||||
except Exception as e:
|
||||
print(f"[RandomLoRALoader] Preview load error ({preview_path}): {e}")
|
||||
continue
|
||||
matched_files.append(file)
|
||||
|
||||
if not matched_files:
|
||||
return None
|
||||
|
||||
# 優先順位でソート
|
||||
def get_priority(filename):
|
||||
lower = filename.lower()
|
||||
if any(lower.endswith(ext) for ext in static_image_exts):
|
||||
return 0 # 最優先
|
||||
elif any(lower.endswith(ext) for ext in animated_image_exts):
|
||||
return 1 # 次優先
|
||||
elif any(lower.endswith(ext) for ext in video_exts):
|
||||
return 2 # 最後
|
||||
else:
|
||||
return 999 # その他
|
||||
|
||||
matched_files.sort(key=get_priority)
|
||||
|
||||
# 各ファイルを試す
|
||||
for file in matched_files:
|
||||
preview_path = os.path.join(folder, file)
|
||||
lower_file = file.lower()
|
||||
|
||||
# 静止画像
|
||||
if any(lower_file.endswith(ext) for ext in static_image_exts):
|
||||
img = self._load_static_image(preview_path)
|
||||
if img is not None:
|
||||
return img
|
||||
|
||||
# アニメーション画像(GIF/WebP)
|
||||
elif any(lower_file.endswith(ext) for ext in animated_image_exts):
|
||||
img = self._load_animated_image_first_frame(preview_path)
|
||||
if img is not None:
|
||||
return img
|
||||
|
||||
# 動画ファイル(opencv-python必要)
|
||||
elif any(lower_file.endswith(ext) for ext in video_exts):
|
||||
img = self._load_video_first_frame(preview_path)
|
||||
if img is not None:
|
||||
return img
|
||||
|
||||
except Exception as e:
|
||||
print(f"[RandomLoRALoader] Folder read error: {e}")
|
||||
|
||||
return None
|
||||
|
||||
def _load_static_image(self, image_path):
|
||||
"""静止画像を読み込み"""
|
||||
try:
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
img = Image.open(image_path).convert('RGB')
|
||||
return self._resize_and_convert_image(img)
|
||||
except Exception as e:
|
||||
print(f"[RandomLoRALoader] Static image load error ({image_path}): {e}")
|
||||
return None
|
||||
|
||||
def _load_animated_image_first_frame(self, image_path):
|
||||
"""アニメーション画像(GIF/WebP)の1フレーム目を読み込み"""
|
||||
try:
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
img = Image.open(image_path)
|
||||
|
||||
# 1フレーム目に移動
|
||||
if hasattr(img, 'seek'):
|
||||
img.seek(0)
|
||||
|
||||
img = img.convert('RGB')
|
||||
return self._resize_and_convert_image(img)
|
||||
except Exception as e:
|
||||
print(f"[RandomLoRALoader] Animated image load error ({image_path}): {e}")
|
||||
return None
|
||||
|
||||
def _load_video_first_frame(self, video_path):
|
||||
"""動画ファイルの1フレーム目を読み込み(opencv-python必要)"""
|
||||
try:
|
||||
import cv2
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
# OpenCVで動画を開く
|
||||
cap = cv2.VideoCapture(video_path)
|
||||
|
||||
if not cap.isOpened():
|
||||
return None
|
||||
|
||||
# 1フレーム目を読み込み
|
||||
ret, frame = cap.read()
|
||||
cap.release()
|
||||
|
||||
if not ret:
|
||||
return None
|
||||
|
||||
# BGR → RGB 変換
|
||||
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
||||
|
||||
# PIL Imageに変換
|
||||
img = Image.fromarray(frame_rgb)
|
||||
|
||||
return self._resize_and_convert_image(img)
|
||||
|
||||
except ImportError:
|
||||
# opencv-pythonがインストールされていない(初回のみ警告)
|
||||
if not RandomLoRALoader._opencv_warning_shown:
|
||||
print("=" * 60)
|
||||
print("[RandomLoRALoader] Video Preview Support")
|
||||
print("=" * 60)
|
||||
print(f"Video file detected: {os.path.basename(video_path)}")
|
||||
print("opencv-python is not installed.")
|
||||
print("")
|
||||
print("To enable video preview support, install:")
|
||||
print(" pip install opencv-python")
|
||||
print("")
|
||||
print("Static images (.png/.jpg) and animated images (.gif/.webp)")
|
||||
print("will continue to work without opencv-python.")
|
||||
print("=" * 60)
|
||||
RandomLoRALoader._opencv_warning_shown = True
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"[RandomLoRALoader] Video load error ({video_path}): {e}")
|
||||
return None
|
||||
|
||||
def _resize_and_convert_image(self, img):
|
||||
"""画像をリサイズしてTensorに変換"""
|
||||
try:
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
# 長辺を1240pxにリサイズ(アスペクト比保持)
|
||||
width, height = img.size
|
||||
max_size = 1240
|
||||
|
||||
if max(width, height) > max_size:
|
||||
# 長辺が1240pxを超える場合はリサイズ
|
||||
if width > height:
|
||||
new_width = max_size
|
||||
new_height = int(height * (max_size / width))
|
||||
else:
|
||||
new_height = max_size
|
||||
new_width = int(width * (max_size / height))
|
||||
|
||||
img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
|
||||
elif max(width, height) < max_size:
|
||||
# 長辺が1240px未満の場合は拡大
|
||||
if width > height:
|
||||
new_width = max_size
|
||||
new_height = int(height * (max_size / width))
|
||||
else:
|
||||
new_height = max_size
|
||||
new_width = int(width * (max_size / height))
|
||||
|
||||
img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
|
||||
|
||||
# numpy配列に変換 (H, W, C)
|
||||
img_array = np.array(img).astype(np.float32) / 255.0
|
||||
# Tensor化
|
||||
return torch.from_numpy(img_array)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[RandomLoRALoader] Image resize/convert error: {e}")
|
||||
return None
|
||||
|
||||
def _generate_preview_batch(self, preview_images):
|
||||
"""
|
||||
プレビュー画像のバッチを生成(1240pxに統一、パディング)
|
||||
|
||||