diff --git a/CHANGELOG.md b/CHANGELOG.md
index d2c6c83e9..1b20e9b69 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -52,7 +52,6 @@ TBD
- refactor: reorganize `cli` scripts
- refactor: move tests to dedicated `/test/`
- refactor: all image handling to `modules/image/`
- - refactor: captioning part-2, thanks @CalamitousFelicitousness
- refactor: remove face restoration, thanks @CalamitousFelicitousness
- refactor: unified command line parsing
- refactor: launch use threads to async execute non-critical tasks
@@ -61,11 +60,13 @@ TBD
- refactor: improve `pydantic==2.x` compatibility
- refactor: entire logging into separate `modules/logger`
- refactor: replace `timestamp` based startup checks with state caching
- - refactor: split monolithic `shared` module and introduce `ui_definitions`
- - update `lint` rules, thanks @awsr
+ - refactor: split monolithic `shared` module and introduce `ui_definitions`
+ - use `threading` for deferable operatios
+ - use `threading` for io-independent parallel operations
- remove requirements: `clip`, `open-clip`
- remove `normalbae` pre-processor
- - update `requirements`
+ - refactor: captioning part-2, thanks @CalamitousFelicitousness
+ - update `lint` rules, thanks @awsr
- **Fixes**
- handle `clip` installer doing unwanted `setuptools` update
- cleanup for `uv` installer fallback
diff --git a/installer.py b/installer.py
index 9e237f8c1..91c28b26a 100644
--- a/installer.py
+++ b/installer.py
@@ -1,4 +1,3 @@
-from typing import overload
from functools import lru_cache
import os
import sys
@@ -14,7 +13,7 @@ import cProfile
import importlib
import importlib.util
import importlib.metadata
-from modules.logger import setup_logging, get_console, get_log, install_traceback, log, console
+from modules.logger import setup_logging, log
class Dot(dict): # dot notation access to dictionary attributes
@@ -141,7 +140,7 @@ def package_spec(package):
# check if package is installed
-def installed(package, friendly: str = None, reload = False, quiet = False): # pylint: disable=redefined-outer-name
+def installed(package, friendly: str = None, quiet = False): # pylint: disable=redefined-outer-name
t_start = time.time()
ok = True
try:
@@ -275,7 +274,6 @@ def install(package, friendly: str = None, ignore: bool = False, reinstall: bool
isolation = '' if not no_build_isolation else '--no-build-isolation '
cmd = f"install{' --upgrade' if not args.uv else ''}{' --force-reinstall' if force else ''} {deps}{isolation}{package}"
res = pip(cmd, ignore=ignore, uv=package != "uv" and not package.startswith('git+'))
- pass
ts('install', t_start)
return res
@@ -1095,10 +1093,11 @@ def install_gradio():
# aiofiles-23.2.1 altair-5.5.0 annotated-types-0.7.0 anyio-4.9.0 attrs-25.3.0 certifi-2025.6.15 charset_normalizer-3.4.2 click-8.2.1 contourpy-1.3.2 cycler-0.12.1 fastapi-0.115.14 ffmpy-0.6.0 filelock-3.18.0 fonttools-4.58.4 fsspec-2025.5.1 gradio-3.43.2 gradio-client-0.5.0 h11-0.16.0 hf-xet-1.1.5 httpcore-1.0.9 httpx-0.28.1 huggingface-hub-0.33.1 idna-3.10 importlib-resources-6.5.2 jinja2-3.1.6 jsonschema-4.24.0 jsonschema-specifications-2025.4.1 kiwisolver-1.4.8 markupsafe-2.1.5 matplotlib-3.10.3 narwhals-1.45.0 numpy-1.26.4 orjson-3.10.18 packaging-25.0 pandas-2.3.0 pillow-10.4.0 pydantic-2.11.7 pydantic-core-2.33.2 pydub-0.25.1 pyparsing-3.2.3 python-dateutil-2.9.0.post0 python-multipart-0.0.20 pytz-2025.2 pyyaml-6.0.2 referencing-0.36.2 requests-2.32.4 rpds-py-0.25.1 semantic-version-2.10.0 six-1.17.0 sniffio-1.3.1 starlette-0.46.2 tqdm-4.67.1 typing-extensions-4.14.0 typing-inspection-0.4.1 tzdata-2025.2 urllib3-2.5.0 uvicorn-0.35.0 websockets-11.0.3
install('gradio==3.43.2', no_deps=True)
install('gradio-client==0.5.0', no_deps=True, quiet=True)
- pkgs = ['fastapi', 'websockets', 'aiofiles', 'ffmpy', 'pydub', 'uvicorn', 'semantic-version', 'altair', 'python-multipart', 'matplotlib']
- for pkg in pkgs:
- if not installed(pkg, quiet=True):
- install(pkg, quiet=True)
+ if not quick_allowed: # on quick path these are guaranteed installed by the state file
+ pkgs = ['fastapi', 'websockets', 'aiofiles', 'ffmpy', 'pydub', 'uvicorn', 'semantic-version', 'altair', 'python-multipart', 'matplotlib']
+ for pkg in pkgs:
+ if not installed(pkg, quiet=True):
+ install(pkg, quiet=True)
def install_pydantic():
@@ -1178,7 +1177,6 @@ def install_requirements():
if args.optional:
quick_allowed = False
install_optional()
- installed('torch', reload=True) # reload packages cache
log.info('Install: verifying requirements')
if args.new:
log.debug('Install: flag=new')
@@ -1480,6 +1478,11 @@ def run_deferred_tasks():
t_start = time.time()
log.debug('Starting deferred tasks')
time.sleep(1.0) # wait for server to start
+ try:
+ from modules.sd_models import write_metadata
+ write_metadata()
+ except Exception as e:
+ log.error(f'Deferred task error: write_metadata {e}')
try:
check_version()
except Exception as e:
@@ -1514,20 +1517,28 @@ def get_state():
except Exception:
pass
try:
+ from concurrent.futures import ThreadPoolExecutor
from modules.paths import extensions_builtin_dir, extensions_dir
extension_folders = [extensions_builtin_dir] if args.safe else [extensions_builtin_dir, extensions_dir]
+ ext_dirs = []
for folder in extension_folders:
if not os.path.isdir(folder):
continue
- extensions = list_extensions_folder(folder, quiet=True)
- for ext in extensions:
- extension_dir = os.path.join(folder, ext)
- try:
- res = subprocess.run('git rev-parse HEAD', capture_output=True, shell=True, check=False, cwd=extension_dir)
- commit = res.stdout.decode(encoding='utf8', errors='ignore').strip()
+ for ext in list_extensions_folder(folder, quiet=True):
+ ext_dirs.append((ext, os.path.join(folder, ext)))
+
+ def _get_commit(item):
+ ext, ext_dir = item
+ try:
+ res = subprocess.run('git rev-parse HEAD', capture_output=True, shell=True, check=False, cwd=ext_dir)
+ return ext, res.stdout.decode(encoding='utf8', errors='ignore').strip()
+ except Exception:
+ return ext, ''
+
+ with ThreadPoolExecutor(max_workers=min(len(ext_dirs), 8), thread_name_prefix='sdnext-git') as pool:
+ for ext, commit in pool.map(_get_commit, ext_dirs):
+ if commit:
state['extensions'][ext] = commit
- except Exception:
- pass
except Exception:
pass
return state
diff --git a/launch.py b/launch.py
index 369aef053..b4076354d 100755
--- a/launch.py
+++ b/launch.py
@@ -1,13 +1,13 @@
#!/usr/bin/env python
-from modules.logger import log
+from functools import lru_cache
import os
import sys
import time
import shlex
import subprocess
-from functools import lru_cache
import installer
+from modules.logger import log
debug_install = log.debug if os.environ.get('SD_INSTALL_DEBUG', None) is not None else lambda *args, **kwargs: None
diff --git a/modules/control/proc/dwpose/__init__.py b/modules/control/proc/dwpose/__init__.py
index 8f1f223af..ba1ea332c 100644
--- a/modules/control/proc/dwpose/__init__.py
+++ b/modules/control/proc/dwpose/__init__.py
@@ -49,7 +49,7 @@ def check_dependencies():
'mmpose==1.3.2',
'mmdet==3.3.0',
]
- status = [installed(p, reload=False, quiet=True) for p in packages]
+ status = [installed(p, quiet=True) for p in packages]
debug(f'DWPose required={packages} status={status}')
if not all(status):
log.info(f'Installing dependencies: for=dwpose packages={packages}')
diff --git a/modules/control/proc/mediapipe_face.py b/modules/control/proc/mediapipe_face.py
index 834d7910c..ceda9f857 100644
--- a/modules/control/proc/mediapipe_face.py
+++ b/modules/control/proc/mediapipe_face.py
@@ -13,7 +13,7 @@ def check_dependencies():
from modules.logger import log
packages = [('mediapipe', 'mediapipe')]
for pkg in packages:
- if not installed(pkg[1], reload=True, quiet=True):
+ if not installed(pkg[1], quiet=True):
install(pkg[0], pkg[1], ignore=False)
try:
import mediapipe as mp # pylint: disable=unused-import
diff --git a/modules/control/run.py b/modules/control/run.py
index 0954abea0..d5e1fc8f3 100644
--- a/modules/control/run.py
+++ b/modules/control/run.py
@@ -12,7 +12,7 @@ from modules.control.units import lite # Kohya ControlLLLite
from modules.control.units import t2iadapter # TencentARC T2I-Adapter
from modules.control.units import reference # ControlNet-Reference
from modules.control.processor import preprocess_image
-from modules import devices, shared, errors, processing, images, sd_models, sd_vae, scripts_manager, masking
+from modules import devices, shared, errors, processing, images, video, sd_models, sd_vae, scripts_manager, masking
from modules.logger import log
from modules.processing_class import StableDiffusionProcessingControl
from modules.ui_common import infotext_to_html
@@ -679,7 +679,7 @@ def control_run(state: str = '', # pylint: disable=keyword-arg-before-vararg
if video_type != 'None' and isinstance(output_images, list) and 'video' in p.ops:
p.do_not_save_grid = True # pylint: disable=attribute-defined-outside-init
- output_filename = images.save_video(p, filename=None, images=output_images, video_type=video_type, duration=video_duration, loop=video_loop, pad=video_pad, interpolate=video_interpolate, sync=True)
+ output_filename = video.save_video(p, filename=None, images=output_images, video_type=video_type, duration=video_duration, loop=video_loop, pad=video_pad, interpolate=video_interpolate, sync=True)
if shared.opts.gradio_skip_video:
output_filename = ''
image_txt = f'| Frames {len(output_images)} | Size {output_images[0].width}x{output_images[0].height}'
diff --git a/modules/face/insightface.py b/modules/face/insightface.py
index 99be2d32e..b167080e3 100644
--- a/modules/face/insightface.py
+++ b/modules/face/insightface.py
@@ -11,9 +11,9 @@ def get_app(mp_name, threshold=0.5, resolution=640):
global insightface_app, instightface_mp # pylint: disable=global-statement
from installer import install, installed, install_insightface
- if not installed('insightface', reload=False, quiet=True):
+ if not installed('insightface', quiet=True):
install_insightface()
- if not installed('ip_adapter', reload=False, quiet=True):
+ if not installed('ip_adapter', quiet=True):
install('git+https://github.com/tencent-ailab/IP-Adapter.git', 'ip_adapter', ignore=False)
if insightface_app is None or mp_name != instightface_mp:
diff --git a/modules/images.py b/modules/images.py
index e5137c072..32e831e6c 100644
--- a/modules/images.py
+++ b/modules/images.py
@@ -1,7 +1,8 @@
from modules.image.metadata import image_data, read_info_from_image
from modules.image.save import save_image, sanitize_filename_part
from modules.image.resize import resize_image
-from modules.image.grid import image_grid, check_grid_size, get_grid_size, draw_grid_annotations, draw_prompt_matrix
+from modules.image.namegen import FilenameGenerator
+from modules.image.grid import image_grid, check_grid_size, get_grid_size, draw_grid_annotations, draw_prompt_matrix, combine_grid
__all__ = [
'check_grid_size',
@@ -10,8 +11,10 @@ __all__ = [
'get_grid_size',
'image_data',
'image_grid',
+ 'combine_grid',
'read_info_from_image',
'resize_image',
'sanitize_filename_part',
- 'save_image'
+ 'save_image',
+ 'FilenameGenerator',
]
diff --git a/modules/intel/openvino/__init__.py b/modules/intel/openvino/__init__.py
index 252c5d730..8eed0eb10 100644
--- a/modules/intel/openvino/__init__.py
+++ b/modules/intel/openvino/__init__.py
@@ -19,7 +19,7 @@ from types import MappingProxyType
from hashlib import sha256
import functools
-from modules import shared, devices, sd_models
+from modules import shared, devices, sd_models, sd_models_utils
from modules.logger import log
@@ -527,7 +527,7 @@ def openvino_fx(subgraph, example_inputs, options=None):
pass
else:
# Delete unused subgraphs
- subgraph = subgraph.apply(sd_models.convert_to_faketensors)
+ subgraph = subgraph.apply(sd_models_utils.convert_to_faketensors)
devices.torch_gc(force=True, reason='openvino')
# Model is fully supported and already cached. Run the cached OV model directly.
diff --git a/modules/lora/network.py b/modules/lora/network.py
index a959a3338..9172a584d 100644
--- a/modules/lora/network.py
+++ b/modules/lora/network.py
@@ -1,7 +1,7 @@
import os
import enum
from collections import namedtuple
-from modules import sd_models, hashes, shared
+from modules import hashes, shared, sd_models, sd_checkpoint
NetworkWeights = namedtuple('NetworkWeights', ['network_key', 'sd_key', 'w', 'sd_module'])
@@ -33,7 +33,7 @@ class NetworkOnDisk:
self.metadata = {}
self.is_safetensors = os.path.splitext(filename)[1].lower() == ".safetensors"
if self.is_safetensors:
- self.metadata = sd_models.read_metadata_from_safetensors(filename)
+ self.metadata = sd_checkpoint.read_metadata_from_safetensors(filename)
if self.metadata:
m = {}
for k, v in sorted(self.metadata.items(), key=lambda x: metadata_tags_order.get(x[0], 999)):
diff --git a/modules/memmon.py b/modules/memmon.py
index eb521f93f..32070ac79 100644
--- a/modules/memmon.py
+++ b/modules/memmon.py
@@ -55,7 +55,7 @@ class MemUsageMonitor:
return self.data
def summary(self):
- from modules.shared import ram_stats
+ from modules.memstats import ram_stats
gpu = ''
cpu = ''
gpu = ''
diff --git a/modules/onnx_impl/execution_providers.py b/modules/onnx_impl/execution_providers.py
index e8978aa06..1b0a47be5 100644
--- a/modules/onnx_impl/execution_providers.py
+++ b/modules/onnx_impl/execution_providers.py
@@ -99,7 +99,6 @@ def install_execution_provider(ep: ExecutionProvider):
from installer import installed, install, uninstall
res = "
"
res += uninstall(["onnxruntime", "onnxruntime-directml", "onnxruntime-gpu", "onnxruntime-training", "onnxruntime-openvino"], quiet=True)
- installed("onnxruntime", reload=True)
packages = ["onnxruntime"] # Failed to load olive: cannot import name '__version__' from 'onnxruntime'
if ep == ExecutionProvider.DirectML:
packages.append("onnxruntime-directml")
diff --git a/modules/postprocess/esrgan_model.py b/modules/postprocess/esrgan_model.py
index f79e5de4b..0014725cb 100644
--- a/modules/postprocess/esrgan_model.py
+++ b/modules/postprocess/esrgan_model.py
@@ -4,6 +4,7 @@ from PIL import Image
from rich.progress import Progress, TextColumn, BarColumn, TaskProgressColumn, TimeRemainingColumn, TimeElapsedColumn
import modules.postprocess.esrgan_model_arch as arch
from modules import images, devices, shared
+from modules.images.grid import split_grid
from modules.logger import log, console
from modules.upscaler import Upscaler, UpscalerData, compile_upscaler
@@ -193,7 +194,7 @@ def esrgan_upscale(model, img):
if shared.opts.upscaler_tile_size == 0:
return upscale_without_tiling(model, img)
- grid = images.split_grid(img, shared.opts.upscaler_tile_size, shared.opts.upscaler_tile_size, shared.opts.upscaler_tile_overlap)
+ grid = split_grid(img, shared.opts.upscaler_tile_size, shared.opts.upscaler_tile_size, shared.opts.upscaler_tile_overlap)
newtiles = []
scale_factor = 1
diff --git a/modules/processing_vae.py b/modules/processing_vae.py
index 28bf33297..d720a57b5 100644
--- a/modules/processing_vae.py
+++ b/modules/processing_vae.py
@@ -2,7 +2,7 @@ import os
import time
import numpy as np
import torch
-from modules import shared, devices, sd_models, sd_vae, errors
+from modules import shared, devices, errors, sd_models, sd_models_utils, sd_vae
from modules.logger import log
from modules.vae import sd_vae_taesd
@@ -71,7 +71,7 @@ def full_vqgan_decode(latents, model):
if 'VAE' in shared.opts.cuda_compile and shared.opts.cuda_compile_backend == "openvino_fx" and shared.compiled_model_state.first_pass_vae:
shared.compiled_model_state.first_pass_vae = False
if not shared.opts.openvino_disable_memory_cleanup and hasattr(shared.sd_model, "vqgan"):
- model.vqgan.apply(sd_models.convert_to_faketensors)
+ model.vqgan.apply(sd_models_utils.convert_to_faketensors)
devices.torch_gc(force=True)
if shared.opts.diffusers_offload_mode == "balanced":
@@ -166,7 +166,7 @@ def full_vae_decode(latents, model):
if 'VAE' in shared.opts.cuda_compile and shared.opts.cuda_compile_backend == "openvino_fx" and shared.compiled_model_state.first_pass_vae:
shared.compiled_model_state.first_pass_vae = False
if not shared.opts.openvino_disable_memory_cleanup and hasattr(shared.sd_model, "vae"):
- model.vae.apply(sd_models.convert_to_faketensors)
+ model.vae.apply(sd_models_utils.convert_to_faketensors)
devices.torch_gc(force=True)
elif shared.opts.diffusers_move_unet and not getattr(model, 'has_accelerate', False) and base_device is not None:
diff --git a/modules/sd_models_compile.py b/modules/sd_models_compile.py
index ecaf91989..0b68f6047 100644
--- a/modules/sd_models_compile.py
+++ b/modules/sd_models_compile.py
@@ -1,7 +1,7 @@
import time
import logging
import torch
-from modules import shared, devices, sd_models, errors
+from modules import shared, errors, devices, sd_models, sd_models_utils
from modules.logger import log
from installer import setup_logging
@@ -319,10 +319,10 @@ def openvino_post_compile(op="base"): # delete unet after OpenVINO compile
if shared.compiled_model_state.first_pass and op == "base":
shared.compiled_model_state.first_pass = False
if not shared.opts.openvino_disable_memory_cleanup and hasattr(shared.sd_model, "unet"):
- shared.sd_model.unet.apply(sd_models.convert_to_faketensors)
+ shared.sd_model.unet.apply(sd_models_utils.convert_to_faketensors)
devices.torch_gc(force=True)
if shared.compiled_model_state.first_pass_refiner and op == "refiner":
shared.compiled_model_state.first_pass_refiner = False
if not shared.opts.openvino_disable_memory_cleanup and hasattr(shared.sd_refiner, "unet"):
- shared.sd_refiner.unet.apply(sd_models.convert_to_faketensors)
+ shared.sd_refiner.unet.apply(sd_models_utils.convert_to_faketensors)
devices.torch_gc(force=True)
diff --git a/modules/shared_state.py b/modules/shared_state.py
index f7a086849..9ada75aa0 100644
--- a/modules/shared_state.py
+++ b/modules/shared_state.py
@@ -271,7 +271,8 @@ class State:
def do_set_current_image(self):
if (self.current_latent is None) or self.disable_preview or (self.preview_job == self.job_no):
return False
- from modules import shared, sd_samplers
+ from modules import shared, sd_samplers, sd_samplers_common
+ from modules.sd_samplers_common import samples_to_image_grid, sample_to_image
self.preview_job = self.job_no
try:
sample = self.current_latent
@@ -285,7 +286,7 @@ class State:
sample = self.current_noise_pred * (-self.current_sigma / (self.current_sigma**2 + 1) ** 0.5) + (original_sample / (self.current_sigma**2 + 1)) # pylint: disable=invalid-unary-operand-type
except Exception:
pass # ignore sigma errors
- image = sd_samplers.samples_to_image_grid(sample) if shared.opts.show_progress_grid else sd_samplers.sample_to_image(sample)
+ image = samples_to_image_grid(sample) if shared.opts.show_progress_grid else sample_to_image(sample)
self.assign_current_image(image)
self.preview_job = -1
return True
diff --git a/modules/theme.py b/modules/theme.py
index 459fb8df4..8af1b0148 100644
--- a/modules/theme.py
+++ b/modules/theme.py
@@ -3,6 +3,7 @@ import json
import gradio as gr
import modules.shared
import modules.extensions
+from modules.logger import log
gradio_theme = gr.themes.Base()
@@ -21,18 +22,18 @@ def refresh_themes(no_update=False):
with open(themes_file, encoding='utf8') as f:
res = json.load(f)
except Exception:
- modules.log.error('Exception loading UI themes')
+ log.error('Exception loading UI themes')
if not no_update:
try:
- modules.log.info('Refreshing UI themes')
+ log.info('Refreshing UI themes')
r = modules.shared.req('https://huggingface.co/datasets/freddyaboulton/gradio-theme-subdomains/resolve/main/subdomains.json')
if r.status_code == 200:
res = r.json()
modules.shared.writefile(res, themes_file)
else:
- modules.log.error('Error refreshing UI themes')
+ log.error('Error refreshing UI themes')
except Exception:
- modules.log.error('Exception refreshing UI themes')
+ log.error('Exception refreshing UI themes')
return res
@@ -46,12 +47,12 @@ def list_themes():
themes = ['lobe']
modules.shared.opts.data['gradio_theme'] = themes[0]
modules.shared.opts.data['theme_type'] = 'None'
- modules.log.info('UI theme: extension="lobe"')
+ log.info('UI theme: extension="lobe"')
elif 'Cozy-Nest' in extensions and modules.shared.opts.gradio_theme == 'cozy-nest':
themes = ['cozy-nest']
modules.shared.opts.data['gradio_theme'] = themes[0]
modules.shared.opts.data['theme_type'] = 'None'
- modules.log.info('UI theme: extension="cozy-nest"')
+ log.info('UI theme: extension="cozy-nest"')
elif modules.shared.opts.theme_type == 'None':
gradio = ["gradio/default", "gradio/base", "gradio/glass", "gradio/monochrome", "gradio/soft"]
huggingface = refresh_themes(no_update=True)
@@ -64,7 +65,7 @@ def list_themes():
elif modules.shared.opts.theme_type == 'Modern':
ext = next((e for e in modules.extensions.extensions if e.name == 'sdnext-modernui'), None)
if ext is None:
- modules.log.error('UI themes: ModernUI not found')
+ log.error('UI themes: ModernUI not found')
builtin = list_builtin_themes()
themes = sorted(builtin)
modules.shared.opts.theme_type = 'Standard'
@@ -79,7 +80,7 @@ def list_themes():
themes.append('modern/Default')
themes = sorted(themes)
else:
- modules.log.error(f'UI themes: type={modules.shared.opts.theme_type} unknown')
+ log.error(f'UI themes: type={modules.shared.opts.theme_type} unknown')
themes = []
return themes
@@ -94,7 +95,7 @@ def reload_gradio_theme():
gradio_theme = gr.themes.Base(**default_font_params)
available_themes = list_themes()
if theme_name not in available_themes:
- # modules.log.error(f'UI theme invalid: type={modules.shared.opts.theme_type} theme="{theme_name}"')
+ # log.error(f'UI theme invalid: type={modules.shared.opts.theme_type} theme="{theme_name}"')
if modules.shared.opts.theme_type == 'Standard':
theme_name = 'black-teal'
elif modules.shared.opts.theme_type == 'Modern':
@@ -106,22 +107,22 @@ def reload_gradio_theme():
theme_name = 'black-teal'
modules.shared.opts.data['gradio_theme'] = theme_name
- modules.log.info(f'UI locale: name="{modules.shared.opts.ui_locale}"')
+ log.info(f'UI locale: name="{modules.shared.opts.ui_locale}"')
if theme_name.lower() in ['lobe', 'cozy-nest']:
- modules.log.info(f'UI theme extension: name="{theme_name}"')
+ log.info(f'UI theme extension: name="{theme_name}"')
return None
elif modules.shared.opts.theme_type == 'Standard':
gradio_theme = gr.themes.Base(**default_font_params)
- modules.log.info(f'UI theme: type={modules.shared.opts.theme_type} name="{theme_name}" available={len(available_themes)}')
+ log.info(f'UI theme: type={modules.shared.opts.theme_type} name="{theme_name}" available={len(available_themes)}')
return 'sdnext.css'
elif modules.shared.opts.theme_type == 'Modern':
gradio_theme = gr.themes.Base(**default_font_params)
- modules.log.info(f'UI theme: type={modules.shared.opts.theme_type} name="{theme_name}" available={len(available_themes)}')
+ log.info(f'UI theme: type={modules.shared.opts.theme_type} name="{theme_name}" available={len(available_themes)}')
return 'base.css'
elif modules.shared.opts.theme_type == 'None':
if theme_name.startswith('gradio/'):
- modules.log.warning('UI theme: using Gradio default theme which is not optimized for SD.Next')
+ log.warning('UI theme: using Gradio default theme which is not optimized for SD.Next')
if theme_name == "gradio/default":
gradio_theme = gr.themes.Default(**default_font_params)
elif theme_name == "gradio/base":
@@ -133,18 +134,18 @@ def reload_gradio_theme():
elif theme_name == "gradio/soft":
gradio_theme = gr.themes.Soft(**default_font_params)
else:
- modules.log.warning('UI theme: unknown Gradio theme')
+ log.warning('UI theme: unknown Gradio theme')
theme_name = "gradio/default"
gradio_theme = gr.themes.Default(**default_font_params)
elif theme_name.startswith('huggingface/'):
- modules.log.warning('UI theme: using 3rd party theme which is not optimized for SD.Next')
+ log.warning('UI theme: using 3rd party theme which is not optimized for SD.Next')
try:
hf_theme_name = theme_name.replace('huggingface/', '')
gradio_theme = gr.themes.ThemeClass.from_hub(hf_theme_name)
except Exception as e:
- modules.log.error(f"UI theme: download error accessing HuggingFace {e}")
+ log.error(f"UI theme: download error accessing HuggingFace {e}")
gradio_theme = gr.themes.Default(**default_font_params)
- modules.log.info(f'UI theme: type={modules.shared.opts.theme_type} name="{theme_name}" style={modules.shared.opts.theme_style}')
+ log.info(f'UI theme: type={modules.shared.opts.theme_type} name="{theme_name}" style={modules.shared.opts.theme_style}')
return 'base.css'
- modules.log.error(f'UI theme: type={modules.shared.opts.theme_type} unknown')
+ log.error(f'UI theme: type={modules.shared.opts.theme_type} unknown')
return None
diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py
index 094912e18..b1ade3229 100644
--- a/modules/ui_extra_networks.py
+++ b/modules/ui_extra_networks.py
@@ -18,6 +18,7 @@ from PIL import Image
from starlette.responses import FileResponse, JSONResponse
from modules import paths, shared, files_cache, errors, infotext, ui_symbols, ui_components, modelstats
from modules.logger import log
+from modules.json_helpers import writefile
allowed_dirs = []
@@ -838,7 +839,7 @@ def create_ui(container, button_parent, tabname, skip_indexing = False):
def fn_save_info(info):
fn = os.path.splitext(ui.last_item.filename)[0] + '.json'
- shared.writefile(info, fn, silent=True)
+ writefile(info, fn, silent=True)
log.debug(f'Network save info: item="{ui.last_item.name}" filename="{fn}"')
return info
@@ -851,7 +852,7 @@ def create_ui(container, button_parent, tabname, skip_indexing = False):
fn = os.path.splitext(ui.last_item.filename)[0] + '.json'
if hasattr(ui.last_item, 'type') and ui.last_item.type == 'Style':
info.update(**{ 'description': description, 'prompt': prompt, 'negative': negative, 'extra': extra, 'wildcards': wildcards })
- shared.writefile(info, fn, silent=True)
+ writefile(info, fn, silent=True)
log.debug(f'Network save style: item="{ui.last_item.name}" filename="{fn}"')
return info
@@ -1077,7 +1078,7 @@ def create_ui(container, button_parent, tabname, skip_indexing = False):
"negative": negative,
"extra": '',
}
- shared.writefile(item, fn, silent=True)
+ writefile(item, fn, silent=True)
if len(prompt) > 0:
log.debug(f'Networks type=style quicksave style: item="{name}" filename="{fn}" prompt="{prompt}"')
else:
diff --git a/modules/ui_javascript.py b/modules/ui_javascript.py
index 0eb88de7c..c789535df 100644
--- a/modules/ui_javascript.py
+++ b/modules/ui_javascript.py
@@ -82,17 +82,17 @@ def html_css(css: list[str]):
themecss = os.path.join(script_path, "javascript", f"{modules.shared.opts.gradio_theme}.css")
if os.path.exists(themecss):
head += stylesheet(themecss)
- modules.log.debug(f'UI theme: css="{themecss}" base="{css}" user="{usercss}"')
+ log.debug(f'UI theme: css="{themecss}" base="{css}" user="{usercss}"')
else:
- modules.log.error(f'UI theme: css="{themecss}" not found')
+ log.error(f'UI theme: css="{themecss}" not found')
elif modules.shared.opts.theme_type == 'Modern':
theme_folder = next((e.path for e in modules.extensions.extensions if e.name == 'sdnext-modernui'), None)
themecss = os.path.join(theme_folder or '', 'themes', f'{modules.shared.opts.gradio_theme}.css')
if os.path.exists(themecss):
head += stylesheet(themecss)
- modules.log.debug(f'UI theme: css="{themecss}" base="{css}" user="{usercss}"')
+ log.debug(f'UI theme: css="{themecss}" base="{css}" user="{usercss}"')
else:
- modules.log.error(f'UI theme: css="{themecss}" not found')
+ log.error(f'UI theme: css="{themecss}" not found')
if usercss is not None:
head += stylesheet(usercss)
return head
diff --git a/scripts/animatediff.py b/scripts/animatediff.py
index 93c5615b2..ee23187b6 100644
--- a/scripts/animatediff.py
+++ b/scripts/animatediff.py
@@ -263,7 +263,7 @@ class Script(scripts_manager.Script):
def after(self, p: processing.StableDiffusionProcessing, processed: processing.Processed, adapter_index, frames, lora_index, strength, latent_mode, video_type, duration, gif_loop, mp4_pad, mp4_interpolate, override_scheduler, fi_method, fi_iters, fi_order, fi_spatial, fi_temporal): # pylint: disable=arguments-differ, unused-argument
- from modules.images import save_video
+ from modules.video import save_video
if video_type != 'None':
log.debug(f'AnimateDiff video: type={video_type} duration={duration} loop={gif_loop} pad={mp4_pad} interpolate={mp4_interpolate}')
save_video(p, filename=None, images=processed.images, video_type=video_type, duration=duration, loop=gif_loop, pad=mp4_pad, interpolate=mp4_interpolate)
diff --git a/scripts/image2video.py b/scripts/image2video.py
index ef756381d..be9efed54 100644
--- a/scripts/image2video.py
+++ b/scripts/image2video.py
@@ -1,7 +1,7 @@
import torch
import gradio as gr
import diffusers
-from modules import scripts_manager, processing, shared, images, sd_models, devices
+from modules import scripts_manager, processing, shared, images, video, sd_models, devices
from modules.logger import log
@@ -110,5 +110,5 @@ class Script(scripts_manager.Script):
shared.sd_model = orig_pipeline
if video_type != 'None' and processed is not None:
- images.save_video(p, filename=None, images=processed.images, video_type=video_type, duration=duration, loop=gif_loop, pad=mp4_pad, interpolate=mp4_interpolate)
+ video.save_video(p, filename=None, images=processed.images, video_type=video_type, duration=duration, loop=gif_loop, pad=mp4_pad, interpolate=mp4_interpolate)
return processed
diff --git a/scripts/infiniteyou_ext.py b/scripts/infiniteyou_ext.py
index 2411aa267..1b551fa88 100644
--- a/scripts/infiniteyou_ext.py
+++ b/scripts/infiniteyou_ext.py
@@ -15,7 +15,7 @@ orig_pipeline, orig_prompt_attention = None, None
def verify_insightface():
from installer import installed, install_insightface
- if not installed('insightface', reload=False, quiet=True):
+ if not installed('insightface', quiet=True):
install_insightface()
diff --git a/scripts/mixture_tiling.py b/scripts/mixture_tiling.py
index d411e50ab..5b95e694b 100644
--- a/scripts/mixture_tiling.py
+++ b/scripts/mixture_tiling.py
@@ -14,7 +14,7 @@ def check_dependencies():
('ligo-segments', 'ligo-segments'),
]
for pkg in packages:
- if not installed(pkg[1], reload=True, quiet=True):
+ if not installed(pkg[1], quiet=True):
install(pkg[0], pkg[1], ignore=False)
try:
from ligo.segments import segment # pylint: disable=unused-import
diff --git a/scripts/poor_mans_outpainting.py b/scripts/poor_mans_outpainting.py
index 9d9d4fb22..6569ce290 100644
--- a/scripts/poor_mans_outpainting.py
+++ b/scripts/poor_mans_outpainting.py
@@ -4,6 +4,7 @@ from PIL import Image, ImageDraw
from modules import images, devices, scripts_manager
from modules.processing import get_processed, process_images
from modules.shared import opts, state, log
+from modules.images.grid import split_grid
class Script(scripts_manager.Script):
@@ -64,9 +65,9 @@ class Script(scripts_manager.Script):
mask.height - down - (mask_blur//2 if down > 0 else 0)
), fill="black")
devices.torch_gc()
- grid = images.split_grid(img, tile_w=p.width, tile_h=p.height, overlap=pixels)
- grid_mask = images.split_grid(mask, tile_w=p.width, tile_h=p.height, overlap=pixels)
- grid_latent_mask = images.split_grid(latent_mask, tile_w=p.width, tile_h=p.height, overlap=pixels)
+ grid = split_grid(img, tile_w=p.width, tile_h=p.height, overlap=pixels)
+ grid_mask = split_grid(mask, tile_w=p.width, tile_h=p.height, overlap=pixels)
+ grid_latent_mask = split_grid(latent_mask, tile_w=p.width, tile_h=p.height, overlap=pixels)
p.n_iter = 1
p.batch_size = 1
p.do_not_save_grid = True
diff --git a/scripts/postprocessing_video.py b/scripts/postprocessing_video.py
index 17ff802f8..84836abca 100644
--- a/scripts/postprocessing_video.py
+++ b/scripts/postprocessing_video.py
@@ -1,6 +1,5 @@
import gradio as gr
-import modules.images
-from modules import scripts_postprocessing
+from modules import video, scripts_postprocessing
class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing):
@@ -47,4 +46,4 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing):
filename = filename.strip() if filename is not None else ''
if video_type == 'None' or len(filename) == 0 or images is None or len(images) < 2:
return
- modules.images.save_video(p=None, filename=filename, images=images, video_type=video_type, duration=duration, loop=loop, pad=pad, interpolate=interpolate, scale=scale, change=change)
+ video.save_video(p=None, filename=filename, images=images, video_type=video_type, duration=duration, loop=loop, pad=pad, interpolate=interpolate, scale=scale, change=change)
diff --git a/scripts/pulid_ext.py b/scripts/pulid_ext.py
index 1eb52556d..62e606f47 100644
--- a/scripts/pulid_ext.py
+++ b/scripts/pulid_ext.py
@@ -30,7 +30,7 @@ class Script(scripts_manager.Script):
def dependencies(self):
from installer import installed, install, install_insightface
- if not installed('insightface', reload=False, quiet=True):
+ if not installed('insightface', quiet=True):
install_insightface()
if not installed('torchdiffeq'):
install('torchdiffeq')
diff --git a/scripts/sd_upscale.py b/scripts/sd_upscale.py
index ee944f54a..206efd7ea 100644
--- a/scripts/sd_upscale.py
+++ b/scripts/sd_upscale.py
@@ -5,6 +5,7 @@ from modules import processing, shared, images, devices, scripts_manager
from modules.processing import get_processed
from modules.shared import opts, state, log
from modules.image.util import flatten
+from modules.images.grid import split_grid
class Script(scripts_manager.Script):
@@ -48,7 +49,7 @@ class Script(scripts_manager.Script):
else:
img = init_img
devices.torch_gc()
- grid = images.split_grid(img, tile_w=init_img.width, tile_h=init_img.height, overlap=overlap)
+ grid = split_grid(img, tile_w=init_img.width, tile_h=init_img.height, overlap=overlap)
batch_size = p.batch_size
upscale_count = p.n_iter
p.n_iter = 1
diff --git a/scripts/stablevideodiffusion.py b/scripts/stablevideodiffusion.py
index 2ded2a9c2..6ea267256 100644
--- a/scripts/stablevideodiffusion.py
+++ b/scripts/stablevideodiffusion.py
@@ -5,7 +5,7 @@ Additional params for StableVideoDiffusion
import os
import torch
import gradio as gr
-from modules import scripts_manager, processing, shared, sd_models, images, modelloader
+from modules import scripts_manager, processing, shared, sd_models, images, modelloader, video
from modules.logger import log
@@ -121,5 +121,5 @@ class Script(scripts_manager.Script):
# run processing
processed = processing.process_images(p)
if video_type != 'None':
- images.save_video(p, filename=None, images=processed.images, video_type=video_type, duration=duration, loop=gif_loop, pad=mp4_pad, interpolate=mp4_interpolate)
+ video.save_video(p, filename=None, images=processed.images, video_type=video_type, duration=duration, loop=gif_loop, pad=mp4_pad, interpolate=mp4_interpolate)
return processed
diff --git a/scripts/text2video.py b/scripts/text2video.py
index 81c28799c..3605ff4a1 100644
--- a/scripts/text2video.py
+++ b/scripts/text2video.py
@@ -7,7 +7,7 @@ TODO text2video items:
"""
import gradio as gr
-from modules import scripts_manager, processing, shared, images, sd_models, modelloader
+from modules import scripts_manager, processing, shared, images, video, sd_models, modelloader
from modules.logger import log
@@ -92,5 +92,5 @@ class Script(scripts_manager.Script):
processed = processing.process_images(p)
if video_type != 'None':
- images.save_video(p, filename=None, images=processed.images, video_type=video_type, duration=duration, loop=gif_loop, pad=mp4_pad, interpolate=mp4_interpolate)
+ video.save_video(p, filename=None, images=processed.images, video_type=video_type, duration=duration, loop=gif_loop, pad=mp4_pad, interpolate=mp4_interpolate)
return processed
diff --git a/scripts/xyz/xyz_grid_classes.py b/scripts/xyz/xyz_grid_classes.py
index a6c6678b7..ca78569ad 100644
--- a/scripts/xyz/xyz_grid_classes.py
+++ b/scripts/xyz/xyz_grid_classes.py
@@ -248,8 +248,8 @@ axis_options = [
AxisOption("[Postprocess] Context", str, apply_context, choices=lambda: ["Add with forward", "Remove with forward", "Add with backward", "Remove with backward"]),
AxisOption("[Postprocess] Detailer", bool, apply_detailer, fmt=format_bool, choices=lambda: [False, True]),
AxisOption("[Postprocess] Detailer strength", str, apply_field("detailer_strength")),
- AxisOption("[Quant] SDNQ quant mode", str, apply_sdnq_quant, cost=0.9, fmt=format_value_add_label, choices=lambda: ['none'] + sorted(shared.sdnq_quant_modes)),
- AxisOption("[Quant] SDNQ quant mode TE", str, apply_sdnq_quant_te, cost=0.9, fmt=format_value_add_label, choices=lambda: ['none'] + sorted(shared.sdnq_quant_modes)),
+ AxisOption("[Quant] SDNQ quant mode", str, apply_sdnq_quant, cost=0.9, fmt=format_value_add_label, choices=lambda: ['none'] + sorted(shared_items.sdnq_quant_modes)),
+ AxisOption("[Quant] SDNQ quant mode TE", str, apply_sdnq_quant_te, cost=0.9, fmt=format_value_add_label, choices=lambda: ['none'] + sorted(shared_items.sdnq_quant_modes)),
AxisOption("[HDR] Mode", int, apply_field("hdr_mode")),
AxisOption("[HDR] Brightness", float, apply_field("hdr_brightness")),
AxisOption("[HDR] Color", float, apply_field("hdr_color")),
diff --git a/scripts/xyz/xyz_grid_draw.py b/scripts/xyz/xyz_grid_draw.py
index 0c4efa705..ed88b656f 100644
--- a/scripts/xyz/xyz_grid_draw.py
+++ b/scripts/xyz/xyz_grid_draw.py
@@ -1,15 +1,16 @@
import time
from copy import copy
from PIL import Image
+from modues.images.grid import GridAnnotation
from modules import shared, images, processing
from modules.logger import log
from modules.image.util import draw_text
def draw_xyz_grid(p, xs, ys, zs, x_labels, y_labels, z_labels, cell, draw_legend, include_lone_images, include_sub_grids, first_axes_processed, second_axes_processed, margin_size, no_grid: False, include_time: False, include_text: False): # pylint: disable=unused-argument
- x_texts = [[images.GridAnnotation(x)] for x in x_labels]
- y_texts = [[images.GridAnnotation(y)] for y in y_labels]
- z_texts = [[images.GridAnnotation(z)] for z in z_labels]
+ x_texts = [[GridAnnotation(x)] for x in x_labels]
+ y_texts = [[GridAnnotation(y)] for y in y_labels]
+ z_texts = [[GridAnnotation(z)] for z in z_labels]
list_size = (len(xs) * len(ys) * len(zs))
processed_result = None
diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py
index 50dd4be6e..c8681229a 100644
--- a/scripts/xyz_grid.py
+++ b/scripts/xyz_grid.py
@@ -12,7 +12,7 @@ from scripts.xyz.xyz_grid_shared import str_permutations, list_to_csv_string, re
from scripts.xyz.xyz_grid_classes import axis_options, AxisOption, SharedSettingsStackHelper # pylint: disable=no-name-in-module
from scripts.xyz.xyz_grid_draw import draw_xyz_grid # pylint: disable=no-name-in-module
from scripts.xyz.xyz_grid_shared import apply_field, apply_task_args, apply_setting, apply_prompt, apply_order, apply_sampler, apply_hr_sampler_name, confirm_samplers, apply_checkpoint, apply_refiner, apply_unet, apply_clip_skip, apply_vae, list_lora, apply_lora, apply_lora_strength, apply_te, apply_styles, apply_upscaler, apply_context, apply_detailer, apply_override, apply_processing, apply_options, apply_seed, format_value_add_label, format_value, format_value_join_list, do_nothing, format_nothing # pylint: disable=no-name-in-module, unused-import
-from modules import shared, errors, scripts_manager, images, processing
+from modules import shared, errors, scripts_manager, images, video, processing
from modules.ui_components import ToolButton
from modules.ui_sections import create_video_inputs
import modules.ui_symbols as symbols
@@ -412,7 +412,7 @@ class Script(scripts_manager.Script):
debug(f'XYZ grid remove subgrids: total={processed.images}')
if create_video and video_type != 'None' and not shared.state.interrupted:
- images.save_video(p, filename=None, images=have_images, video_type=video_type, duration=video_duration, loop=video_loop, pad=video_pad, interpolate=video_interpolate)
+ video.save_video(p, filename=None, images=have_images, video_type=video_type, duration=video_duration, loop=video_loop, pad=video_pad, interpolate=video_interpolate)
shared.state.end(jobid)
return processed
diff --git a/scripts/xyz_grid_on.py b/scripts/xyz_grid_on.py
index 24851a7bb..6a9af5b00 100644
--- a/scripts/xyz_grid_on.py
+++ b/scripts/xyz_grid_on.py
@@ -11,7 +11,7 @@ import gradio as gr
from scripts.xyz.xyz_grid_shared import str_permutations, list_to_csv_string, restore_comma, re_range, re_plain_comma # pylint: disable=no-name-in-module
from scripts.xyz.xyz_grid_classes import axis_options, AxisOption, SharedSettingsStackHelper # pylint: disable=no-name-in-module
from scripts.xyz.xyz_grid_draw import draw_xyz_grid # pylint: disable=no-name-in-module
-from modules import shared, errors, scripts_manager, images, processing
+from modules import shared, errors, scripts_manager, images, video, processing
from modules.ui_components import ToolButton
from modules.ui_sections import create_video_inputs
import modules.ui_symbols as symbols
@@ -440,7 +440,7 @@ class Script(scripts_manager.Script):
debug(f'XYZ grid remove subgrids: total={processed.images}')
if create_video and video_type != 'None' and not shared.state.interrupted:
- images.save_video(p, filename=None, images=have_images, video_type=video_type, duration=video_duration, loop=video_loop, pad=video_pad, interpolate=video_interpolate)
+ video.save_video(p, filename=None, images=have_images, video_type=video_type, duration=video_duration, loop=video_loop, pad=video_pad, interpolate=video_interpolate)
p.do_not_save_grid = True
p.do_not_save_samples = True
diff --git a/webui.py b/webui.py
index 4097f62bf..3d25bc047 100644
--- a/webui.py
+++ b/webui.py
@@ -74,6 +74,7 @@ fastapi_args = {
def initialize():
log.debug('Initializing: modules')
+ from concurrent.futures import ThreadPoolExecutor, as_completed
modules.sd_checkpoint.init_metadata()
modules.hashes.init_cache()
@@ -81,22 +82,32 @@ def initialize():
modules.sd_samplers.list_samplers()
timer.startup.record("samplers")
- modules.sd_vae.refresh_vae_list()
- timer.startup.record("vae")
+ # run independent filesystem scans in parallel
+ def _scan_vae():
+ modules.sd_vae.refresh_vae_list()
+ def _scan_unet():
+ modules.sd_unet.refresh_unet_list()
+ def _scan_te():
+ modules.model_te.refresh_te_list()
+ def _scan_models():
+ modules.modelloader.cleanup_models()
+ modules.sd_checkpoint.setup_model()
+ def _scan_lora():
+ from modules.lora import lora_load
+ lora_load.list_available_networks()
+ def _scan_upscalers():
+ modules.modelloader.load_upscalers()
- modules.sd_unet.refresh_unet_list()
- timer.startup.record("unet")
-
- modules.model_te.refresh_te_list()
- timer.startup.record("te")
-
- modules.modelloader.cleanup_models()
- modules.sd_checkpoint.setup_model()
- timer.startup.record("models")
-
- from modules.lora import lora_load
- lora_load.list_available_networks()
- timer.startup.record("lora")
+ scans = [_scan_vae, _scan_unet, _scan_te, _scan_models, _scan_lora, _scan_upscalers]
+ with ThreadPoolExecutor(max_workers=len(scans), thread_name_prefix='sdnext-scan') as pool:
+ futures = {pool.submit(fn): fn.__name__ for fn in scans}
+ for future in as_completed(futures):
+ name = futures[future]
+ try:
+ future.result()
+ except Exception as e:
+ log.error(f'Scan error: {name} {e}')
+ timer.startup.record("scans")
shared.prompt_styles.reload()
timer.startup.record("styles")
@@ -115,9 +126,6 @@ def initialize():
timer.startup.records["extensions"] = t_total # scripts can reset the time
log.debug(f'Extensions init time: {t_timer.summary()}')
- modules.modelloader.load_upscalers()
- timer.startup.record("upscalers")
-
modules.ui_extra_networks.initialize()
modules.ui_extra_networks.register_pages()
modules.extra_networks.initialize()
@@ -128,6 +136,7 @@ def initialize():
hf_init()
hf_check_cache()
+
if shared.cmd_opts.tls_keyfile is not None and shared.cmd_opts.tls_certfile is not None:
try:
if not os.path.exists(shared.cmd_opts.tls_keyfile):
@@ -374,7 +383,6 @@ def webui(restart=False):
start_common()
app = start_ui()
modules.script_callbacks.after_ui_callback()
- modules.sd_models.write_metadata()
load_model()
mount_subpath(app)