A download by name says where the list's file is; an entry of no stated type no longer fits a node of known type; without the Manager, the page says it would be one more source. docs/model-check-rules.md gets a section on the Manager. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
109 lines
4.4 KiB
Python
109 lines
4.4 KiB
Python
"""Where ComfyUI-Manager's model list says a model file comes from, for Model Check's downloads
|
|
(download_sources.find_source). The list (model-list.json, a few hundred common models: base
|
|
models, VAEs, text encoders, upscalers, ControlNets… mostly on Hugging Face) is read from the
|
|
Manager installed here; without it there is nothing to find. A list entry is only a file of the
|
|
same name: download_sources checks it against the workflow's fingerprint, or offers it as such.
|
|
"""
|
|
|
|
import importlib.util
|
|
import json
|
|
import os
|
|
|
|
import folder_paths
|
|
|
|
# The list's model types and save paths -> ComfyUI folder types.
|
|
TYPE_FOLDERS = {
|
|
"checkpoint": "checkpoints", "checkpoints": "checkpoints", "lora": "loras", "loras": "loras",
|
|
"vae": "vae", "taesd": "vae_approx", "vae_approx": "vae_approx", "clip": "text_encoders",
|
|
"text_encoders": "text_encoders", "clip_vision": "clip_vision", "unet": "diffusion_models",
|
|
"diffusion_model": "diffusion_models", "diffusion_models": "diffusion_models", "controlnet": "controlnet",
|
|
"upscale": "upscale_models", "upscale_models": "upscale_models", "embeddings": "embeddings",
|
|
"embedding": "embeddings", "gligen": "gligen", "style_models": "style_models",
|
|
}
|
|
# ComfyUI's older names for the same folders.
|
|
SAME_FOLDER = {"unet": "diffusion_models", "clip": "text_encoders"}
|
|
|
|
_cache = {"path": "", "mtime": 0.0, "index": {}}
|
|
|
|
|
|
def list_path():
|
|
"""The Manager's model-list.json on this computer, or ''."""
|
|
roots = []
|
|
try:
|
|
roots = list(folder_paths.get_folder_paths("custom_nodes") or [])
|
|
except Exception:
|
|
pass
|
|
for root in roots:
|
|
try:
|
|
names = sorted(os.listdir(root))
|
|
except OSError:
|
|
continue
|
|
for name in names:
|
|
path = os.path.join(root, name, "model-list.json")
|
|
if "manager" in name.lower() and os.path.isfile(path):
|
|
return path
|
|
try: # the Manager installed as a Python package
|
|
spec = importlib.util.find_spec("comfyui_manager")
|
|
for location in (spec.submodule_search_locations or []) if spec else []:
|
|
path = os.path.join(location, "model-list.json")
|
|
if os.path.isfile(path):
|
|
return path
|
|
except (ImportError, ValueError):
|
|
pass
|
|
return ""
|
|
|
|
|
|
def site_of(url):
|
|
"""Where a list entry's file is: the host and, on Hugging Face or GitHub, the repository."""
|
|
parts = str(url or "").split("/")
|
|
if len(parts) < 3:
|
|
return ""
|
|
host = parts[2].lower()
|
|
return "/".join([host, *parts[3:5]]) if host in ("huggingface.co", "hf-mirror.com", "github.com") and len(parts) >= 5 else host
|
|
|
|
|
|
def _folder(entry):
|
|
path = str(entry.get("save_path") or "").replace("\\", "/").strip("/")
|
|
first = path.split("/")[0].lower() if path and path != "default" else ""
|
|
return TYPE_FOLDERS.get(first) or TYPE_FOLDERS.get(str(entry.get("type") or "").lower(), "")
|
|
|
|
|
|
def _index():
|
|
path = list_path()
|
|
if not path:
|
|
return {}
|
|
try:
|
|
mtime = os.path.getmtime(path)
|
|
except OSError:
|
|
return {}
|
|
if path != _cache["path"] or mtime != _cache["mtime"]:
|
|
index = {}
|
|
try:
|
|
with open(path, encoding="utf-8") as source:
|
|
models = json.load(source).get("models") or []
|
|
except (OSError, ValueError, AttributeError):
|
|
models = []
|
|
for entry in models:
|
|
if not isinstance(entry, dict) or not entry.get("filename") or not str(entry.get("url", "")).startswith("https://"):
|
|
continue
|
|
index.setdefault(str(entry["filename"]).lower(), []).append({
|
|
"name": str(entry.get("name") or entry["filename"]), "url": entry["url"],
|
|
"folder": _folder(entry), "base": str(entry.get("base") or ""),
|
|
})
|
|
_cache.update(path=path, mtime=mtime, index=index)
|
|
return _cache["index"]
|
|
|
|
|
|
def candidates(file_name, folder_type=""):
|
|
"""The list's entries for a file of this name (any case) that fit `folder_type`, one per URL.
|
|
When the node's folder type is known, an entry whose type the list does not say does not fit."""
|
|
wanted = SAME_FOLDER.get(folder_type, folder_type)
|
|
seen, found = set(), []
|
|
for entry in _index().get(os.path.basename(str(file_name)).lower(), []):
|
|
if wanted and SAME_FOLDER.get(entry["folder"], entry["folder"]) != wanted:
|
|
continue
|
|
if entry["url"] not in seen:
|
|
seen.add(entry["url"])
|
|
found.append(entry)
|
|
return found
|