202 lines
5.6 KiB
Python
202 lines
5.6 KiB
Python
import enum
|
|
import os
|
|
from .cache import cached_data_for_file
|
|
import re
|
|
import folder_paths as fp
|
|
|
|
available_networks = {}
|
|
available_network_aliases = {}
|
|
loaded_networks = []
|
|
loaded_bundle_embeddings = {}
|
|
networks_in_memory = {}
|
|
available_network_hash_lookup = {}
|
|
forbidden_network_aliases = {}
|
|
|
|
|
|
def read_metadata_from_safetensors(filename):
|
|
import json
|
|
|
|
with open(filename, mode="rb") as file:
|
|
metadata_len = file.read(8)
|
|
metadata_len = int.from_bytes(metadata_len, "little")
|
|
json_start = file.read(2)
|
|
|
|
assert metadata_len > 2 and json_start in (
|
|
b'{"',
|
|
b"{'",
|
|
), f"{filename} is not a safetensors file"
|
|
json_data = json_start + file.read(metadata_len - 2)
|
|
json_obj = json.loads(json_data)
|
|
|
|
res = {}
|
|
for k, v in json_obj.get("__metadata__", {}).items():
|
|
res[k] = v
|
|
if isinstance(v, str) and v[0:1] == "{":
|
|
try:
|
|
res[k] = json.loads(v)
|
|
except Exception:
|
|
pass
|
|
|
|
return res
|
|
|
|
|
|
class SdVersion(enum.Enum):
|
|
Unknown = 1
|
|
SD1 = 2
|
|
SD2 = 3
|
|
SDXL = 4
|
|
|
|
|
|
metadata_tags_order = {
|
|
"ss_sd_model_name": 1,
|
|
"ss_resolution": 2,
|
|
"ss_clip_skip": 3,
|
|
"ss_num_train_images": 10,
|
|
"ss_tag_frequency": 20,
|
|
}
|
|
|
|
|
|
class NetworkOnDisk:
|
|
def __init__(self, name, filename):
|
|
self.name = name
|
|
self.filename = filename
|
|
self.metadata = {}
|
|
self.is_safetensors = os.path.splitext(filename)[1].lower() == ".safetensors"
|
|
|
|
def read_metadata():
|
|
metadata = read_metadata_from_safetensors(filename)
|
|
|
|
return metadata
|
|
|
|
if self.is_safetensors:
|
|
try:
|
|
self.metadata = cached_data_for_file(
|
|
"safetensors-metadata", "lora/" + self.name, filename, read_metadata
|
|
)
|
|
except Exception as e:
|
|
print(e, f"reading lora {filename}")
|
|
|
|
if self.metadata:
|
|
m = {}
|
|
for k, v in sorted(
|
|
self.metadata.items(), key=lambda x: metadata_tags_order.get(x[0], 999)
|
|
):
|
|
m[k] = v
|
|
|
|
self.metadata = m
|
|
|
|
self.alias = self.metadata.get("ss_output_name", self.name)
|
|
|
|
self.hash = None
|
|
self.shorthash = None
|
|
# self.set_hash(
|
|
# self.metadata.get('sshs_model_hash') or
|
|
# hashes.sha256_from_cache(self.filename, "lora/" + self.name, use_addnet_hash=self.is_safetensors) or
|
|
# ''
|
|
# )
|
|
|
|
self.sd_version = self.detect_version()
|
|
|
|
def detect_version(self):
|
|
if str(self.metadata.get("ss_base_model_version", "")).startswith("sdxl_"):
|
|
return SdVersion.SDXL
|
|
elif str(self.metadata.get("ss_v2", "")) == "True":
|
|
return SdVersion.SD2
|
|
elif len(self.metadata):
|
|
return SdVersion.SD1
|
|
|
|
return SdVersion.Unknown
|
|
|
|
# def set_hash(self, v):
|
|
# self.hash = v
|
|
# self.shorthash = self.hash[0:12]
|
|
|
|
# if self.shorthash:
|
|
# import networks
|
|
# networks.available_network_hash_lookup[self.shorthash] = self
|
|
|
|
# def read_hash(self):
|
|
# if not self.hash:
|
|
# self.set_hash(hashes.sha256(self.filename, "lora/" + self.name, use_addnet_hash=self.is_safetensors) or '')
|
|
|
|
def get_alias(self):
|
|
if self.alias.lower() in forbidden_network_aliases:
|
|
return self.name
|
|
else:
|
|
return self.alias
|
|
|
|
|
|
def natural_sort_key(s, regex=re.compile("([0-9]+)")):
|
|
return [int(text) if text.isdigit() else text.lower() for text in regex.split(s)]
|
|
|
|
|
|
def walk_files(path, allowed_extensions=None):
|
|
if not os.path.exists(path):
|
|
return
|
|
|
|
if allowed_extensions is not None:
|
|
allowed_extensions = set(allowed_extensions)
|
|
|
|
items = list(os.walk(path, followlinks=True))
|
|
items = sorted(items, key=lambda x: natural_sort_key(x[0]))
|
|
|
|
for root, _, files in items:
|
|
for filename in sorted(files, key=natural_sort_key):
|
|
if allowed_extensions is not None:
|
|
_, ext = os.path.splitext(filename)
|
|
if ext.lower() not in allowed_extensions:
|
|
continue
|
|
|
|
if "/." in root or "\\." in root:
|
|
continue
|
|
|
|
yield os.path.join(root, filename)
|
|
|
|
|
|
def list_available_networks():
|
|
available_networks.clear()
|
|
available_network_aliases.clear()
|
|
forbidden_network_aliases.clear()
|
|
available_network_hash_lookup.clear()
|
|
forbidden_network_aliases.update({"none": 1, "Addams": 1})
|
|
|
|
lora_paths = fp.get_folder_paths("loras")
|
|
|
|
candidates = []
|
|
for lora_dir in lora_paths:
|
|
candidates += list(
|
|
walk_files(lora_dir, allowed_extensions=[".pt", ".ckpt", ".safetensors"])
|
|
)
|
|
for filename in candidates:
|
|
if os.path.isdir(filename):
|
|
continue
|
|
|
|
name = os.path.splitext(os.path.basename(filename))[0]
|
|
try:
|
|
entry = NetworkOnDisk(name, filename)
|
|
except OSError: # should catch FileNotFoundError and PermissionError etc.
|
|
print(f"Failed to load network {name} from {filename}", exc_info=True)
|
|
continue
|
|
|
|
available_networks[name] = entry
|
|
|
|
if entry.alias in available_network_aliases:
|
|
forbidden_network_aliases[entry.alias.lower()] = 1
|
|
|
|
available_network_aliases[name] = entry
|
|
available_network_aliases[entry.alias] = entry
|
|
|
|
|
|
def create_lora_json(obj, include_metadata=False):
|
|
rt = {
|
|
"name": obj.name,
|
|
"alias": obj.alias,
|
|
"path": obj.filename,
|
|
}
|
|
if include_metadata:
|
|
rt["metadata"] = obj.metadata
|
|
return rt
|
|
|
|
|
|
list_available_networks()
|