stable-diffusion-webui/modules/scripts.py
2022-10-09 11:16:38 +03:00

223 lines
7.5 KiB
Python

import os
from pydoc import visiblename
import sys
import traceback
import modules.ui as ui
import gradio as gr
from modules.processing import StableDiffusionProcessing
from modules import shared
class Script:
filename = None
args_from = None
args_to = None
# The title of the script. This is what will be displayed in the dropdown menu.
def title(self):
raise NotImplementedError()
# How the script is displayed in the UI. See https://gradio.app/docs/#components
# for the different UI components you can use and how to create them.
# Most UI components can return a value, such as a boolean for a checkbox.
# The returned values are passed to the run method as parameters.
def ui(self, is_img2img):
pass
# Determines when the script should be shown in the dropdown menu via the
# returned value. As an example:
# is_img2img is True if the current tab is img2img, and False if it is txt2img.
# Thus, return is_img2img to only show the script on the img2img tab.
def show(self, is_img2img):
return True
# Called when the ui for this script has been shown.
# Useful for hiding some controls, since the scripts module sets visibility to
# everything to true. The parameters will be the parameters returned by the ui method
# The return value should be gradio updates, similar to what you would return
# from a Gradio event handler.
def on_show(self, *args):
return [ui.gr_show(True)] * len(args)
# This is where the additional processing is implemented. The parameters include
# self, the model object "p" (a StableDiffusionProcessing class, see
# processing.py), and the parameters returned by the ui method.
# Custom functions can be defined here, and additional libraries can be imported
# to be used in processing. The return value should be a Processed object, which is
# what is returned by the process_images method.
def run(self, *args):
raise NotImplementedError()
# The description method is currently unused.
# To add a description that appears when hovering over the title, amend the "titles"
# dict in script.js to include the script title (returned by title) as a key, and
# your description as the value.
def describe(self):
return ""
scripts_data = []
def load_scripts(basedir):
if not os.path.exists(basedir):
return
for filename in sorted(os.listdir(basedir)):
path = os.path.join(basedir, filename)
if not os.path.isfile(path):
continue
try:
with open(path, "r", encoding="utf8") as file:
text = file.read()
from types import ModuleType
compiled = compile(text, path, 'exec')
module = ModuleType(filename)
exec(compiled, module.__dict__)
for key, script_class in module.__dict__.items():
if type(script_class) == type and issubclass(script_class, Script):
scripts_data.append((script_class, path))
except Exception:
print(f"Error loading script: {filename}", file=sys.stderr)
print(traceback.format_exc(), file=sys.stderr)
def wrap_call(func, filename, funcname, *args, default=None, **kwargs):
try:
res = func(*args, **kwargs)
return res
except Exception:
print(f"Error calling: {filename}/{funcname}", file=sys.stderr)
print(traceback.format_exc(), file=sys.stderr)
return default
class ScriptRunner:
def __init__(self):
self.scripts = []
def setup_ui(self, is_img2img):
for script_class, path in scripts_data:
script = script_class()
script.filename = path
if not script.show(is_img2img):
continue
self.scripts.append(script)
titles = [wrap_call(script.title, script.filename, "title") or f"{script.filename} [error]" for script in self.scripts]
dropdown = gr.Dropdown(label="Script", choices=["None"] + titles, value="None", type="index")
inputs = [dropdown]
for script in self.scripts:
script.args_from = len(inputs)
script.args_to = len(inputs)
controls = wrap_call(script.ui, script.filename, "ui", is_img2img)
if controls is None:
continue
for control in controls:
control.custom_script_source = os.path.basename(script.filename)
control.visible = False
inputs += controls
script.args_to = len(inputs)
def select_script(*args):
script_index = args[0]
on_show_updates = []
if 0 < script_index <= len(self.scripts):
script = self.scripts[script_index-1]
args_from = script.args_from
args_to = script.args_to
script_args = args[args_from:args_to]
on_show_updates = wrap_call(script.on_show, script.filename, "on_show", *script_args)
else:
args_from = 0
args_to = 0
ret = [ ui.gr_show(True)] # always show the dropdown
for i in range(1, len(inputs)):
if (args_from <= i < args_to):
ret.append( on_show_updates[i - args_from] )
else:
ret.append(ui.gr_show(False))
return ret
# return [ui.gr_show(True if (i == 0) else on_show_updates[i - args_from] if args_from <= i < args_to else False) for i in range(len(inputs))]
dropdown.change(
fn=select_script,
inputs=inputs,
outputs=inputs
)
return inputs
def run(self, p: StableDiffusionProcessing, *args):
script_index = args[0]
if script_index == 0:
return None
script = self.scripts[script_index-1]
if script is None:
return None
script_args = args[script.args_from:script.args_to]
processed = script.run(p, *script_args)
shared.total_tqdm.clear()
return processed
def reload_sources(self):
for si, script in list(enumerate(self.scripts)):
with open(script.filename, "r", encoding="utf8") as file:
args_from = script.args_from
args_to = script.args_to
filename = script.filename
text = file.read()
from types import ModuleType
compiled = compile(text, filename, 'exec')
module = ModuleType(script.filename)
exec(compiled, module.__dict__)
for key, script_class in module.__dict__.items():
if type(script_class) == type and issubclass(script_class, Script):
self.scripts[si] = script_class()
self.scripts[si].filename = filename
self.scripts[si].args_from = args_from
self.scripts[si].args_to = args_to
scripts_txt2img = ScriptRunner()
scripts_img2img = ScriptRunner()
def reload_script_body_only():
scripts_txt2img.reload_sources()
scripts_img2img.reload_sources()
def reload_scripts(basedir):
global scripts_txt2img, scripts_img2img
scripts_data.clear()
load_scripts(basedir)
scripts_txt2img = ScriptRunner()
scripts_img2img = ScriptRunner()