4 Commits
Author SHA1 Message Date
Nourepide 856024b085 Split Loader.py 4 2023-12-16 23:18:55 +03:00
Nourepide 2dcd401955 Split Loader.py 3 2023-12-16 04:59:53 +03:00
Nourepide d94cebcc1b Split Loader.py 2 2023-12-15 21:22:11 +03:00
Nourepide 759ac287fb Split Loader.py 2023-12-07 20:45:23 +03:00
19 changed files with 4313 additions and 370 deletions
+1
View File
@@ -320,5 +320,6 @@ pip-selfcheck.json
### Allor ###
config.json
resources/timestamp.json
resources/logs/*
# End of https://www.toptal.com/developers/gitignore/api/python,venv,visualstudiocode,pycharm
-351
View File
@@ -1,351 +0,0 @@
import json
import os
import platform
import time
from pathlib import Path
import folder_paths
import nodes
class Loader:
def __init__(self):
pass
__ROOT_PATH = os.path.dirname(os.path.abspath(__file__))
__TEMPLATE_PATH = os.path.join(__ROOT_PATH, "resources/template.json")
__TIMESTAMP_PATH = os.path.join(__ROOT_PATH, "resources/timestamp.json")
__CONFIG_PATH = os.path.join(__ROOT_PATH, "config.json")
__GIT_PATH = Path(os.path.join(__ROOT_PATH, ".git"))
__DAY_SECONDS = 24 * 60 * 60
__WEEK_SECONDS = 7 * __DAY_SECONDS
__MONTH_SECONDS = 30 * __DAY_SECONDS
def __log(self, text):
print("\033[92m[Allor]\033[0m: " + text)
def __error(self, text):
print("\033[91m[Allor]\033[0m: " + text)
def __notification(self, text):
print("\033[94m[Allor]\033[0m: " + text)
def __new_line(self):
print()
def __create_config(self):
with open(self.__CONFIG_PATH, "w", encoding="utf-8") as f:
json.dump(self.__template(), f, ensure_ascii=False, indent=4)
def __create_timestamp(self):
with open(self.__TIMESTAMP_PATH, "w", encoding="utf-8") as f:
json.dump({"timestamp": 0}, f, ensure_ascii=False, indent=4)
def __get_template(self):
with open(self.__TEMPLATE_PATH, "r") as f:
template = json.load(f)
if "__comment" in template:
del template["__comment"]
return template
def __get_config(self):
with open(self.__CONFIG_PATH, "r") as f:
return json.load(f)
def __get_timestamp(self):
with open(self.__TIMESTAMP_PATH, "r") as f:
return json.load(f)
def __update_config(self, template, source):
def update_source(__template, __source):
for k, v in __template.items():
if k not in __source:
if isinstance(v, dict):
__source[k] = {}
else:
__source[k] = v
if isinstance(v, dict):
__source[k] = update_source(v, __source[k])
return __source
def delete_keys(__template, __source):
keys_to_delete = [k for k in __source if k not in __template]
for k in keys_to_delete:
del __source[k]
return __source
def sync_order(__template, __source):
new_source = {}
for key in __template:
if key in __source:
if isinstance(__template[key], dict):
new_source[key] = sync_order(__template[key], __source[key])
else:
new_source[key] = __source[key]
return new_source
source = update_source(template, source)
source = delete_keys(template, source)
source = sync_order(template, source)
with open(self.__CONFIG_PATH, "w", encoding="utf-8") as f:
json.dump(source, f, ensure_ascii=False, indent=4)
def __update_timestamp(self):
with open(self.__TIMESTAMP_PATH, "w", encoding="utf-8") as f:
json.dump({"timestamp": time.time()}, f, ensure_ascii=False, indent=4)
__template = __get_template
__config = __get_config
__timestamp = __get_timestamp
def __get_fonts_folder_path(self):
system = platform.system()
user_home = os.path.expanduser('~')
config_font_path = os.path.join(folder_paths.base_path, *self.__config()["fonts"]["folder_path"].replace("\\", "/").split("/"))
if not os.path.exists(config_font_path):
os.makedirs(config_font_path, exist_ok=True)
paths = [config_font_path]
if self.__config()["fonts"]["system_fonts"]:
if system == "Windows":
paths.append(os.path.join(os.environ["WINDIR"], "Fonts"))
elif system == "Darwin":
paths.append(os.path.join("/Library", "Fonts"))
elif system == "Linux":
paths.append(os.path.join("/usr", "share", "fonts"))
paths.append(os.path.join("/usr", "local", "share", "fonts"))
if self.__config()["fonts"]["user_fonts"]:
if system == "Darwin":
paths.append(os.path.join(user_home, "Library", "Fonts"))
elif system == "Linux":
paths.append(os.path.join(user_home, ".fonts"))
return [path for path in paths if os.path.exists(path)]
def __get_keys(self, json_obj, prefix=''):
keys = []
for k, v in json_obj.items():
if isinstance(v, dict):
keys.extend(self.__get_keys(v, prefix + k + '.'))
else:
keys.append(prefix + k)
return set(keys)
def __check_json_keys(self, json1, json2):
keys1 = self.__get_keys(json1)
keys2 = self.__get_keys(json2)
return keys1 == keys2
def setup_config(self):
if not os.path.exists(self.__CONFIG_PATH):
self.__log("Creating config.json")
self.__create_config()
else:
if not self.__check_json_keys(self.__template(), self.__config()):
self.__log("Updating config.json")
self.__update_config(self.__template(), self.__config())
def setup_timestamp(self):
if not os.path.exists(self.__TIMESTAMP_PATH):
self.__log("Creating timestamp.json")
self.__create_timestamp()
def check_updates(self):
branch_name = self.__config()["updates"]["branch_name"]
update_frequency = self.__config()["updates"]["update_frequency"].lower()
valid_frequencies = ["always", "day", "week", "month", "never"]
time_difference = time.time() - self.__timestamp()["timestamp"]
if update_frequency == valid_frequencies[0]:
it_is_time_for_update = True
elif update_frequency == valid_frequencies[1]:
it_is_time_for_update = time_difference >= self.__DAY_SECONDS
elif update_frequency == valid_frequencies[2]:
it_is_time_for_update = time_difference >= self.__WEEK_SECONDS
elif update_frequency == valid_frequencies[3]:
it_is_time_for_update = time_difference >= self.__MONTH_SECONDS
elif update_frequency == valid_frequencies[4]:
it_is_time_for_update = False
else:
self.__error(f"Unknown update frequency - {update_frequency}, available: {valid_frequencies}")
return
if it_is_time_for_update:
if not (self.__GIT_PATH.exists() or self.__GIT_PATH.is_dir()):
self.__error("Root directory of Allor is not a git repository. Update canceled.")
return
try:
import git
from git import Repo
from git import GitCommandError
# noinspection PyTypeChecker, PyUnboundLocalVariable
repo = Repo(self.__ROOT_PATH, odbt=git.db.GitDB)
current_commit = repo.head.commit.hexsha
repo.remotes.origin.fetch()
latest_commit = getattr(repo.remotes.origin.refs, branch_name).commit.hexsha
if current_commit == latest_commit:
if self.__config()["updates"]["notify_if_no_new_updates"]:
self.__notification("No new updates.")
else:
if self.__config()["updates"]["notify_if_has_new_updates"]:
self.__notification("New updates are available.")
if self.__config()["updates"]["auto_update"]:
update_mode = self.__config()["updates"]["update_mode"].lower()
valid_modes = ["soft", "hard"]
if repo.active_branch.name != branch_name:
try:
repo.git.checkout(branch_name)
except GitCommandError:
self.__error(f"An error occurred while switching to the branch {branch_name}.")
return
if update_mode == "soft":
try:
repo.git.pull()
except GitCommandError:
self.__error("An error occurred during the update. "
"It is recommended to use \"hard\" update mode. "
"But be careful, it erases all personal changes from Allor repository.")
elif update_mode == "hard":
repo.git.reset('--hard', 'origin/' + branch_name)
else:
self.__error(f"Unknown update mode - {update_mode}, available: {valid_modes}")
return
self.__notification("Update complete.")
self.__update_timestamp()
except ImportError:
self.__error("GitPython is not installed.")
def setup_rembg(self):
os.environ["U2NET_HOME"] = folder_paths.models_dir + "/onnx"
def setup_paths(self):
fonts_folder_path = self.__get_fonts_folder_path()
folder_paths.folder_names_and_paths["onnx"] = ([os.path.join(folder_paths.models_dir, "onnx")], {".onnx"})
folder_paths.folder_names_and_paths["fonts"] = (fonts_folder_path, {".otf", ".ttf"})
def setup_override(self):
override_nodes_len = 0
def override(function):
start_len = nodes.NODE_CLASS_MAPPINGS.__len__()
nodes.NODE_CLASS_MAPPINGS = dict(
filter(function, nodes.NODE_CLASS_MAPPINGS.items())
)
return start_len - nodes.NODE_CLASS_MAPPINGS.__len__()
if self.__config()["override"]["postprocessing"]:
override_nodes_len += override(lambda item: not item[1].CATEGORY.startswith("image/postprocessing"))
if self.__config()["override"]["transform"]:
override_nodes_len += override(lambda item: not item[0] == "ImageScale" and not item[0] == "ImageScaleBy" and not item[0] == "ImageInvert")
if self.__config()["override"]["debug"]:
nodes.VAEDecodeTiled.CATEGORY = "latent"
nodes.VAEEncodeTiled.CATEGORY = "latent"
override_nodes_len += override(lambda item: not item[1].CATEGORY.startswith("_for_testing"))
self.__log(str(override_nodes_len) + " nodes were overridden.")
def get_modules(self):
modules = dict()
if self.__config()["modules"]["AlphaChanel"]:
from .modules import AlphaChanel
modules.update(AlphaChanel.NODE_CLASS_MAPPINGS)
if self.__config()["modules"]["Clamp"]:
from .modules import Clamp
modules.update(Clamp.NODE_CLASS_MAPPINGS)
if self.__config()["modules"]["ImageBatch"]:
from .modules import ImageBatch
modules.update(ImageBatch.NODE_CLASS_MAPPINGS)
if self.__config()["modules"]["ImageComposite"]:
from .modules import ImageComposite
modules.update(ImageComposite.NODE_CLASS_MAPPINGS)
if self.__config()["modules"]["ImageContainer"]:
from .modules import ImageContainer
modules.update(ImageContainer.NODE_CLASS_MAPPINGS)
if self.__config()["modules"]["ImageDraw"]:
from .modules import ImageDraw
modules.update(ImageDraw.NODE_CLASS_MAPPINGS)
if self.__config()["modules"]["ImageEffects"]:
from .modules import ImageEffects
modules.update(ImageEffects.NODE_CLASS_MAPPINGS)
if self.__config()["modules"]["ImageFilter"]:
from .modules import ImageFilter
modules.update(ImageFilter.NODE_CLASS_MAPPINGS)
if self.__config()["modules"]["ImageNoise"]:
from .modules import ImageNoise
modules.update(ImageNoise.NODE_CLASS_MAPPINGS)
if self.__config()["modules"]["ImageSegmentation"]:
from .modules import ImageSegmentation
modules.update(ImageSegmentation.NODE_CLASS_MAPPINGS)
if self.__config()["modules"]["ImageText"]:
from .modules import ImageText
modules.update(ImageText.NODE_CLASS_MAPPINGS)
if self.__config()["modules"]["ImageTransform"]:
from .modules import ImageTransform
modules.update(ImageTransform.NODE_CLASS_MAPPINGS)
modules_len = dict(
filter(
lambda item: item[1],
self.__config()["modules"].items()
)
).__len__()
nodes_len = modules.__len__()
self.__log(str(modules_len) + " modules were enabled.")
self.__log(str(nodes_len) + " nodes were loaded.")
return modules
+2 -2
View File
@@ -1,4 +1,4 @@
![logo](https://raw.githubusercontent.com/Nourepide/ComfyUI-Allor-Res/c4147e55cf8ec26ca79fe3df6e1af71aca58c19e/v.2/logo_v.2_git.png)
![logo](resources/logo.png)
<details>
<summary>This image from start to end was done in ComfyUI. How?</summary>
@@ -9,7 +9,7 @@
4. Load [RealESRNet_x4plus.pth](https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.1/RealESRNet_x4plus.pth) and put in to `models/upscale` folder.
5. Load [AOM3A1B_orangemixs.safetensors](https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/Models/AbyssOrangeMix3/AOM3A1B_orangemixs.safetensors) and put in to `models/chekpoints` folder.
6. Load [orangemix.vae.pt](https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/VAEs/orangemix.vae.pt) and put in to `models/vae` folder.
7. Drag-and-drop this [image](resources/logo.png) to ComfyUI or load [JSON](https://raw.githubusercontent.com/Nourepide/ComfyUI-Allor-Res/c4147e55cf8ec26ca79fe3df6e1af71aca58c19e/v.2/logo_v.2.json).
7. Drag-and-drop this [image](resources/logo.png) to ComfyUI or load [JSON](resources/logo.json).
8. Press the `Queue Promt` button.
</details>
+14 -9
View File
@@ -1,12 +1,17 @@
from .Loader import Loader
from .boot.Logger import Logger
from .boot.Backends import Backends
from .boot.Config import Config
from .boot.Update import Update
from .boot.Paths import Paths
from .boot.Override import Override
from .boot.Modules import Modules
loader = Loader()
logger = Logger()
config = Config(logger).initiate()
backends = Backends(logger).initiate()
loader.setup_config()
loader.setup_timestamp()
loader.check_updates()
loader.setup_rembg()
loader.setup_paths()
loader.setup_override()
Update(logger, config, backends).initiate()
Paths(logger, config, backends).initiate()
Override(logger, config, backends).initiate()
NODE_CLASS_MAPPINGS = loader.get_modules()
NODE_CLASS_MAPPINGS = Modules(logger, config, backends).initiate()
+28
View File
@@ -0,0 +1,28 @@
from importlib import import_module
class Backends:
COMFY_UI = "main"
TORCH = "torch"
NUMPY = "numpy"
CV2 = "cv2"
PIL = "PIL"
REMBG = "rembg"
GIT = "git"
def __init__(self, logger):
self.__logger = logger
self.__backends = [Backends.COMFY_UI, Backends.TORCH, Backends.NUMPY, Backends.CV2, Backends.PIL, Backends.REMBG, Backends.GIT]
def initiate(self):
dependencies = {}
for backend in self.__backends:
try:
import_module(backend)
dependencies[backend] = True
except ImportError:
self.__logger.error(f"Loading {backend} library ended with an error.")
dependencies[backend] = False
return dependencies
+97
View File
@@ -0,0 +1,97 @@
import json
from .Paths import Paths
class Config:
def __init__(self, logger):
self.__logger = logger
self.__template = self.__get_template()
if not Paths.CONFIG_PATH.exists():
self.__logger.info("Creating configuration file.")
self.__create_config()
self.__config = self.__get_config()
def initiate(self):
if not self.__verify_keys(self.__template, self.__config):
self.__logger.info("Updating configuration file.")
self.__update_config(self.__template, self.__config)
return self.__get_config()
def __create_config(self):
with open(Paths.CONFIG_PATH, "w", encoding="utf-8") as f:
json.dump(self.__template, f, ensure_ascii=False, indent=4)
def __get_template(self):
with open(Paths.TEMPLATE_PATH, "r") as f:
template = json.load(f)
if "__comment" in template:
del template["__comment"]
return template
def __get_config(self):
with open(Paths.CONFIG_PATH, "r") as f:
return json.load(f)
def __verify_keys(self, json1, json2):
def get_keys(json_obj, prefix=''):
keys = []
for k, v in json_obj.items():
if isinstance(v, dict):
keys.extend(get_keys(v, prefix + k + '.'))
else:
keys.append(prefix + k)
return set(keys)
keys1 = get_keys(json1)
keys2 = get_keys(json2)
return keys1 == keys2
def __update_config(self, template, source):
def update_source(__template, __source):
for k, v in __template.items():
if k not in __source:
if isinstance(v, dict):
__source[k] = {}
else:
__source[k] = v
if isinstance(v, dict):
__source[k] = update_source(v, __source[k])
return __source
def delete_keys(__template, __source):
keys_to_delete = [k for k in __source if k not in __template]
for k in keys_to_delete:
del __source[k]
return __source
def sync_order(__template, __source):
new_source = {}
for key in __template:
if key in __source:
if isinstance(__template[key], dict):
new_source[key] = sync_order(__template[key], __source[key])
else:
new_source[key] = __source[key]
return new_source
source = update_source(template, source)
source = delete_keys(template, source)
source = sync_order(template, source)
with open(Paths.CONFIG_PATH, "w", encoding="utf-8") as f:
json.dump(source, f, ensure_ascii=False, indent=4)
+249
View File
@@ -0,0 +1,249 @@
import glob
import json
import os
import re
import sys
from datetime import datetime
from .Paths import Paths
class SingletonLogger(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(SingletonLogger, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class Logger(metaclass=SingletonLogger):
file = Paths.LOG_PATH
def __init__(self):
self.formatting = _Formatting()
self.levels = {
"FATAL": "r",
"ERROR": "r",
"WARN": "y",
"INFO": "b",
"EMIT": "g",
"DEBUG": "m",
"TRACE": "c",
"LINE": "n"
}
self.__length = max(len(key) for key in self.levels)
info = self.__get_info()
if info["branch"] == "main":
info["branch"] = "v.2"
self.debug(f"{info['branch']} : {info['hex']}")
def __log(self, text, level, display):
text = self.formatting.format(text)
if display:
if level == "LINE":
print()
else:
prefix = self.formatting.format(f"!![f{self.levels.get(level)}b;" + re.escape("[Allor]") + ":" + "] ")
postfix = self.formatting.format(text, 9)
print(f"{prefix}{postfix}")
if Logger.file:
directory = Logger.file.parent
if not directory.exists():
directory.mkdir(parents=True)
if not Logger.file.exists():
log_files = list(glob.glob(str(directory / "log_*.log")))
if len(log_files) > 5:
log_files.sort(key=os.path.getmtime)
os.remove(log_files[0])
Logger.file.touch()
with open(Logger.file, "a") as f:
if level == "LINE":
f.write("\n")
else:
prefix = f"[{datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')}][{level:<{self.__length}}]: "
postfix = self.formatting.format(text, len(prefix))
postfix = self.formatting.reset(postfix)
f.write(f"{prefix}{postfix}\n")
def fatal(self, text, display=True):
self.__log(text, "FATAL", display)
def error(self, text, display=True):
self.__log(text, "ERROR", display)
def warn(self, text, display=True):
self.__log(text, "WARN", display)
def info(self, text, display=True):
self.__log(text, "INFO", display)
def emit(self, text, display=True):
self.__log(text, "EMIT", display)
def debug(self, text, display=True):
self.__log(text, "DEBUG", display)
def trace(self, text, display=True):
self.__log(text, "TRACE", display)
def line(self, count=1, display=True):
self.__log("\n" * count, "LINE", display)
def warning_unstable_branch(self, branch_name="main"):
warn_messages = (
f"Attention! You are currently using an unstable !![fyb;{branch_name}] update branch. \n"
f"This branch is intended for the development of !![fmb;Allor v.2]. \n\n"
f"Please be aware that changes made in !![fmb;Allor v.2] may disrupt your current workflow. \n"
"Nodes may be renamed, parameters within them may be altered or even removed. \n\n"
"If backward compatibility of your workflow is important to you, consider this. \n"
f"You can change the !![fcb;branch_name_param] parameter to !![fbb;v1] in your !![fgb;config_json]. \n\n"
"If you are prepared for potential changes, you can modify your configuration file. \n"
f"To accept, switch the !![fcb;confirm_unstable_param] parameter in your !![fgb;config_json] to !![fbb;true]. \n"
"This will result in this warning no longer appearing."
)
emit_messages = (
"We appreciate your support and understanding during this transition period. \n"
f"Thank you and welcome to !![fmb;Allor v.2]."
)
self.warn(warn_messages)
self.line()
self.emit(emit_messages)
self.line()
def __get_info(self):
with open(Paths.INFO_PATH, "r") as f:
return json.load(f)
class _Formatting:
COLOR = {
"d": 0, # DARK
"r": 1, # RED
"g": 2, # GREEN
"y": 3, # YELLOW
"b": 4, # BLUE
"m": 5, # MAGENTA
"c": 6, # CYAN
"w": 7, # WHITE
"n": 8 # NORMAL
}
INTENSITY = {
"n": 30, # NORMAL
"b": 90 # BRIGHT
}
ATTRIBUTE = {
"n": 0, # NORMAL
"b": 1, # BOLD
"f": 2, # FAINT
"i": 3, # ITALIC
"u": 4, # UNDERLINE
"l": 5, # BLINKING
"a": 6, # FAST_BLINKING
"r": 7, # REVERSE
"h": 8, # HIDE
"s": 9 # STRIKETHROUGH
}
def __init__(self):
self.__ansi = self.__formatting_support()
# noinspection PyPep8Naming
def __formatting_support(self):
platform = sys.platform
if platform == "win32":
import ctypes
STD_OUTPUT_HANDLE = -11
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
kernel32 = ctypes.WinDLL("kernel32")
hStdOut = kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
mode = ctypes.c_ulong()
if not kernel32.GetConsoleMode(hStdOut, ctypes.byref(mode)):
return False
if not mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING:
return False
if platform in ("linux", "darwin"):
term = os.getenv("TERM")
if term not in ("xterm", "xterm-256color", "vt100", "ansi", "linux"):
return False
return True
def format(self, text, intend=0):
if self.__ansi:
text = self.__interpolation(text)
else:
text = self.__extraction(text)
if intend > 0:
text = "\n".join(line if i == 0 or not line else " " * intend + line for i, line in enumerate(text.split("\n")))
return text
def reset(self, text):
return re.compile(r"\033\[[0-?]*[ -/]*[@-~]").sub("", text)
def __interpolation(self, input_string):
pattern = re.compile(r"!!\[(.*?)(?<!\\)]")
def replace_func(match):
params = match.group(1).split(';')
fg, bg, attr, text = "fnn", "bnn", "an", None
for param in params:
if (len(param) == 3
and param[0] in ["f", "b"]
and param[1] in self.COLOR
and param[2] in self.INTENSITY):
if param[0] == "f":
fg = param
else:
bg = param
elif len(param) == 2 and param[0] == "a" and param[1] in self.ATTRIBUTE:
attr = param
else:
text = param
text = text.replace("\\", "")
return (f"\033["
f"{self.COLOR[fg[1]] + self.INTENSITY[fg[2]]};"
f"{self.COLOR[bg[1]] + self.INTENSITY[bg[2]] + 10};"
f"{self.ATTRIBUTE[attr[1]]}m{text}\033[0m")
return pattern.sub(replace_func, input_string)
def __extraction(self, input_string):
pattern = re.compile(r"!!\[(.*?)(?<!\\)]")
def replace_func(match):
return match.group(1).split(":")[-1]
return pattern.sub(replace_func, input_string)
+54
View File
@@ -0,0 +1,54 @@
import functools
from importlib import import_module
from .Backends import Backends
class Modules:
def __init__(self, logger, config, backends):
self.__logger = logger
self.__config = config
self.__backends = backends
def initiate(self):
modules_loaded = dict()
modules_not_loaded = dict()
modules_info = {
"AlphaChanel": [Backends.TORCH],
"Clamp": [],
"ImageBatch": [Backends.TORCH],
"ImageComposite": [Backends.TORCH, Backends.PIL],
"ImageContainer": [Backends.TORCH],
"ImageDraw": [Backends.PIL],
"ImageEffects": [Backends.TORCH, Backends.NUMPY, Backends.CV2],
"ImageFilter": [Backends.TORCH, Backends.PIL, Backends.CV2],
"ImageNoise": [Backends.TORCH, Backends.NUMPY],
"ImageSegmentation": [Backends.TORCH, Backends.PIL, Backends.REMBG],
"ImageText": [Backends.PIL],
"ImageTransform": [Backends.TORCH, Backends.PIL]
}
for module_name, backends in modules_info.items():
if self.__config["modules"][module_name]:
if self.__required(module_name, *backends):
if module := import_module(f"..modules.{module_name}", package=__package__):
modules_loaded.update(module.NODE_CLASS_MAPPINGS)
else:
modules_not_loaded[module_name] = backends
modules_len = len({k: v for k, v in self.__config["modules"].items() if v})
nodes_len = len(modules_loaded)
self.__logger.info(f"{modules_len} modules were enabled.", self.__config["logger"]["modules_enabled"])
self.__logger.info(f"{nodes_len} nodes were loaded.", self.__config["logger"]["nodes_loaded"])
return modules_loaded
@functools.lru_cache
def __required(self, module, *backends):
for backend in backends:
if not self.__backends[backend]:
self.__logger.error(f"Module {module} did not find all necessary backends. Loading skipped.")
return False
return True
+34
View File
@@ -0,0 +1,34 @@
import nodes
class Override:
def __init__(self, logger, config, backends):
self.__logger = logger
self.__config = config
self.__backends = backends
def initiate(self):
override_nodes_len = 0
def override(function):
start_len = nodes.NODE_CLASS_MAPPINGS.__len__()
nodes.NODE_CLASS_MAPPINGS = dict(
filter(function, nodes.NODE_CLASS_MAPPINGS.items())
)
return start_len - nodes.NODE_CLASS_MAPPINGS.__len__()
if self.__config["override"]["postprocessing"]:
override_nodes_len += override(lambda item: not item[1].CATEGORY.startswith("image/postprocessing"))
if self.__config["override"]["transform"]:
override_nodes_len += override(lambda item: not item[0] == "ImageScale" and not item[0] == "ImageScaleBy" and not item[0] == "ImageInvert")
if self.__config["override"]["debug"]:
nodes.VAEDecodeTiled.CATEGORY = "latent"
nodes.VAEEncodeTiled.CATEGORY = "latent"
override_nodes_len += override(lambda item: not item[1].CATEGORY.startswith("_for_testing"))
self.__logger.info(str(override_nodes_len) + " nodes were overridden.", self.__config["logger"]["nodes_overridden"])
+52
View File
@@ -0,0 +1,52 @@
import os
import platform
from datetime import datetime
from importlib import import_module
from pathlib import Path
from .Backends import Backends
class Paths:
ROOT_PATH = Path(__file__).resolve().parent.parent
RESOURCE_PATH = ROOT_PATH / "resources"
TEMPLATE_PATH = RESOURCE_PATH / "template.json"
TIMESTAMP_PATH = RESOURCE_PATH / "timestamp.json"
INFO_PATH = RESOURCE_PATH / "info.json"
LOG_PATH = RESOURCE_PATH / f"logs/log_{datetime.now().strftime('%Y_%m_%d_%H_%M_%S')}.log"
CONFIG_PATH = ROOT_PATH / "config.json"
GIT_PATH = ROOT_PATH / ".git"
def __init__(self, logger, config, backends):
self.__logger = logger
self.__config = config
self.__backends = backends
def initiate(self):
if self.__backends[Backends.COMFY_UI]:
folder_paths = import_module("folder_paths")
fonts_folder_path = self.__get_font_paths(folder_paths)
os.environ["U2NET_HOME"] = str(Path(folder_paths.models_dir) / "onnx")
folder_paths.folder_names_and_paths.update({
"onnx": ([Path(folder_paths.models_dir) / "onnx"], {".onnx"}),
"fonts": (fonts_folder_path, {".otf", ".ttf"})
})
def __get_font_paths(self, folder_paths):
system = platform.system()
user_home = Path.home()
config_font_path = Path(folder_paths.base_path) / Path(self.__config["fonts"]["folder_path"].replace("\\", "/"))
config_font_path.mkdir(parents=True, exist_ok=True)
system_fonts_paths = {
"Windows": [Path(os.environ.get("WINDIR", "")) / "Fonts"],
"Darwin": [Path("/Library") / "Fonts", user_home / "Library" / "Fonts"],
"Linux": [Path("/usr") / "share" / "fonts", Path("/usr") / "local" / "share" / "fonts", user_home / ".fonts"]
}
paths = [config_font_path] + system_fonts_paths.get(system, [])
return [str(path) for path in paths if path.exists()]
+143
View File
@@ -0,0 +1,143 @@
import json
import time
from importlib import import_module
from .Backends import Backends
from .Paths import Paths
class Update:
DAY_SECONDS = 24 * 60 * 60
WEEK_SECONDS = 7 * DAY_SECONDS
MONTH_SECONDS = 30 * DAY_SECONDS
def __init__(self, logger, config, backends):
self.__logger = logger
self.__config = config
self.__backends = backends
if not Paths.TIMESTAMP_PATH.exists():
self.__logger.info("Creating timestamp file.")
self.__create_timestamp()
self.__timestamp = self.__get_timestamp()
def initiate(self):
confirm_unstable = self.__config["logger"]["confirm_unstable"]
branch_name = self.__config["updates"]["branch_name"]
search_frequency = self.__config["updates"]["search_frequency"].lower()
time_difference = time.time() - self.__timestamp["timestamp"]
valid_frequencies = {
"always": True,
"day": time_difference >= self.DAY_SECONDS,
"week": time_difference >= self.WEEK_SECONDS,
"month": time_difference >= self.MONTH_SECONDS,
"never": False
}
unstable_branches = ["main"]
if branch_name in unstable_branches and search_frequency != "never":
if not confirm_unstable:
self.__logger.warning_unstable_branch(branch_name)
else:
self.__logger.debug("Unstable branch agreement confirmed.\nPlease be careful when using this version.", False)
if self.__backends[Backends.GIT]:
if Paths.GIT_PATH.exists() and Paths.GIT_PATH.is_dir():
repo = self.__repo()
self.__checkout(repo, branch_name)
try:
update_scheduled = valid_frequencies[search_frequency]
if update_scheduled:
self.__pull(repo, branch_name)
except KeyError:
self.__logger.error(f"Unknown update frequency - {search_frequency}, available: {list(valid_frequencies.keys())}")
else:
self.__logger.error("Update canceled because Allor is not a git repository.")
else:
self.__logger.error("Update canceled because GitPython is not installed.")
def __get_timestamp(self):
with open(Paths.TIMESTAMP_PATH, "r") as f:
return json.load(f)
def __create_timestamp(self):
with open(Paths.TIMESTAMP_PATH, "w", encoding="utf-8") as f:
json.dump({"timestamp": 0}, f, ensure_ascii=False, indent=4)
def __update_timestamp(self):
with open(Paths.TIMESTAMP_PATH, "w", encoding="utf-8") as f:
json.dump({"timestamp": time.time()}, f, ensure_ascii=False, indent=4)
def __repo(self):
git = import_module("git")
repo = git.Repo
return repo(Paths.ROOT_PATH, odbt=git.db.GitDB)
def __checkout(self, repo, branch_name):
from git import GitCommandError
if repo.active_branch.name != branch_name:
if any([branch.name == branch_name for branch in repo.branches]):
try:
update_mode = self.__config["updates"]["update_mode"].lower()
valid_modes = ["soft", "hard"]
if update_mode == "soft":
repo.git.checkout(branch_name)
elif update_mode == "hard":
repo.git.checkout(branch_name, force=True)
else:
self.__logger.error(f"Unknown update mode - {update_mode}, available: {valid_modes}")
except GitCommandError:
self.__logger.error(f"An error occurred while switching to the branch {branch_name}.")
else:
self.__logger.error(f"Branch with name {branch_name} not exist.")
def __pull(self, repo, branch_name):
from git import GitCommandError
try:
repo.remotes.origin.fetch()
local_commits = list(repo.iter_commits(branch_name))[::-1]
remote_commits = list(repo.iter_commits(f"origin/{branch_name}"))[::-1]
incorrect_hex = any(lc != rc for lc, rc in zip(local_commits, remote_commits))
if incorrect_hex:
new_remote_commits = []
else:
last_hex = next((i for i, (lc, rc) in enumerate(zip(local_commits, remote_commits)) if lc != rc), len(local_commits))
new_remote_commits = remote_commits[last_hex:] if len(remote_commits) > len(local_commits) else []
if incorrect_hex or new_remote_commits:
if self.__config["updates"]["install_update"]:
update_mode = self.__config["updates"]["update_mode"].lower()
valid_modes = ["soft", "hard"]
if update_mode == "soft":
if incorrect_hex:
self.__logger.warn("Incorrect hex of commits found in commits repository history.\n"
"Updating using \"soft\" update mode is unlikely to complete successfully.")
repo.git.pull()
elif update_mode == "hard":
repo.git.reset('--hard', 'origin/' + branch_name)
else:
self.__logger.error(f"Unknown update mode - {update_mode}, available: {valid_modes}")
return
self.__update_timestamp()
self.__logger.info("Updates installed successfully.", self.__config["logger"]["install_complete"])
except GitCommandError:
self.__logger.error("An error occurred during the updating.\n"
"It is recommended to use \"hard\" update mode.\n"
"But be careful, it erases all personal changes from Allor repository.")
View File
+8 -2
View File
@@ -269,10 +269,16 @@ class ImageCompositeRelative:
container_width = img_a_width + img_b_width
container_height = img_a_height + img_b_height
elif container_size_type == "sum_width":
if img_a_height != img_b_height:
raise ValueError()
container_width = img_a_width + img_b_width
container_height = max(img_a_height, img_b_height)
container_height = img_a_height
elif container_size_type == "sum_height":
container_width = max(img_a_width, img_b_width)
if img_b_width != img_b_width:
raise ValueError()
container_width = img_a_width
container_height = img_a_height + img_a_height
else:
raise ValueError()
+7
View File
@@ -0,0 +1,7 @@
@echo off
for /f %%i in ('git rev-parse --abbrev-ref HEAD') do set branch=%%i
for /f "delims=" %%i in ('git log -1 --pretty=%%B') do set commit=%%i
for /f %%i in ('git rev-parse --short HEAD') do set hex=%%i
echo {\"branch\": \"%branch%\", \"commit\": \"%commit%\", \"hex\": \"%hex%\"} > info.json
+1
View File
@@ -0,0 +1 @@
{"branch": "main", "commit": "Split Loader.py", "hex": "14bff6a"}
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash
branch=$(git rev-parse --abbrev-ref HEAD)
commit=$(git log -1 --pretty=%B)
hex=$(git rev-parse --short HEAD)
echo "{\"branch\": \"$branch\", \"commit\": \"$commit\", \"hex\": \"$hex\"}" > info.json
+3602
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:dafc8789058e0a6c1341785a72195c746278283a455525c69cbf13153807a914
size 1919499
+11 -6
View File
@@ -20,17 +20,22 @@
"debug": false
},
"updates": {
"update_frequency": "day",
"notify_if_has_new_updates": true,
"notify_if_no_new_updates": true,
"auto_update": true,
"search_frequency": "day",
"install_update": true,
"branch_name": "main",
"update_mode": "soft",
"confirm_unstable_agreement": false
"update_mode": "soft"
},
"fonts": {
"folder_path": "comfy_extras/fonts",
"system_fonts": false,
"user_fonts": false
},
"logger": {
"confirm_unstable": false,
"updates_search": true,
"install_complete": true,
"modules_enabled": true,
"nodes_loaded": true,
"nodes_overridden": true
}
}