Show more model metadata in Model Info view.
- In model tab, optionally view embedded thumbnails in safetensors (slow if there are many safetensors without previews).
This commit is contained in:
123
__init__.py
123
__init__.py
@@ -7,6 +7,7 @@ import sys
|
|||||||
import copy
|
import copy
|
||||||
import importlib
|
import importlib
|
||||||
import re
|
import re
|
||||||
|
import base64
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
import server
|
import server
|
||||||
@@ -149,6 +150,7 @@ def ui_rules():
|
|||||||
Rule("model-search-always-append", "", str),
|
Rule("model-search-always-append", "", str),
|
||||||
Rule("model-persistent-search", True, bool),
|
Rule("model-persistent-search", True, bool),
|
||||||
Rule("model-show-label-extensions", False, bool),
|
Rule("model-show-label-extensions", False, bool),
|
||||||
|
Rule("model-preview-fallback-search-safetensors-thumbnail", False, bool),
|
||||||
Rule("model-show-add-button", True, bool),
|
Rule("model-show-add-button", True, bool),
|
||||||
Rule("model-show-copy-button", True, bool),
|
Rule("model-show-copy-button", True, bool),
|
||||||
Rule("model-add-embedding-extension", False, bool),
|
Rule("model-add-embedding-extension", False, bool),
|
||||||
@@ -193,17 +195,28 @@ async def get_model_preview(request):
|
|||||||
|
|
||||||
image_path = no_preview_image
|
image_path = no_preview_image
|
||||||
image_extension = "png"
|
image_extension = "png"
|
||||||
|
image_data = None
|
||||||
if uri != "no-preview":
|
if uri != "no-preview":
|
||||||
sep = os.path.sep
|
sep = os.path.sep
|
||||||
uri = uri.replace("/" if sep == "\\" else "/", os.path.sep)
|
uri = uri.replace("/" if sep == "\\" else "/", sep)
|
||||||
image_path, _ = search_path_to_system_path(uri)
|
path, _ = search_path_to_system_path(uri)
|
||||||
if os.path.exists(image_path):
|
head, extension = os.path.splitext(path)
|
||||||
_, image_extension = os.path.splitext(uri)
|
if os.path.exists(path):
|
||||||
image_extension = image_extension[1:]
|
image_extension = extension[1:]
|
||||||
|
image_path = path
|
||||||
|
elif os.path.exists(head) and os.path.splitext(head)[1] == ".safetensors":
|
||||||
|
image_extension = extension[1:]
|
||||||
|
header = get_safetensor_header(head)
|
||||||
|
metadata = header.get("__metadata__", None)
|
||||||
|
if metadata is not None:
|
||||||
|
thumbnail = metadata.get("modelspec.thumbnail", None)
|
||||||
|
if thumbnail is not None:
|
||||||
|
image_data = thumbnail.split(',')[1]
|
||||||
|
image_data = base64.b64decode(image_data)
|
||||||
|
|
||||||
with open(image_path, "rb") as img_file:
|
if image_data == None:
|
||||||
image_data = img_file.read()
|
with open(image_path, "rb") as file:
|
||||||
|
image_data = file.read()
|
||||||
|
|
||||||
return web.Response(body=image_data, content_type="image/" + image_extension)
|
return web.Response(body=image_data, content_type="image/" + image_extension)
|
||||||
|
|
||||||
@@ -267,7 +280,12 @@ async def delete_model_preview(request):
|
|||||||
|
|
||||||
|
|
||||||
@server.PromptServer.instance.routes.get("/model-manager/models/list")
|
@server.PromptServer.instance.routes.get("/model-manager/models/list")
|
||||||
async def load_download_models(request):
|
async def get_model_list(request):
|
||||||
|
use_safetensor_thumbnail = (
|
||||||
|
config_loader.yaml_load(ui_settings_uri, ui_rules())
|
||||||
|
.get("model-preview-fallback-search-safetensors-thumbnail", False)
|
||||||
|
)
|
||||||
|
|
||||||
model_types = os.listdir(comfyui_model_uri)
|
model_types = os.listdir(comfyui_model_uri)
|
||||||
model_types.remove("configs")
|
model_types.remove("configs")
|
||||||
model_types.sort()
|
model_types.sort()
|
||||||
@@ -277,7 +295,7 @@ async def load_download_models(request):
|
|||||||
model_extensions = tuple(folder_paths_get_supported_pt_extensions(model_type))
|
model_extensions = tuple(folder_paths_get_supported_pt_extensions(model_type))
|
||||||
file_infos = []
|
file_infos = []
|
||||||
for base_path_index, model_base_path in enumerate(folder_paths_get_folder_paths(model_type)):
|
for base_path_index, model_base_path in enumerate(folder_paths_get_folder_paths(model_type)):
|
||||||
if not os.path.exists(model_base_path): # Bug in main code?
|
if not os.path.exists(model_base_path): # TODO: Bug in main code? ("ComfyUI\output\checkpoints", "ComfyUI\output\clip", "ComfyUI\models\t2i_adapter", "ComfyUI\output\vae")
|
||||||
continue
|
continue
|
||||||
for cwd, _subdirs, files in os.walk(model_base_path):
|
for cwd, _subdirs, files in os.walk(model_base_path):
|
||||||
dir_models = []
|
dir_models = []
|
||||||
@@ -290,7 +308,7 @@ async def load_download_models(request):
|
|||||||
dir_images.append(file)
|
dir_images.append(file)
|
||||||
|
|
||||||
for model in dir_models:
|
for model in dir_models:
|
||||||
model_name, _ = os.path.splitext(model)
|
model_name, model_ext = os.path.splitext(model)
|
||||||
image = None
|
image = None
|
||||||
image_modified = None
|
image_modified = None
|
||||||
for iImage in range(len(dir_images)-1, -1, -1):
|
for iImage in range(len(dir_images)-1, -1, -1):
|
||||||
@@ -304,6 +322,19 @@ async def load_download_models(request):
|
|||||||
stats = pathlib.Path(abs_path).stat()
|
stats = pathlib.Path(abs_path).stat()
|
||||||
model_modified = stats.st_mtime_ns
|
model_modified = stats.st_mtime_ns
|
||||||
model_created = stats.st_ctime_ns
|
model_created = stats.st_ctime_ns
|
||||||
|
if use_safetensor_thumbnail and image is None and model_ext == ".safetensors":
|
||||||
|
# try to fallback on safetensor embedded thumbnail
|
||||||
|
header = get_safetensor_header(abs_path)
|
||||||
|
metadata = header.get("__metadata__", None)
|
||||||
|
if metadata is not None:
|
||||||
|
thumbnail = metadata.get("modelspec.thumbnail", None)
|
||||||
|
if thumbnail is not None:
|
||||||
|
i0 = thumbnail.find("/") + 1
|
||||||
|
i1 = thumbnail.find(";")
|
||||||
|
image_ext = "." + thumbnail[i0:i1]
|
||||||
|
if image_ext in image_extensions:
|
||||||
|
image = model + image_ext
|
||||||
|
image_modified = model_modified
|
||||||
rel_path = "" if cwd == model_base_path else os.path.relpath(cwd, model_base_path)
|
rel_path = "" if cwd == model_base_path else os.path.relpath(cwd, model_base_path)
|
||||||
info = (model, image, base_path_index, rel_path, model_modified, model_created, image_modified)
|
info = (model, image, base_path_index, rel_path, model_modified, model_created, image_modified)
|
||||||
file_infos.append(info)
|
file_infos.append(info)
|
||||||
@@ -386,7 +417,7 @@ def linear_directory_hierarchy(refresh = False):
|
|||||||
|
|
||||||
|
|
||||||
@server.PromptServer.instance.routes.get("/model-manager/models/directory-list")
|
@server.PromptServer.instance.routes.get("/model-manager/models/directory-list")
|
||||||
async def directory_list(request):
|
async def get_directory_list(request):
|
||||||
#body = await request.json()
|
#body = await request.json()
|
||||||
dir_list = linear_directory_hierarchy(True)
|
dir_list = linear_directory_hierarchy(True)
|
||||||
#json.dump(dir_list, sys.stdout, indent=4)
|
#json.dump(dir_list, sys.stdout, indent=4)
|
||||||
@@ -510,9 +541,10 @@ async def get_model_info(request):
|
|||||||
info["File Directory"] = path
|
info["File Directory"] = path
|
||||||
info["File Size"] = str(os.path.getsize(file)) + " bytes"
|
info["File Size"] = str(os.path.getsize(file)) + " bytes"
|
||||||
stats = pathlib.Path(file).stat()
|
stats = pathlib.Path(file).stat()
|
||||||
date_format = "%Y/%m/%d %H:%M:%S"
|
date_format = "%Y-%m-%d %H:%M:%S"
|
||||||
|
date_modified = datetime.fromtimestamp(stats.st_mtime).strftime(date_format)
|
||||||
|
info["Date Modified"] = date_modified
|
||||||
info["Date Created"] = datetime.fromtimestamp(stats.st_ctime).strftime(date_format)
|
info["Date Created"] = datetime.fromtimestamp(stats.st_ctime).strftime(date_format)
|
||||||
info["Date Modified"] = datetime.fromtimestamp(stats.st_mtime).strftime(date_format)
|
|
||||||
|
|
||||||
file_name, _ = os.path.splitext(file)
|
file_name, _ = os.path.splitext(file)
|
||||||
|
|
||||||
@@ -529,11 +561,66 @@ async def get_model_info(request):
|
|||||||
|
|
||||||
header = get_safetensor_header(file)
|
header = get_safetensor_header(file)
|
||||||
metadata = header.get("__metadata__", None)
|
metadata = header.get("__metadata__", None)
|
||||||
|
#json.dump(metadata, sys.stdout, indent=4)
|
||||||
|
#print()
|
||||||
|
|
||||||
|
if metadata is not None and info.get("Preview", None) is None:
|
||||||
|
thumbnail = metadata.get("modelspec.thumbnail")
|
||||||
|
if thumbnail is not None:
|
||||||
|
i0 = thumbnail.find("/") + 1
|
||||||
|
i1 = thumbnail.find(";", i0)
|
||||||
|
thumbnail_extension = "." + thumbnail[i0:i1]
|
||||||
|
if thumbnail_extension in image_extensions:
|
||||||
|
info["Preview"] = {
|
||||||
|
"path": request.query["path"] + thumbnail_extension,
|
||||||
|
"dateModified": date_modified,
|
||||||
|
}
|
||||||
|
|
||||||
if metadata is not None:
|
if metadata is not None:
|
||||||
info["Base Model"] = metadata.get("ss_sd_model_name", "")
|
train_end = metadata.get("modelspec.date", "").replace("T", " ")
|
||||||
info["Clip Skip"] = metadata.get("ss_clip_skip", "")
|
train_start = metadata.get("ss_training_started_at", "")
|
||||||
info["Hash"] = metadata.get("sshs_model_hash", "")
|
if train_start != "":
|
||||||
info["Output Name"] = metadata.get("ss_output_name", "")
|
try:
|
||||||
|
train_start = float(train_start)
|
||||||
|
train_start = datetime.fromtimestamp(train_start).strftime(date_format)
|
||||||
|
except:
|
||||||
|
train_start = ""
|
||||||
|
info["Date Trained"] = (
|
||||||
|
train_start +
|
||||||
|
(" ... " if train_start != "" and train_end != "" else "") +
|
||||||
|
train_end
|
||||||
|
)
|
||||||
|
|
||||||
|
info["Base Training Model"] = metadata.get("ss_sd_model_name", "")
|
||||||
|
info["Base Model"] = metadata.get("ss_base_model_version", "")
|
||||||
|
info["Architecture"] = metadata.get("modelspec.architecture", "") # "stable-diffusion-xl-v1-base"
|
||||||
|
|
||||||
|
clip_skip = metadata.get("ss_clip_skip", "")
|
||||||
|
if clip_skip == "None":
|
||||||
|
clip_skip = ""
|
||||||
|
info["Clip Skip"] = clip_skip # default 1 (disable clip skip)
|
||||||
|
info["Model Sampling Type"] = metadata.get("modelspec.prediction_type", "") # "epsilon"
|
||||||
|
|
||||||
|
# it is unclear what these are
|
||||||
|
#info["Hash SHA256"] = metadata.get("modelspec.hash_sha256", "")
|
||||||
|
#info["SSHS Model Hash"] = metadata.get("sshs_model_hash", "")
|
||||||
|
#info["SSHS Legacy Hash"] = metadata.get("sshs_legacy_hash", "")
|
||||||
|
#info["New SD Model Hash"] = metadata.get("ss_new_sd_model_hash", "")
|
||||||
|
|
||||||
|
#info["Output Name"] = metadata.get("ss_output_name", "")
|
||||||
|
#info["Title"] = metadata.get("modelspec.title", "")
|
||||||
|
info["Author"] = metadata.get("modelspec.author", "")
|
||||||
|
info["License"] = metadata.get("modelspec.license", "")
|
||||||
|
|
||||||
|
if metadata is not None:
|
||||||
|
training_comment = metadata.get("ss_training_comment", "")
|
||||||
|
info["Description"] = (
|
||||||
|
metadata.get("modelspec.description", "") +
|
||||||
|
"\n\n" +
|
||||||
|
metadata.get("modelspec.usage_hint", "") +
|
||||||
|
"\n\n" +
|
||||||
|
training_comment if training_comment != "None" else ""
|
||||||
|
).strip()
|
||||||
|
|
||||||
txt_file = file_name + ".txt"
|
txt_file = file_name + ".txt"
|
||||||
notes = ""
|
notes = ""
|
||||||
|
|||||||
@@ -1687,6 +1687,7 @@ class ModelInfoView {
|
|||||||
setPreviewButton,
|
setPreviewButton,
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
|
$el("h2", ["Details:"]),
|
||||||
$el("div",
|
$el("div",
|
||||||
(() => {
|
(() => {
|
||||||
const elements = [];
|
const elements = [];
|
||||||
@@ -1746,6 +1747,12 @@ class ModelInfoView {
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
else if (key === "Description") {
|
||||||
|
if (value !== "") {
|
||||||
|
elements.push($el("h2", [key + ":"]));
|
||||||
|
elements.push($el("p", [value]));
|
||||||
|
}
|
||||||
|
}
|
||||||
else if (key === "Preview") {
|
else if (key === "Preview") {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
@@ -2491,6 +2498,8 @@ class SettingsTab {
|
|||||||
/** @type {HTMLTextAreaElement} */ "model-search-always-append": null,
|
/** @type {HTMLTextAreaElement} */ "model-search-always-append": null,
|
||||||
/** @type {HTMLInputElement} */ "model-persistent-search": null,
|
/** @type {HTMLInputElement} */ "model-persistent-search": null,
|
||||||
/** @type {HTMLInputElement} */ "model-show-label-extensions": null,
|
/** @type {HTMLInputElement} */ "model-show-label-extensions": null,
|
||||||
|
/** @type {HTMLInputElement} */ "model-preview-fallback-search-safetensors-thumbnail": null,
|
||||||
|
|
||||||
/** @type {HTMLInputElement} */ "model-show-add-button": null,
|
/** @type {HTMLInputElement} */ "model-show-add-button": null,
|
||||||
/** @type {HTMLInputElement} */ "model-show-copy-button": null,
|
/** @type {HTMLInputElement} */ "model-show-copy-button": null,
|
||||||
/** @type {HTMLInputElement} */ "model-add-embedding-extension": null,
|
/** @type {HTMLInputElement} */ "model-add-embedding-extension": null,
|
||||||
@@ -2642,6 +2651,10 @@ class SettingsTab {
|
|||||||
$: (el) => (settings["model-show-label-extensions"] = el),
|
$: (el) => (settings["model-show-label-extensions"] = el),
|
||||||
textContent: "Show model file extension in labels",
|
textContent: "Show model file extension in labels",
|
||||||
}),
|
}),
|
||||||
|
$checkbox({
|
||||||
|
$: (el) => (settings["model-preview-fallback-search-safetensors-thumbnail"] = el),
|
||||||
|
textContent: "Fallback on embedded thumbnail in safetensors (refresh slow)",
|
||||||
|
}),
|
||||||
$checkbox({
|
$checkbox({
|
||||||
$: (el) => (settings["model-show-add-button"] = el),
|
$: (el) => (settings["model-show-add-button"] = el),
|
||||||
textContent: "Show add button",
|
textContent: "Show add button",
|
||||||
|
|||||||
Reference in New Issue
Block a user