Fixed an issue
This commit is contained in:
@@ -2,6 +2,38 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [1.3.0] - 2026-09-11
|
||||
|
||||
### Fixed
|
||||
|
||||
#### Symlinked folders were skipped during scanning
|
||||
- Folder scans now follow symbolic links, matching ComfyUI's own behaviour (`folder_paths.recursive_search` uses `followlinks=True`)
|
||||
- Previously, a subfolder that was a symlink was skipped entirely, so LoRAs behind a link never appeared as candidates — even though they showed up fine in ComfyUI's native LoRA dropdown
|
||||
- Affects all three nodes:
|
||||
- **Random LoRA Loader**: `glob.glob("**/*.safetensors")` replaced with `os.walk(followlinks=True)` (glob's `**` does not descend into symlinked directories)
|
||||
- **Filtered Random LoRA Loader** / **Filtered Random LoRA Loader (LBW)**: `os.walk()` now passes `followlinks=True`
|
||||
- Symlink loops are detected via a `realpath` set and skipped, so a cyclic link cannot hang the scan
|
||||
- A folder reachable through two different links is no longer scanned twice
|
||||
|
||||
#### Redundant error logging on non-safetensors LoRAs
|
||||
- `_load_embedded_metadata` now returns early for `.pt` / `.ckpt` files instead of calling `safe_open`, which can only read `.safetensors`
|
||||
- Previously every selected `.pt` / `.ckpt` produced a spurious "metadata read error" log line
|
||||
- Trigger words for these formats still come from `.metadata.json` / `.info` sidecar files as before
|
||||
|
||||
### Changed
|
||||
|
||||
#### Seed reproducibility — candidate list is now sorted
|
||||
- The candidate file list is sorted before random selection
|
||||
- Previously the order came straight from the filesystem, so **the same seed could produce different selections on different machines**. Sorting makes a seed reproducible across environments
|
||||
- ⚠️ Because the ordering changed, an existing workflow may select a different set of LoRAs after upgrading even with a fixed seed. If a result you were relying on changes, re-roll or re-pick the seed
|
||||
|
||||
#### Random LoRA Loader: extension support aligned with the Filtered nodes
|
||||
- Now scans `.safetensors`, `.pt`, and `.ckpt` (previously `.safetensors` only)
|
||||
- All three nodes now return an identical candidate list for the same folder
|
||||
- Extension matching is case-insensitive, so `.SAFETENSORS` is picked up on Linux as well (glob was case-sensitive there)
|
||||
|
||||
---
|
||||
|
||||
## [1.2.0] - 2026-01-13
|
||||
|
||||
### Added
|
||||
|
||||
@@ -245,6 +245,20 @@ Group 3: Unused
|
||||
|
||||
## Settings
|
||||
|
||||
### Folder Scanning
|
||||
|
||||
All three nodes scan the folders you point them at the same way.
|
||||
|
||||
**Supported extensions:** `.safetensors`, `.pt`, `.ckpt` (case-insensitive).
|
||||
|
||||
> Note: `.pt` and `.ckpt` files have no readable embedded metadata, so trigger words for them come only from `.metadata.json` / `.info` sidecar files.
|
||||
|
||||
**Symbolic links are followed.** A subfolder that is a symlink is descended into, matching ComfyUI's own folder scan — so LoRAs that only exist behind a link are included as candidates, just as they appear in ComfyUI's native LoRA dropdown. Link loops are detected and skipped rather than hanging the scan, and a folder reachable through two different links is only scanned once.
|
||||
|
||||
**The candidate list is sorted before selection.** This makes a given `seed` reproduce the same selection on any machine. Prior to v1.3.0 the order came straight from the filesystem, so the same seed could pick different LoRAs on a different PC.
|
||||
|
||||
> ⚠️ Because the ordering changed, an existing workflow may select a different set of LoRAs after upgrading to v1.3.0, even with a fixed seed. If a result you were relying on changes, re-roll or re-pick the seed.
|
||||
|
||||
### Common Settings
|
||||
|
||||
| Setting | Description | Default |
|
||||
@@ -303,7 +317,7 @@ Each group can be configured individually:
|
||||
| Setting | Description | Default |
|
||||
|---------|-------------|---------|
|
||||
| `lora_folder_path_X` | LoRA folder absolute path | (empty) |
|
||||
| `include_subfolders_X` | Include subfolders | `true` |
|
||||
| `include_subfolders_X` | Include subfolders (symbolic links are followed) | `true` |
|
||||
| `unique_by_filename_X` | Exclude duplicate filenames | `true` |
|
||||
| `model_strength_X` | MODEL application strength | `"1.0"` |
|
||||
| `clip_strength_X` | CLIP application strength | `"1.0"` |
|
||||
@@ -558,7 +572,7 @@ anime style, alice, blonde hair, 1girl, beautiful
|
||||
| `num_loras` | Number of LoRAs to select | `1` |
|
||||
| `model_strength` | MODEL strength (fixed or range) | `"1.0"` |
|
||||
| `clip_strength` | CLIP strength (fixed or range) | `"1.0"` |
|
||||
| `include_subfolders` | Include subfolders | `true` |
|
||||
| `include_subfolders` | Include subfolders (symbolic links are followed) | `true` |
|
||||
| `unique_by_filename` | Exclude duplicate filenames | `true` |
|
||||
|
||||
### Keyword Filter Syntax
|
||||
@@ -923,7 +937,7 @@ Enable preview display to:
|
||||
|
||||
**Check:**
|
||||
1. LoRA folder path is correct (absolute path)
|
||||
2. `.safetensors` files exist in the folder
|
||||
2. `.safetensors`, `.pt` or `.ckpt` files exist in the folder
|
||||
3. `include_subfolders` setting if LoRAs are in subfolders
|
||||
4. Console for error messages
|
||||
|
||||
|
||||
@@ -208,6 +208,8 @@ Detected: SDXL (requires 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")
|
||||
|
||||
> Folder scanning behaviour (supported extensions, symbolic links, sorted candidate order) is shared with the other nodes — see [Folder Scanning](README.md#folder-scanning) in the main README.
|
||||
|
||||
#### **Keyword Filter**
|
||||
- `keyword_filter`: Space-separated keywords (e.g., `style anime` or `"anime style" red`)
|
||||
- `filter_mode`: AND / OR
|
||||
|
||||
@@ -176,6 +176,8 @@ BASE,IN (OFF),M00,OUT (ON)
|
||||
num_loras: 1
|
||||
```
|
||||
|
||||
> フォルダスキャンの挙動(対象拡張子、シンボリックリンクの追跡、候補リストのソート)は他のノードと共通です。詳細は[README_ja.md の「フォルダスキャンについて」](README_ja.md#フォルダスキャンについて)を参照してください。
|
||||
|
||||
3. **LBWモードを選択**
|
||||
```
|
||||
weight_mode: "Style Focused"
|
||||
|
||||
+17
-3
@@ -245,6 +245,20 @@ rmdir /s RandomLoRALoader
|
||||
|
||||
## 設定
|
||||
|
||||
### フォルダスキャンについて
|
||||
|
||||
3つのノードとも、指定されたフォルダを同じ方法でスキャンします。
|
||||
|
||||
**対象拡張子:** `.safetensors` / `.pt` / `.ckpt`(大文字小文字は区別しません)
|
||||
|
||||
> 注: `.pt`と`.ckpt`は埋め込みメタデータを読み取れないため、トリガーワードは`.metadata.json`や`.info`のサイドカーファイルからのみ取得されます。
|
||||
|
||||
**シンボリックリンクを辿ります。** サブフォルダがシンボリックリンクの場合もその中まで検索します。ComfyUI本体のフォルダスキャンと同じ挙動なので、リンク先にしか存在しないLoRAも、ComfyUIネイティブのLoRAドロップダウンと同様に候補へ含まれます。リンクが循環している場合は検出してスキップするためスキャンが止まることはなく、同じフォルダに2本のリンクが張られていても二重に走査されません。
|
||||
|
||||
**候補リストは選択前にソートされます。** これにより、同じ`seed`ならどのマシンでも同じ選択結果が再現されます。v1.3.0より前は列挙順がファイルシステム任せだったため、PCが変わると同じシードでも違うLoRAが選ばれることがありました。
|
||||
|
||||
> ⚠️ 並び順が変わるため、v1.3.0へのアップグレード後は、シードを固定していても既存のワークフローで選ばれるLoRAが変わる可能性があります。依存していた結果が変わった場合はシードを振り直してください。
|
||||
|
||||
### 共通設定
|
||||
|
||||
| 設定項目 | 説明 | デフォルト |
|
||||
@@ -303,7 +317,7 @@ LoRA適用: フォルダ指定機能を使用 ✅
|
||||
| 設定項目 | 説明 | デフォルト |
|
||||
|---------|------|-----------|
|
||||
| `lora_folder_path_X` | LoRAフォルダの絶対パス | (空) |
|
||||
| `include_subfolders_X` | サブフォルダを含める | `true` |
|
||||
| `include_subfolders_X` | サブフォルダを含める(シンボリックリンクも辿ります) | `true` |
|
||||
| `unique_by_filename_X` | 重複ファイル名を除外 | `true` |
|
||||
| `model_strength_X` | MODEL適用強度 | `"1.0"` |
|
||||
| `clip_strength_X` | CLIP適用強度 | `"1.0"` |
|
||||
@@ -558,7 +572,7 @@ anime style, alice, blonde hair, 1girl, beautiful
|
||||
| `num_loras` | 選択するLoRA数 | `1` |
|
||||
| `model_strength` | MODEL強度(固定または範囲) | `"1.0"` |
|
||||
| `clip_strength` | CLIP強度(固定または範囲) | `"1.0"` |
|
||||
| `include_subfolders` | サブフォルダを含める | `true` |
|
||||
| `include_subfolders` | サブフォルダを含める(シンボリックリンクも辿ります) | `true` |
|
||||
| `unique_by_filename` | 重複ファイル名を除外 | `true` |
|
||||
|
||||
### キーワードフィルタ構文
|
||||
@@ -923,7 +937,7 @@ keyword_filter: '"anime style" detailed'
|
||||
|
||||
**確認事項:**
|
||||
1. LoRAフォルダパスが正しい(絶対パス)
|
||||
2. フォルダに`.safetensors`ファイルが存在
|
||||
2. フォルダに`.safetensors` / `.pt` / `.ckpt`ファイルが存在
|
||||
3. LoRAがサブフォルダにある場合は`include_subfolders`設定
|
||||
4. コンソールでエラーメッセージを確認
|
||||
|
||||
|
||||
@@ -277,7 +277,22 @@ class FilteredRandomLoRALoader:
|
||||
extensions = ['.safetensors', '.pt', '.ckpt']
|
||||
|
||||
if include_subfolders:
|
||||
for root, dirs, files in os.walk(folder_path):
|
||||
# followlinks=True はComfyUI本体のフォルダスキャン
|
||||
# (folder_paths.recursive_search) と同じ挙動。これがないと
|
||||
# シンボリックリンクのサブフォルダが丸ごとスキップされ、
|
||||
# ComfyUIネイティブのLoRAドロップダウンには出てくるのに
|
||||
# ここでは候補に入らない、という食い違いが起きる。
|
||||
#
|
||||
# realpathの集合はリンクを辿る代償。リンクが循環している場合に
|
||||
# スキャンが無限ループするのを防ぎ、同じフォルダに2本のリンクが
|
||||
# 張られている場合の二重スキャンも抑止する。
|
||||
seen_dirs = set()
|
||||
for root, dirs, files in os.walk(folder_path, followlinks=True):
|
||||
real_root = os.path.realpath(root)
|
||||
if real_root in seen_dirs:
|
||||
dirs[:] = []
|
||||
continue
|
||||
seen_dirs.add(real_root)
|
||||
for file in files:
|
||||
if any(file.lower().endswith(ext) for ext in extensions):
|
||||
lora_files.append(os.path.join(root, file))
|
||||
@@ -287,7 +302,10 @@ class FilteredRandomLoRALoader:
|
||||
if os.path.isfile(file_path) and any(file.lower().endswith(ext) for ext in extensions):
|
||||
lora_files.append(file_path)
|
||||
|
||||
return lora_files
|
||||
# ソートして返す。以前は列挙順がファイルシステム任せだったため、
|
||||
# 同じシードでもマシンが変われば選ばれるLoRAが違っていた。
|
||||
# ソートすることでシード指定が環境をまたいで再現するようになる。
|
||||
return sorted(lora_files)
|
||||
|
||||
def _parse_keywords(self, keyword_filter):
|
||||
"""
|
||||
@@ -518,6 +536,11 @@ class FilteredRandomLoRALoader:
|
||||
|
||||
def _load_embedded_metadata(self, lora_path):
|
||||
"""LoRAファイルから埋め込みメタデータを読み込み(Civitai形式に変換)"""
|
||||
# safe_open は .safetensors しか読めない。候補には .pt/.ckpt も
|
||||
# 含まれるため、ここで弾いておかないと選択のたびに無駄な例外が出る
|
||||
if not lora_path.lower().endswith(".safetensors"):
|
||||
return None
|
||||
|
||||
try:
|
||||
from safetensors.torch import safe_open
|
||||
|
||||
|
||||
@@ -323,7 +323,22 @@ class FilteredRandomLoRALoaderLBW:
|
||||
extensions = ['.safetensors', '.pt', '.ckpt']
|
||||
|
||||
if include_subfolders:
|
||||
for root, dirs, files in os.walk(folder_path):
|
||||
# followlinks=True はComfyUI本体のフォルダスキャン
|
||||
# (folder_paths.recursive_search) と同じ挙動。これがないと
|
||||
# シンボリックリンクのサブフォルダが丸ごとスキップされ、
|
||||
# ComfyUIネイティブのLoRAドロップダウンには出てくるのに
|
||||
# ここでは候補に入らない、という食い違いが起きる。
|
||||
#
|
||||
# realpathの集合はリンクを辿る代償。リンクが循環している場合に
|
||||
# スキャンが無限ループするのを防ぎ、同じフォルダに2本のリンクが
|
||||
# 張られている場合の二重スキャンも抑止する。
|
||||
seen_dirs = set()
|
||||
for root, dirs, files in os.walk(folder_path, followlinks=True):
|
||||
real_root = os.path.realpath(root)
|
||||
if real_root in seen_dirs:
|
||||
dirs[:] = []
|
||||
continue
|
||||
seen_dirs.add(real_root)
|
||||
for file in files:
|
||||
if any(file.lower().endswith(ext) for ext in extensions):
|
||||
lora_files.append(os.path.join(root, file))
|
||||
@@ -333,7 +348,10 @@ class FilteredRandomLoRALoaderLBW:
|
||||
if os.path.isfile(file_path) and any(file.lower().endswith(ext) for ext in extensions):
|
||||
lora_files.append(file_path)
|
||||
|
||||
return lora_files
|
||||
# ソートして返す。以前は列挙順がファイルシステム任せだったため、
|
||||
# 同じシードでもマシンが変われば選ばれるLoRAが違っていた。
|
||||
# ソートすることでシード指定が環境をまたいで再現するようになる。
|
||||
return sorted(lora_files)
|
||||
|
||||
def _parse_keywords(self, keyword_filter):
|
||||
"""
|
||||
@@ -564,6 +582,11 @@ class FilteredRandomLoRALoaderLBW:
|
||||
|
||||
def _load_embedded_metadata(self, lora_path):
|
||||
"""LoRAファイルから埋め込みメタデータを読み込み(Civitai形式に変換)"""
|
||||
# safe_open は .safetensors しか読めない。候補には .pt/.ckpt も
|
||||
# 含まれるため、ここで弾いておかないと選択のたびに無駄な例外が出る
|
||||
if not lora_path.lower().endswith(".safetensors"):
|
||||
return None
|
||||
|
||||
try:
|
||||
from safetensors.torch import safe_open
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "randomloraloader"
|
||||
description = "Randomly select and apply LoRAs with keyword filtering, LoRA Block Weight support, preview images, and multiple folder support for ComfyUI"
|
||||
version = "1.2.0"
|
||||
version = "1.3.0"
|
||||
license = {file = "LICENSE"}
|
||||
|
||||
classifiers = [
|
||||
|
||||
+43
-6
@@ -222,7 +222,7 @@ class RandomLoRALoader:
|
||||
|
||||
def _find_lora_files(self, folder_path, include_subfolders):
|
||||
"""
|
||||
指定フォルダ内のLoRAファイル(.safetensors)を検索
|
||||
指定フォルダ内のLoRAファイル(.safetensors / .pt / .ckpt)を検索
|
||||
|
||||
Args:
|
||||
folder_path: 検索対象フォルダの絶対パス
|
||||
@@ -235,9 +235,41 @@ class RandomLoRALoader:
|
||||
print(f"[RandomLoRALoader] フォルダが存在しません: {folder_path}")
|
||||
return []
|
||||
|
||||
pattern = "**/*.safetensors" if include_subfolders else "*.safetensors"
|
||||
lora_files = glob.glob(os.path.join(folder_path, pattern), recursive=include_subfolders)
|
||||
|
||||
lora_files = []
|
||||
# Filtered版と対象拡張子を揃えている
|
||||
extensions = ['.safetensors', '.pt', '.ckpt']
|
||||
|
||||
if include_subfolders:
|
||||
# glob の "**" はシンボリックリンクのディレクトリを辿らないため
|
||||
# os.walk(followlinks=True) に置き換えている。ComfyUI本体の
|
||||
# フォルダスキャン (folder_paths.recursive_search) と挙動を揃える
|
||||
# ためで、これがないとリンク先にしか存在しないLoRAが、
|
||||
# ComfyUIネイティブのドロップダウンには出るのにここでは
|
||||
# 候補に入らない、という食い違いが起きる。
|
||||
#
|
||||
# realpathの集合はリンクを辿る代償。リンクが循環している場合に
|
||||
# スキャンが無限ループするのを防ぎ、同じフォルダに2本のリンクが
|
||||
# 張られている場合の二重スキャンも抑止する。
|
||||
seen_dirs = set()
|
||||
for root, dirs, files in os.walk(folder_path, followlinks=True):
|
||||
real_root = os.path.realpath(root)
|
||||
if real_root in seen_dirs:
|
||||
dirs[:] = []
|
||||
continue
|
||||
seen_dirs.add(real_root)
|
||||
for file in files:
|
||||
if any(file.lower().endswith(ext) for ext in extensions):
|
||||
lora_files.append(os.path.join(root, file))
|
||||
else:
|
||||
for file in os.listdir(folder_path):
|
||||
file_path = os.path.join(folder_path, file)
|
||||
if os.path.isfile(file_path) and any(file.lower().endswith(ext) for ext in extensions):
|
||||
lora_files.append(file_path)
|
||||
|
||||
# ソートして返す。以前は列挙順がファイルシステム任せだったため、
|
||||
# 同じシードでもマシンが変われば選ばれるLoRAが違っていた。
|
||||
# ソートすることでシード指定が環境をまたいで再現するようになる。
|
||||
lora_files = sorted(lora_files)
|
||||
print(f"[RandomLoRALoader] 検出されたLoRA数: {len(lora_files)}")
|
||||
return lora_files
|
||||
|
||||
@@ -389,12 +421,12 @@ class RandomLoRALoader:
|
||||
3. LoRA本体ファイルの埋め込みメタデータ
|
||||
|
||||
Args:
|
||||
lora_path: LoRAファイルパス (.safetensors)
|
||||
lora_path: LoRAファイルパス (.safetensors / .pt / .ckpt)
|
||||
|
||||
Returns:
|
||||
dict: JSONデータ(読み込み失敗時はNone)
|
||||
"""
|
||||
# .safetensorsを除いたファイル名を取得
|
||||
# 拡張子を除いたファイル名を取得
|
||||
base_name = os.path.splitext(lora_path)[0]
|
||||
|
||||
# 優先順位1: .metadata.json (ComfyUI Lora Manager)
|
||||
@@ -437,6 +469,11 @@ class RandomLoRALoader:
|
||||
if not SAFETENSORS_AVAILABLE:
|
||||
return None
|
||||
|
||||
# safe_open は .safetensors しか読めない。.pt/.ckpt も候補に含むように
|
||||
# なったため、ここで弾いておかないと選択のたびにエラーログが出る
|
||||
if not lora_path.lower().endswith(".safetensors"):
|
||||
return None
|
||||
|
||||
if not os.path.exists(lora_path):
|
||||
return None
|
||||
|
||||
|
||||
Reference in New Issue
Block a user