diff --git a/TODO.md b/TODO.md index f3a01489a..03761fc3e 100644 --- a/TODO.md +++ b/TODO.md @@ -59,11 +59,19 @@ Tech that can be integrated as part of the core workflow... ### Pending Code Updates - tested with **torch 2.1** and **cuda 12.1** - (production remains on torch2.0.1+cuda11.8) + (production remains on torch2.0.1+cuda11.8) - fully extend support of `--data-dir` - allows multiple installations to share pretty much everything, not just models -- add dark/light theme mode toggle -- redo some `clip-skip` functionality -- better matching for vae vs model -- update to `xyz grid` to allow creation of large number of images without -- fixes...amazing how many issues were introduced by porting new a1111 code without adding almost no new functionality + allows multiple installations to share pretty much everything, not just models +- redo api authentication + now api authentication will use same user/pwd (if specified) for ui and strictly enforce it using httpbasicauth + new authentication is also fully supported in combination with ssl for both sync and async calls + if you want to use api programatically, see examples in `cli/sdapi.py` +- add dark/light theme mode toggle +- redo some `clip-skip` functionality +- better matching for vae vs model +- update to `xyz grid` to allow creation of large number of images without +- update `gradio` (again) +- more prompt parser optimizations +- better error handling when importing image settings which are not compatible with current install + for example, when upscaler or sampler originally used is not available +- fixes...amazing how many issues were introduced by porting new a1111 code without adding almost no new functionality diff --git a/cli/train/latents.py b/cli/latents.py similarity index 98% rename from cli/train/latents.py rename to cli/latents.py index b0509f2cd..53be33527 100755 --- a/cli/train/latents.py +++ b/cli/latents.py @@ -23,7 +23,7 @@ console = Console(log_time=True, log_time_format='%H:%M:%S-%f') pretty_install(console=console) traceback_install(console=console, extra_lines=1, width=console.width, word_wrap=False, indent_guides=False) -sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'modules', 'lora')) +sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'modules', 'lora')) import library.model_util as model_util import library.train_util as train_util diff --git a/cli/train/options.py b/cli/options.py similarity index 100% rename from cli/train/options.py rename to cli/options.py diff --git a/cli/train/process.py b/cli/process.py similarity index 100% rename from cli/train/process.py rename to cli/process.py diff --git a/cli/sdapi.py b/cli/sdapi.py index 2a258955e..62edb3038 100755 --- a/cli/sdapi.py +++ b/cli/sdapi.py @@ -5,19 +5,55 @@ helper methods that creates HTTP session with managed connection pool provides async HTTP get/post methods and several helper methods """ +import os import sys +import ssl import asyncio import logging import aiohttp import requests +import urllib3 from util import Map, log -sd_url = "http://127.0.0.1:7860" # automatic1111 api url root +sd_url = os.environ.get('SDAPI_URL', "http://127.0.0.1:7860") # automatic1111 api url root + use_session = True +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) +ssl.create_default_context = ssl._create_unverified_context # pylint: disable=protected-access timeout = aiohttp.ClientTimeout(total = None, sock_connect = 10, sock_read = None) # default value is 5 minutes, we need longer for training sess = None quiet = False +BaseThreadPolicy = asyncio.WindowsSelectorEventLoopPolicy if sys.platform == "win32" and hasattr(asyncio, "WindowsSelectorEventLoopPolicy") else asyncio.DefaultEventLoopPolicy + + +class AnyThreadEventLoopPolicy(BaseThreadPolicy): + def get_event_loop(self) -> asyncio.AbstractEventLoop: + try: + return super().get_event_loop() + except (RuntimeError, AssertionError): + loop = self.new_event_loop() + self.set_event_loop(loop) + return loop + +asyncio.set_event_loop_policy(AnyThreadEventLoopPolicy()) + + +def authsync(): + sd_username = os.environ.get('SDAPI_USR', None) + sd_password = os.environ.get('SDAPI_PWD', None) + if sd_username is not None and sd_password is not None: + return requests.auth.HTTPBasicAuth(sd_username, sd_password) + return None + + +def auth(): + sd_username = os.environ.get('SDAPI_USR', None) + sd_password = os.environ.get('SDAPI_PWD', None) + if sd_username is not None and sd_password is not None: + return aiohttp.BasicAuth(sd_username, sd_password) + return None + async def result(req): @@ -60,7 +96,7 @@ async def get(endpoint: str, json: dict = None): global sess # pylint: disable=global-statement sess = sess if sess is not None else await session() try: - async with sess.get(url = endpoint, json = json) as req: + async with sess.get(url=endpoint, json=json, verify_ssl=False) as req: res = await result(req) return res except Exception as err: @@ -70,7 +106,7 @@ async def get(endpoint: str, json: dict = None): def getsync(endpoint: str, json: dict = None): try: - req = requests.get(f'{sd_url}{endpoint}', json = json) # pylint: disable=missing-timeout + req = requests.get(f'{sd_url}{endpoint}', json=json, verify=False, auth=authsync()) # pylint: disable=missing-timeout res = resultsync(req) return res except Exception as err: @@ -85,7 +121,7 @@ async def post(endpoint: str, json: dict = None): await sess.close() sess = await session() try: - async with sess.post(url = endpoint, json = json) as req: + async with sess.post(url=endpoint, json=json, verify_ssl=False) as req: res = await result(req) return res except Exception as err: @@ -94,7 +130,7 @@ async def post(endpoint: str, json: dict = None): def postsync(endpoint: str, json: dict = None): - req = requests.post(f'{sd_url}{endpoint}', json = json) # pylint: disable=missing-timeout + req = requests.post(f'{sd_url}{endpoint}', json=json, verify=False, auth=authsync()) # pylint: disable=missing-timeout res = resultsync(req) return res @@ -150,7 +186,7 @@ def shutdown(): async def session(): global sess # pylint: disable=global-statement time = aiohttp.ClientTimeout(total = None, sock_connect = 10, sock_read = None) # default value is 5 minutes, we need longer for training - sess = aiohttp.ClientSession(timeout = time, base_url = sd_url) + sess = aiohttp.ClientSession(timeout = time, base_url = sd_url, auth=auth()) log.debug({ 'sdapi': 'session created', 'endpoint': sd_url }) """ sess = await aiohttp.ClientSession(timeout = timeout).__aenter__() @@ -170,6 +206,7 @@ async def session(): async def close(): if sess is not None: await asyncio.sleep(0) + await sess.close() await sess.__aexit__(None, None, None) log.debug({ 'sdapi': 'session closed', 'endpoint': sd_url }) @@ -180,6 +217,8 @@ if __name__ == "__main__": asyncio.run(interrupt()) if 'progress' in sys.argv: asyncio.run(progress()) + if 'progresssync' in sys.argv: + progresssync() if 'options' in sys.argv: opt = options() log.debug({ 'options' }) @@ -189,4 +228,5 @@ if __name__ == "__main__": print(json.dumps(opt['flags'], indent = 2)) if 'shutdown' in sys.argv: shutdown() - asyncio.run(close()) + asyncio.run(close(), debug=True) + asyncio.run(asyncio.sleep(0.5)) diff --git a/cli/train/train.py b/cli/train.py similarity index 98% rename from cli/train/train.py rename to cli/train.py index bbb4bdd2a..a2507660b 100755 --- a/cli/train/train.py +++ b/cli/train.py @@ -17,7 +17,6 @@ import warnings warnings.filterwarnings(action="ignore", category=DeprecationWarning) warnings.filterwarnings(action="ignore", category=UserWarning) warnings.filterwarnings(action="ignore", category=FutureWarning) -sys.path.append('.') # 3rd party imports import filetype @@ -27,9 +26,9 @@ from tqdm.rich import tqdm # local imports import util import sdapi +import options import process import latents -import options # globals @@ -79,7 +78,7 @@ def mem_stats(): def parse_args(): global args # pylint: disable=global-statement - parser = argparse.ArgumentParser(description = 'Train') + parser = argparse.ArgumentParser(description = 'SD.Next Train') group_main = parser.add_argument_group('Main') group_main.add_argument('--type', type=str, choices=['embedding', 'ti', 'lora', 'lyco', 'dreambooth', 'hypernetwork'], default=None, required=True, help='training type') @@ -240,9 +239,9 @@ def train_lora(): log.info(f'{args.type} options: {options.lora}') # lora imports lora_path = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, 'modules', 'lora')) + lycoris_path = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, 'modules', 'lycoris')) sys.path.append(lora_path) if args.type == 'lyco': - lycoris_path = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, 'modules', 'lycoris')) sys.path.append(lycoris_path) log.debug('importing lora lib') import train_network @@ -368,7 +367,7 @@ def process_inputs(): if __name__ == '__main__': - log.info('train script for stable diffusion') + log.info('SD.Next train script') parse_args() setup_logging() prepare_server() diff --git a/cli/train/sdapi.py b/cli/train/sdapi.py deleted file mode 100644 index f642e1bad..000000000 --- a/cli/train/sdapi.py +++ /dev/null @@ -1,109 +0,0 @@ -import asyncio -import aiohttp -import requests -from util import Map - - -sd_url = "http://127.0.0.1:7860" # automatic1111 api url root -use_session = True -timeout = aiohttp.ClientTimeout(total = None, sock_connect = 10, sock_read = None) # default value is 5 minutes, we need longer for training -sess = None -quiet = False - - -async def result(req): - if req.status != 200: - if not use_session and sess is not None: - await sess.close() - return Map({ 'error': req.status, 'reason': req.reason, 'url': req.url }) - else: - json = await req.json() - if type(json) == list: - res = json - elif json is None: - res = {} - else: - res = Map(json) - return res - - -def resultsync(req: requests.Response): - if req.status_code != 200: - return Map({ 'error': req.status_code, 'reason': req.reason, 'url': req.url }) - else: - json = req.json() - if type(json) == list: - res = json - elif json is None: - res = {} - else: - res = Map(json) - return res - - -async def get(endpoint: str, json: dict = None): - global sess # pylint: disable=global-statement - sess = sess if sess is not None else await session() - async with sess.get(url = endpoint, json = json) as req: - res = await result(req) - return res - - -def getsync(endpoint: str, json: dict = None): - req = requests.get(f'{sd_url}{endpoint}', json = json) # pylint: disable=missing-timeout - res = resultsync(req) - return res - - -async def post(endpoint: str, json: dict = None): - global sess # pylint: disable=global-statement - # sess = sess if sess is not None else await session() - if sess and not sess.closed: - await sess.close() - sess = await session() - async with sess.post(url = endpoint, json = json) as req: - res = await result(req) - return res - - -def postsync(endpoint: str, json: dict = None): - req = requests.post(f'{sd_url}{endpoint}', json = json) # pylint: disable=missing-timeout - res = resultsync(req) - return res - - -def interrupt(): - res = getsync('/sdapi/v1/progress?skip_current_image=true') - if 'state' in res and res.state.job_count > 0: - res = postsync('/sdapi/v1/interrupt') - return res - else: - return { 'interrupt': 'idle' } - - -def progress(): - res = getsync('/sdapi/v1/progress?skip_current_image=true') - return res - - -def options(): - opt = getsync('/sdapi/v1/options') - flags = getsync('/sdapi/v1/cmd-flags') - return { 'options': opt, 'flags': flags } - - -def shutdown(): - postsync('/sdapi/v1/shutdown') - - -async def session(): - global sess # pylint: disable=global-statement - time = aiohttp.ClientTimeout(total = None, sock_connect = 10, sock_read = None) # default value is 5 minutes, we need longer for training - sess = aiohttp.ClientSession(timeout = time, base_url = sd_url) - return sess - - -async def close(): - if sess is not None: - await asyncio.sleep(0) - await sess.__aexit__(None, None, None) diff --git a/cli/train/util.py b/cli/train/util.py deleted file mode 100755 index e67b4f403..000000000 --- a/cli/train/util.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env python -import os - -import transformers -transformers.logging.set_verbosity_error() - - -def get_memory(): - def gb(val: float): - return round(val / 1024 / 1024 / 1024, 2) - mem = {} - try: - import psutil - process = psutil.Process(os.getpid()) - res = process.memory_info() - ram_total = 100 * res.rss / process.memory_percent() - ram = { 'free': gb(ram_total - res.rss), 'used': gb(res.rss), 'total': gb(ram_total) } - mem.update({ 'ram': ram }) - except Exception as e: - mem.update({ 'ram': e }) - try: - import torch - if torch.cuda.is_available(): - s = torch.cuda.mem_get_info() - gpu = { 'free': gb(s[0]), 'used': gb(s[1] - s[0]), 'total': gb(s[1]) } - s = dict(torch.cuda.memory_stats('cuda')) - allocated = { 'current': gb(s['allocated_bytes.all.current']), 'peak': gb(s['allocated_bytes.all.peak']) } - reserved = { 'current': gb(s['reserved_bytes.all.current']), 'peak': gb(s['reserved_bytes.all.peak']) } - active = { 'current': gb(s['active_bytes.all.current']), 'peak': gb(s['active_bytes.all.peak']) } - inactive = { 'current': gb(s['inactive_split_bytes.all.current']), 'peak': gb(s['inactive_split_bytes.all.peak']) } - warnings = { 'retries': s['num_alloc_retries'], 'oom': s['num_ooms'] } - mem.update({ - 'gpu': gpu, - 'gpu-active': active, - 'gpu-allocated': allocated, - 'gpu-reserved': reserved, - 'gpu-inactive': inactive, - 'events': warnings, - }) - except: - pass - return Map(mem) - - -class Map(dict): # pylint: disable=C0205 - __slots__ = ('__dict__') # pylint: disable=C0325 - def __init__(self, *args, **kwargs): - super(Map, self).__init__(*args, **kwargs) - for arg in args: - if isinstance(arg, dict): - for k, v in arg.items(): - if isinstance(v, dict): - v = Map(v) - if isinstance(v, list): - self.__convert(v) - self[k] = v - if kwargs: - for k, v in kwargs.items(): - if isinstance(v, dict): - v = Map(v) - elif isinstance(v, list): - self.__convert(v) - self[k] = v - def __convert(self, v): - for elem in range(0, len(v)): # pylint: disable=consider-using-enumerate - if isinstance(v[elem], dict): - v[elem] = Map(v[elem]) - elif isinstance(v[elem], list): - self.__convert(v[elem]) - def __getattr__(self, attr): - return self.get(attr) - def __setattr__(self, key, value): - self.__setitem__(key, value) - def __setitem__(self, key, value): - super(Map, self).__setitem__(key, value) - self.__dict__.update({key: value}) - def __delattr__(self, item): - self.__delitem__(item) - def __delitem__(self, key): - super(Map, self).__delitem__(key) - del self.__dict__[key] - - -if __name__ == "__main__": - pass diff --git a/cli/util.py b/cli/util.py index c1ca29c94..0fafe664c 100755 --- a/cli/util.py +++ b/cli/util.py @@ -6,9 +6,13 @@ generic helper methods import os import string import logging +import warnings log_format = '%(asctime)s %(levelname)s: %(message)s' logging.basicConfig(level = logging.INFO, format = log_format) +warnings.filterwarnings(action="ignore", category=DeprecationWarning) +warnings.filterwarnings(action="ignore", category=FutureWarning) +warnings.filterwarnings(action="ignore", category=UserWarning) log = logging.getLogger("sd") @@ -52,14 +56,14 @@ def get_memory(): reserved = { 'current': gb(s['reserved_bytes.all.current']), 'peak': gb(s['reserved_bytes.all.peak']) } active = { 'current': gb(s['active_bytes.all.current']), 'peak': gb(s['active_bytes.all.peak']) } inactive = { 'current': gb(s['inactive_split_bytes.all.current']), 'peak': gb(s['inactive_split_bytes.all.peak']) } - warnings = { 'retries': s['num_alloc_retries'], 'oom': s['num_ooms'] } + events = { 'retries': s['num_alloc_retries'], 'oom': s['num_ooms'] } mem.update({ 'gpu': gpu, 'gpu-active': active, 'gpu-allocated': allocated, 'gpu-reserved': reserved, 'gpu-inactive': inactive, - 'events': warnings, + 'events': events, }) except: pass diff --git a/extensions-builtin/multidiffusion-upscaler-for-automatic1111 b/extensions-builtin/multidiffusion-upscaler-for-automatic1111 index ade2c4441..50f5f8894 160000 --- a/extensions-builtin/multidiffusion-upscaler-for-automatic1111 +++ b/extensions-builtin/multidiffusion-upscaler-for-automatic1111 @@ -1 +1 @@ -Subproject commit ade2c4441988c80edc5b1d4360c3e0bc28b90a37 +Subproject commit 50f5f88944427a1f7e1321917790dbd9a5ddbed8 diff --git a/extensions-builtin/sd-webui-controlnet b/extensions-builtin/sd-webui-controlnet index 7c674f836..2514a460a 160000 --- a/extensions-builtin/sd-webui-controlnet +++ b/extensions-builtin/sd-webui-controlnet @@ -1 +1 @@ -Subproject commit 7c674f8364227d63e1628fc29fa8619d33c56674 +Subproject commit 2514a460ab9e0c6db38033aed29b12c94d5f1964 diff --git a/extensions-builtin/sd-webui-model-converter b/extensions-builtin/sd-webui-model-converter index d19e28168..f6e0fa538 160000 --- a/extensions-builtin/sd-webui-model-converter +++ b/extensions-builtin/sd-webui-model-converter @@ -1 +1 @@ -Subproject commit d19e28168268b0f2f50c8a5b7a4fa0a0d9b42b8c +Subproject commit f6e0fa5386fb82ef44feac74d66958af951fcc48 diff --git a/installer.py b/installer.py index 92f1e7584..b701803c9 100644 --- a/installer.py +++ b/installer.py @@ -68,6 +68,8 @@ def setup_logging(clean=False): traceback_install(console=console, extra_lines=1, width=console.width, word_wrap=False, indent_guides=False, suppress=[]) rh = RichHandler(show_time=True, omit_repeated_times=False, show_level=True, show_path=False, markup=False, rich_tracebacks=True, log_time_format='%H:%M:%S-%f', level=logging.DEBUG if args.debug else logging.INFO, console=console) rh.set_name(logging.DEBUG if args.debug else logging.INFO) + while log.hasHandlers() and len(log.handlers) > 0: + log.removeHandler(log.handlers[0]) log.addHandler(rh) @@ -186,6 +188,7 @@ def clone(url, folder, commithash=None): git(f'checkout {commithash}', folder) return else: + log.info(f'Cloning repository: {url}') git(f'clone "{url}" "{folder}"') if commithash is not None: git(f'-C "{folder}" checkout {commithash}') @@ -309,7 +312,7 @@ def install_packages(): # openclip_package = os.environ.get('OPENCLIP_PACKAGE', "git+https://github.com/mlfoundations/open_clip.git@bb6e834e9c70d9c27d0dc3ecedeebeaeb1ffad6b") # install(gfpgan_package, 'gfpgan') # install(openclip_package, 'open-clip-torch') - clip_package = os.environ.get('CLIP_PACKAGE', "git+https://github.com/openai/CLIP.git@d50d76daa670286dd6cacf3bcd80b5e4823fc8e1") + clip_package = os.environ.get('CLIP_PACKAGE', "git+https://github.com/openai/CLIP.git") install(clip_package, 'clip') install('onnxruntime==1.14.0', 'onnxruntime', ignore=True) @@ -321,19 +324,24 @@ def install_repositories(): log.info('Installing repositories') os.makedirs(os.path.join(os.path.dirname(__file__), 'repositories'), exist_ok=True) stable_diffusion_repo = os.environ.get('STABLE_DIFFUSION_REPO', "https://github.com/Stability-AI/stablediffusion.git") - stable_diffusion_commit = os.environ.get('STABLE_DIFFUSION_COMMIT_HASH', "cf1d67a6fd5ea1aa600c4df58e5b47da45f6bdbf") + # stable_diffusion_commit = os.environ.get('STABLE_DIFFUSION_COMMIT_HASH', "cf1d67a6fd5ea1aa600c4df58e5b47da45f6bdbf") + stable_diffusion_commit = os.environ.get('STABLE_DIFFUSION_COMMIT_HASH', None) clone(stable_diffusion_repo, d('stable-diffusion-stability-ai'), stable_diffusion_commit) taming_transformers_repo = os.environ.get('TAMING_TRANSFORMERS_REPO', "https://github.com/CompVis/taming-transformers.git") - taming_transformers_commit = os.environ.get('TAMING_TRANSFORMERS_COMMIT_HASH', "3ba01b241669f5ade541ce990f7650a3b8f65318") + # taming_transformers_commit = os.environ.get('TAMING_TRANSFORMERS_COMMIT_HASH', "3ba01b241669f5ade541ce990f7650a3b8f65318") + taming_transformers_commit = os.environ.get('TAMING_TRANSFORMERS_COMMIT_HASH', None) clone(taming_transformers_repo, d('taming-transformers'), taming_transformers_commit) k_diffusion_repo = os.environ.get('K_DIFFUSION_REPO', 'https://github.com/crowsonkb/k-diffusion.git') - k_diffusion_commit = os.environ.get('K_DIFFUSION_COMMIT_HASH', "b43db16749d51055f813255eea2fdf1def801919") + # k_diffusion_commit = os.environ.get('K_DIFFUSION_COMMIT_HASH', "b43db16749d51055f813255eea2fdf1def801919") + k_diffusion_commit = os.environ.get('K_DIFFUSION_COMMIT_HASH', None) clone(k_diffusion_repo, d('k-diffusion'), k_diffusion_commit) codeformer_repo = os.environ.get('CODEFORMER_REPO', 'https://github.com/sczhou/CodeFormer.git') - codeformer_commit = os.environ.get('CODEFORMER_COMMIT_HASH', "c5b4593074ba6214284d6acd5f1719b6c5d739af") + # codeformer_commit = os.environ.get('CODEFORMER_COMMIT_HASH', "c5b4593074ba6214284d6acd5f1719b6c5d739af") + codeformer_commit = os.environ.get('CODEFORMER_COMMIT_HASH', "7a584fd") clone(codeformer_repo, d('CodeFormer'), codeformer_commit) blip_repo = os.environ.get('BLIP_REPO', 'https://github.com/salesforce/BLIP.git') - blip_commit = os.environ.get('BLIP_COMMIT_HASH', "48211a1594f1321b00f14c9f7a5b4813144b2fb9") + # blip_commit = os.environ.get('BLIP_COMMIT_HASH', "48211a1594f1321b00f14c9f7a5b4813144b2fb9") + blip_commit = os.environ.get('BLIP_COMMIT_HASH', None) clone(blip_repo, d('BLIP'), blip_commit) @@ -635,12 +643,14 @@ def extensions_preload(force = False): log.info('Running extension preloading') if args.safe: log.info('Running in safe mode without user extensions') - from modules.script_loading import preload_extensions - from modules.paths_internal import extensions_builtin_dir, extensions_dir - extension_folders = [extensions_builtin_dir] if args.safe else [extensions_builtin_dir, extensions_dir] - for ext_dir in extension_folders: - preload_extensions(ext_dir, parser) - + try: + from modules.script_loading import preload_extensions + from modules.paths_internal import extensions_builtin_dir, extensions_dir + extension_folders = [extensions_builtin_dir] if args.safe else [extensions_builtin_dir, extensions_dir] + for ext_dir in extension_folders: + preload_extensions(ext_dir, parser, args.debug) + except: + log.error('Error running extension preloading') def git_reset(): log.warning('Running GIT reset') diff --git a/launch.py b/launch.py index d061afdfd..2b58226a1 100644 --- a/launch.py +++ b/launch.py @@ -9,6 +9,7 @@ commandline_args = os.environ.get('COMMANDLINE_ARGS', "") sys.argv += shlex.split(commandline_args) import installer +installer.setup_logging(False) installer.add_args() installer.ensure_base_requirements() installer.parse_args() diff --git a/modules/api/api.py b/modules/api/api.py index f44a03d9b..71230abc1 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -88,25 +88,16 @@ def encode_pil_to_base64(image): class Api: def __init__(self, app: FastAPI, queue_lock: Lock): - if shared.cmd_opts.api_auth: - self.credentials = dict() - for auth in shared.cmd_opts.api_auth.split(","): + self.credentials = dict() + if shared.cmd_opts.auth: + for auth in shared.cmd_opts.auth.split(","): user, password = auth.split(":") - self.credentials[user] = password - else: - if shared.cmd_opts.auth: - self.credentials = dict() - for auth in shared.cmd_opts.auth.split(","): - user, password = auth.split(":") - self.credentials[user] = password - user, password = [x.strip() for x in shared.cmd_opts.auth.strip('"').replace('\n', '').split(',') if x.strip()].split(':') - self.credentials[user] = password - if shared.cmd_opts.authfile: - self.credentials = dict() - with open(shared.cmd_opts.authfile, 'r', encoding="utf8") as file: - for line in file.readlines(): - user, password = line.split(":") - self.credentials[user] = password + self.credentials[user.replace('"', '').strip()] = password.replace('"', '').strip() + if shared.cmd_opts.auth_file: + with open(shared.cmd_opts.auth_file, 'r', encoding="utf8") as file: + for line in file.readlines(): + user, password = line.split(":") + self.credentials[user.replace('"', '').strip()] = password.replace('"', '').strip() self.router = APIRouter() self.app = app @@ -146,7 +137,7 @@ class Api: self.default_script_arg_img2img = [] def add_api_route(self, path: str, endpoint, **kwargs): - if shared.cmd_opts.api_auth: + if shared.cmd_opts.auth or shared.cmd_opts.auth_file: return self.app.add_api_route(path, endpoint, dependencies=[Depends(self.auth)], **kwargs) return self.app.add_api_route(path, endpoint, **kwargs) @@ -154,7 +145,7 @@ class Api: if credentials.username in self.credentials: if compare_digest(credentials.password, self.credentials[credentials.username]): return True - raise HTTPException(status_code=401, detail="Incorrect username or password", headers={"WWW-Authenticate": "Basic"}) + raise HTTPException(status_code=401, detail="Unauthorized", headers={"WWW-Authenticate": "Basic"}) def get_selectable_script(self, script_name, script_runner): if script_name is None or script_name == "": @@ -630,6 +621,5 @@ class Api: def launch(self, server_name, port): self.app.include_router(self.router) - server_name = "0.0.0.0" if cmd_opts.listen else None - + server_name = "0.0.0.0" if shared.cmd_opts.listen else None uvicorn.run(self.app, host=server_name, port=port) diff --git a/modules/cmd_args.py b/modules/cmd_args.py index b1541f608..2c62342bf 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -23,10 +23,9 @@ group.add_argument("--listen", action='store_true', help="Launch web server usin group.add_argument("--port", type=int, default=7860, help="Launch web server with given server port, default: %(default)s") group.add_argument("--freeze", action='store_true', help="Disable editing settings", default=False) group.add_argument("--auth", type=str, help='Set access authentication like "user:pwd,user:pwd""', default=None) -group.add_argument("--authfile", type=str, help='Set access authentication using file, default: %(default)s', default=None) +group.add_argument("--auth-file", type=str, help='Set access authentication using file, default: %(default)s', default=None) group.add_argument("--autolaunch", action='store_true', help="Open the UI URL in the system's default browser upon launch", default=False) group.add_argument('--api-only', default = False, action='store_true', help = "Run in API only mode without starting UI") -group.add_argument("--api-auth", type=str, help='Set API authentication, default: %(default)s', default=None) group.add_argument("--api-log", default=False, action='store_true', help="Enable logging of all API requests, default: %(default)s") group.add_argument("--device-id", type=str, help="Select the default CUDA device to use, default: %(default)s", default=None) group.add_argument("--cors-origins", type=str, help="Allowed CORS origins as comma-separated list, default: %(default)s", default=None) @@ -57,6 +56,7 @@ group.add_argument("--disable-safe-unpickle", action='store_true', help=argparse group.add_argument("--lowram", action='store_true', help=argparse.SUPPRESS) group.add_argument("--disable-extension-access", default = False, action='store_true', help=argparse.SUPPRESS) group.add_argument("--api", help=argparse.SUPPRESS, default=True) +group.add_argument("--api-auth", type=str, help=argparse.SUPPRESS, default=None) def compatibility_args(opts, args): diff --git a/modules/codeformer_model.py b/modules/codeformer_model.py index c2b0689ba..944dedfcd 100644 --- a/modules/codeformer_model.py +++ b/modules/codeformer_model.py @@ -20,10 +20,8 @@ codeformer = None def setup_model(dirname): - global model_path if not os.path.exists(model_path): os.makedirs(model_path) - path = modules.paths.paths.get("CodeFormer", None) if path is None: return @@ -31,7 +29,7 @@ def setup_model(dirname): try: from torchvision.transforms.functional import normalize from modules.codeformer.codeformer_arch import CodeFormer - from basicsr.utils import imwrite, img2tensor, tensor2img + from basicsr.utils import img2tensor, tensor2img from facelib.utils.face_restoration_helper import FaceRestoreHelper from facelib.detection.retinaface import retinaface from modules.shared import cmd_opts @@ -74,7 +72,7 @@ def setup_model(dirname): def send_model_to(self, device): self.net.to(device) - self.face_helper.face_det.to(device) + self.face_helper.face_det.to(device) # pylint: disable=no-member self.face_helper.face_parse.to(device) def restore(self, np_image, w=None): @@ -93,7 +91,7 @@ def setup_model(dirname): self.face_helper.get_face_landmarks_5(only_center_face=False, resize=640, eye_dist_threshold=5) self.face_helper.align_warp_face() - for idx, cropped_face in enumerate(self.face_helper.cropped_faces): + for _idx, cropped_face in enumerate(self.face_helper.cropped_faces): cropped_face_t = img2tensor(cropped_face / 255., bgr2rgb=True, float32=True) normalize(cropped_face_t, (0.5, 0.5, 0.5), (0.5, 0.5, 0.5), inplace=True) cropped_face_t = cropped_face_t.unsqueeze(0).to(devices.device_codeformer) @@ -129,10 +127,10 @@ def setup_model(dirname): return restored_img - global have_codeformer + global have_codeformer # pylint: disable=global-statement have_codeformer = True - global codeformer + global codeformer # pylint: disable=global-statement codeformer = FaceRestorerCodeFormer(dirname) shared.face_restorers.append(codeformer) diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index 6b0d4c00b..1fb8d159a 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -257,7 +257,7 @@ Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 965400086, Size: 512x512, Model res["Prompt"] = prompt res["Negative prompt"] = negative_prompt for k, v in re_param.findall(lastline): - v = v[1:-1] if v[0] == '"' and v[-1] == '"' else v + v = v[1:-1] if len(v) > 0 and v[0] == '"' and v[-1] == '"' else v m = re_imagesize.match(v) if m is not None: res[f"{k}-1"] = m.group(1) diff --git a/modules/images.py b/modules/images.py index a33674cbc..7c615dd13 100644 --- a/modules/images.py +++ b/modules/images.py @@ -236,7 +236,7 @@ def resize_image(resize_mode, im, width, height, upscaler_name=None): upscalers = [x for x in shared.sd_upscalers if x.name == upscaler_name] if len(upscalers) == 0: upscaler = shared.sd_upscalers[0] - shared.log.warning(f"could not find upscaler named {upscaler_name or ''}, using {upscaler.name} as a fallback") + shared.log.warning(f"Could not find upscaler named {upscaler_name or ''}, using {upscaler.name} as a fallback") else: upscaler = upscalers[0] im = upscaler.scaler.upscale(im, scale, upscaler.data_path) diff --git a/modules/processing.py b/modules/processing.py index 0df5ede29..ad80a6dca 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -3,7 +3,6 @@ import math import os import hashlib import random -import logging from typing import Any, Dict, List import torch import numpy as np @@ -33,13 +32,13 @@ opt_f = 8 def setup_color_correction(image): - logging.info("Calibrating color correction.") + log.debug("Calibrating color correction.") correction_target = cv2.cvtColor(np.asarray(image.copy()), cv2.COLOR_RGB2LAB) return correction_target def apply_color_correction(correction, original_image): - logging.info("Applying color correction.") + log.debug("Applying color correction.") image = Image.fromarray(cv2.cvtColor(exposure.match_histograms( cv2.cvtColor(np.asarray(original_image), cv2.COLOR_RGB2LAB), correction, @@ -575,7 +574,9 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: for n in range(p.n_iter): p.iteration = n if state.skipped: + shared.log.debug(f'Process skipped: {n}/{p.n_iter}') state.skipped = False + continue if state.interrupted: shared.log.debug(f'Process interrupted: {n}/{p.n_iter}') break @@ -710,7 +711,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: index_of_first_image=index_of_first_image, infotexts=infotexts, ) - if p.scripts is not None: + if p.scripts is not None and not state.interrupted: p.scripts.postprocess(p, res) return res @@ -803,10 +804,12 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): self.sampler = sd_samplers.create_sampler(self.sampler_name, self.sd_model) latent_scale_mode = shared.latent_upscale_modes.get(self.hr_upscaler, None) if self.hr_upscaler is not None else shared.latent_upscale_modes.get(shared.latent_upscale_default_mode, "nearest") if self.enable_hr and latent_scale_mode is None: - assert len([x for x in shared.sd_upscalers if x.name == self.hr_upscaler]) > 0, f"could not find upscaler named {self.hr_upscaler}" + if len([x for x in shared.sd_upscalers if x.name == self.hr_upscaler]) == 0: + log.warning("Could not find upscaler to use with hrfix") + self.enable_hr = False x = create_random_tensors([opt_C, self.height // opt_f, self.width // opt_f], seeds=seeds, subseeds=subseeds, subseed_strength=self.subseed_strength, seed_resize_from_h=self.seed_resize_from_h, seed_resize_from_w=self.seed_resize_from_w, p=self) samples = self.sampler.sample(self, x, conditioning, unconditional_conditioning, image_conditioning=self.txt2img_image_conditioning(x)) - if not self.enable_hr: + if not self.enable_hr or state.interrupted or state.skipped: return samples self.is_hr_pass = True target_width = self.hr_upscale_to_x diff --git a/modules/script_loading.py b/modules/script_loading.py index d93fd898f..2bc9b6fda 100644 --- a/modules/script_loading.py +++ b/modules/script_loading.py @@ -3,18 +3,24 @@ import importlib.util import modules.errors as errors -def load_module(path): +preloaded = [] + + +def load_module(path, detailed=False): module_spec = importlib.util.spec_from_file_location(os.path.basename(path), path) module = importlib.util.module_from_spec(module_spec) try: module_spec.loader.exec_module(module) except Exception as e: - errors.display(e, f'Module load: {path}') + if detailed: + errors.display(e, f'Module load: {path}') + else: + errors.log.error(f'Module load: {path}') return module -preloaded = [] -def preload_extensions(extensions_dir, parser): + +def preload_extensions(extensions_dir, parser, detailed=False): if not os.path.isdir(extensions_dir): return for dirname in sorted(os.listdir(extensions_dir)): @@ -29,4 +35,7 @@ def preload_extensions(extensions_dir, parser): if hasattr(module, 'preload'): module.preload(parser) except Exception as e: - errors.display(e, f'Extension preload: {preload_script}') + if detailed: + errors.display(e, f'Extension preload: {preload_script}') + else: + errors.log.error(f'Extension preload: {preload_script}') diff --git a/scripts/postprocessing_codeformer.py b/scripts/postprocessing_codeformer.py index 251443642..822d06cfa 100644 --- a/scripts/postprocessing_codeformer.py +++ b/scripts/postprocessing_codeformer.py @@ -11,7 +11,7 @@ class ScriptPostprocessingCodeFormer(scripts_postprocessing.ScriptPostprocessing def ui(self): with FormRow(): - codeformer_visibility = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label="CodeFormer visibility", value=1.0, elem_id="extras_codeformer_visibility") + codeformer_visibility = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label="CodeFormer visibility", value=0.0, elem_id="extras_codeformer_visibility") codeformer_weight = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label="CodeFormer weight (0 = max), 1 = min)", value=0.2, elem_id="extras_codeformer_weight") return { diff --git a/scripts/postprocessing_upscale.py b/scripts/postprocessing_upscale.py index 0879e4005..e1faddeaf 100644 --- a/scripts/postprocessing_upscale.py +++ b/scripts/postprocessing_upscale.py @@ -83,16 +83,17 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): upscaler_1_name = None upscaler1 = next(iter([x for x in shared.sd_upscalers if x.name == upscaler_1_name]), None) - assert upscaler1 or (upscaler_1_name is None), f'could not find upscaler named {upscaler_1_name}' - if not upscaler1: + shared.log.warning(f"Could not find upscaler named {upscaler_1_name or ''}") return if upscaler_2_name == "None": upscaler_2_name = None upscaler2 = next(iter([x for x in shared.sd_upscalers if x.name == upscaler_2_name and x.name != "None"]), None) - assert upscaler2 or (upscaler_2_name is None), f'could not find upscaler named {upscaler_2_name}' + if not upscaler2 and (upscaler_2_name is not None): + shared.log.warning(f"Could not find upscaler named {upscaler_1_name or ''}") + return upscaled_image = self.upscale(pp.image, pp.info, upscaler1, upscale_mode, upscale_by, upscale_to_width, upscale_to_height, upscale_crop) pp.info["Postprocess upscaler"] = upscaler1.name @@ -128,7 +129,8 @@ class ScriptPostprocessingUpscaleSimple(ScriptPostprocessingUpscale): return upscaler1 = next(iter([x for x in shared.sd_upscalers if x.name == upscaler_name]), None) - assert upscaler1, f'could not find upscaler named {upscaler_name}' + if upscaler1 is None: + shared.log.warning(f"Could not find upscaler named {upscaler_name or ''}") pp.image = self.upscale(pp.image, pp.info, upscaler1, 0, upscale_by, 0, 0, False) pp.info["Postprocess upscaler"] = upscaler1.name diff --git a/webui.py b/webui.py index afdb03a6a..3f74568d1 100644 --- a/webui.py +++ b/webui.py @@ -223,8 +223,8 @@ def start_ui(): gradio_auth_creds = [] if cmd_opts.auth: gradio_auth_creds += [x.strip() for x in cmd_opts.auth.strip('"').replace('\n', '').split(',') if x.strip()] - if cmd_opts.authfile: - with open(cmd_opts.authfile, 'r', encoding="utf8") as file: + if cmd_opts.auth_file: + with open(cmd_opts.auth_file, 'r', encoding="utf8") as file: for line in file.readlines(): gradio_auth_creds += [x.strip() for x in line.split(',') if x.strip()]