stable-diffusion-webui
984 строки · 38.0 Кб
1import os2import re3import sys4import inspect5from collections import namedtuple6from dataclasses import dataclass7
8import gradio as gr9
10from modules import shared, paths, script_callbacks, extensions, script_loading, scripts_postprocessing, errors, timer11
12AlwaysVisible = object()13
14class MaskBlendArgs:15def __init__(self, current_latent, nmask, init_latent, mask, blended_latent, denoiser=None, sigma=None):16self.current_latent = current_latent17self.nmask = nmask18self.init_latent = init_latent19self.mask = mask20self.blended_latent = blended_latent21
22self.denoiser = denoiser23self.is_final_blend = denoiser is None24self.sigma = sigma25
26class PostSampleArgs:27def __init__(self, samples):28self.samples = samples29
30class PostprocessImageArgs:31def __init__(self, image):32self.image = image33
34class PostProcessMaskOverlayArgs:35def __init__(self, index, mask_for_overlay, overlay_image):36self.index = index37self.mask_for_overlay = mask_for_overlay38self.overlay_image = overlay_image39
40class PostprocessBatchListArgs:41def __init__(self, images):42self.images = images43
44
45@dataclass
46class OnComponent:47component: gr.blocks.Block48
49
50class Script:51name = None52"""script's internal name derived from title"""53
54section = None55"""name of UI section that the script's controls will be placed into"""56
57filename = None58args_from = None59args_to = None60alwayson = False61
62is_txt2img = False63is_img2img = False64tabname = None65
66group = None67"""A gr.Group component that has all script's UI inside it."""68
69create_group = True70"""If False, for alwayson scripts, a group component will not be created."""71
72infotext_fields = None73"""if set in ui(), this is a list of pairs of gradio component + text; the text will be used when74parsing infotext to set the value for the component; see ui.py's txt2img_paste_fields for an example
75"""
76
77paste_field_names = None78"""if set in ui(), this is a list of names of infotext fields; the fields will be sent through the79various "Send to <X>" buttons when clicked
80"""
81
82api_info = None83"""Generated value of type modules.api.models.ScriptInfo with information about the script for API"""84
85on_before_component_elem_id = None86"""list of callbacks to be called before a component with an elem_id is created"""87
88on_after_component_elem_id = None89"""list of callbacks to be called after a component with an elem_id is created"""90
91setup_for_ui_only = False92"""If true, the script setup will only be run in Gradio UI, not in API"""93
94controls = None95"""A list of controls retured by the ui()."""96
97def title(self):98"""this function should return the title of the script. This is what will be displayed in the dropdown menu."""99
100raise NotImplementedError()101
102def ui(self, is_img2img):103"""this function should create gradio UI elements. See https://gradio.app/docs/#components104The return value should be an array of all components that are used in processing.
105Values of those returned components will be passed to run() and process() functions.
106"""
107
108pass109
110def show(self, is_img2img):111"""112is_img2img is True if this function is called for the img2img interface, and Fasle otherwise
113
114This function should return:
115- False if the script should not be shown in UI at all
116- True if the script should be shown in UI if it's selected in the scripts dropdown
117- script.AlwaysVisible if the script should be shown in UI at all times
118"""
119
120return True121
122def run(self, p, *args):123"""124This function is called if the script has been selected in the script dropdown.
125It must do all processing and return the Processed object with results, same as
126one returned by processing.process_images.
127
128Usually the processing is done by calling the processing.process_images function.
129
130args contains all values returned by components from ui()
131"""
132
133pass134
135def setup(self, p, *args):136"""For AlwaysVisible scripts, this function is called when the processing object is set up, before any processing starts.137args contains all values returned by components from ui().
138"""
139pass140
141
142def before_process(self, p, *args):143"""144This function is called very early during processing begins for AlwaysVisible scripts.
145You can modify the processing object (p) here, inject hooks, etc.
146args contains all values returned by components from ui()
147"""
148
149pass150
151def process(self, p, *args):152"""153This function is called before processing begins for AlwaysVisible scripts.
154You can modify the processing object (p) here, inject hooks, etc.
155args contains all values returned by components from ui()
156"""
157
158pass159
160def before_process_batch(self, p, *args, **kwargs):161"""162Called before extra networks are parsed from the prompt, so you can add
163new extra network keywords to the prompt with this callback.
164
165**kwargs will have those items:
166- batch_number - index of current batch, from 0 to number of batches-1
167- prompts - list of prompts for current batch; you can change contents of this list but changing the number of entries will likely break things
168- seeds - list of seeds for current batch
169- subseeds - list of subseeds for current batch
170"""
171
172pass173
174def after_extra_networks_activate(self, p, *args, **kwargs):175"""176Called after extra networks activation, before conds calculation
177allow modification of the network after extra networks activation been applied
178won't be call if p.disable_extra_networks
179
180**kwargs will have those items:
181- batch_number - index of current batch, from 0 to number of batches-1
182- prompts - list of prompts for current batch; you can change contents of this list but changing the number of entries will likely break things
183- seeds - list of seeds for current batch
184- subseeds - list of subseeds for current batch
185- extra_network_data - list of ExtraNetworkParams for current stage
186"""
187pass188
189def process_batch(self, p, *args, **kwargs):190"""191Same as process(), but called for every batch.
192
193**kwargs will have those items:
194- batch_number - index of current batch, from 0 to number of batches-1
195- prompts - list of prompts for current batch; you can change contents of this list but changing the number of entries will likely break things
196- seeds - list of seeds for current batch
197- subseeds - list of subseeds for current batch
198"""
199
200pass201
202def postprocess_batch(self, p, *args, **kwargs):203"""204Same as process_batch(), but called for every batch after it has been generated.
205
206**kwargs will have same items as process_batch, and also:
207- batch_number - index of current batch, from 0 to number of batches-1
208- images - torch tensor with all generated images, with values ranging from 0 to 1;
209"""
210
211pass212
213def postprocess_batch_list(self, p, pp: PostprocessBatchListArgs, *args, **kwargs):214"""215Same as postprocess_batch(), but receives batch images as a list of 3D tensors instead of a 4D tensor.
216This is useful when you want to update the entire batch instead of individual images.
217
218You can modify the postprocessing object (pp) to update the images in the batch, remove images, add images, etc.
219If the number of images is different from the batch size when returning,
220then the script has the responsibility to also update the following attributes in the processing object (p):
221- p.prompts
222- p.negative_prompts
223- p.seeds
224- p.subseeds
225
226**kwargs will have same items as process_batch, and also:
227- batch_number - index of current batch, from 0 to number of batches-1
228"""
229
230pass231
232def on_mask_blend(self, p, mba: MaskBlendArgs, *args):233"""234Called in inpainting mode when the original content is blended with the inpainted content.
235This is called at every step in the denoising process and once at the end.
236If is_final_blend is true, this is called for the final blending stage.
237Otherwise, denoiser and sigma are defined and may be used to inform the procedure.
238"""
239
240pass241
242def post_sample(self, p, ps: PostSampleArgs, *args):243"""244Called after the samples have been generated,
245but before they have been decoded by the VAE, if applicable.
246Check getattr(samples, 'already_decoded', False) to test if the images are decoded.
247"""
248
249pass250
251def postprocess_image(self, p, pp: PostprocessImageArgs, *args):252"""253Called for every image after it has been generated.
254"""
255
256pass257
258def postprocess_maskoverlay(self, p, ppmo: PostProcessMaskOverlayArgs, *args):259"""260Called for every image after it has been generated.
261"""
262
263pass264
265def postprocess_image_after_composite(self, p, pp: PostprocessImageArgs, *args):266"""267Called for every image after it has been generated.
268Same as postprocess_image but after inpaint_full_res composite
269So that it operates on the full image instead of the inpaint_full_res crop region.
270"""
271
272pass273
274def postprocess(self, p, processed, *args):275"""276This function is called after processing ends for AlwaysVisible scripts.
277args contains all values returned by components from ui()
278"""
279
280pass281
282def before_component(self, component, **kwargs):283"""284Called before a component is created.
285Use elem_id/label fields of kwargs to figure out which component it is.
286This can be useful to inject your own components somewhere in the middle of vanilla UI.
287You can return created components in the ui() function to add them to the list of arguments for your processing functions
288"""
289
290pass291
292def after_component(self, component, **kwargs):293"""294Called after a component is created. Same as above.
295"""
296
297pass298
299def on_before_component(self, callback, *, elem_id):300"""301Calls callback before a component is created. The callback function is called with a single argument of type OnComponent.
302
303May be called in show() or ui() - but it may be too late in latter as some components may already be created.
304
305This function is an alternative to before_component in that it also cllows to run before a component is created, but
306it doesn't require to be called for every created component - just for the one you need.
307"""
308if self.on_before_component_elem_id is None:309self.on_before_component_elem_id = []310
311self.on_before_component_elem_id.append((elem_id, callback))312
313def on_after_component(self, callback, *, elem_id):314"""315Calls callback after a component is created. The callback function is called with a single argument of type OnComponent.
316"""
317if self.on_after_component_elem_id is None:318self.on_after_component_elem_id = []319
320self.on_after_component_elem_id.append((elem_id, callback))321
322def describe(self):323"""unused"""324return ""325
326def elem_id(self, item_id):327"""helper function to generate id for a HTML element, constructs final id out of script name, tab and user-supplied item_id"""328
329need_tabname = self.show(True) == self.show(False)330tabkind = 'img2img' if self.is_img2img else 'txt2img'331tabname = f"{tabkind}_" if need_tabname else ""332title = re.sub(r'[^a-z_0-9]', '', re.sub(r'\s', '_', self.title().lower()))333
334return f'script_{tabname}{title}_{item_id}'335
336def before_hr(self, p, *args):337"""338This function is called before hires fix start.
339"""
340pass341
342
343class ScriptBuiltinUI(Script):344setup_for_ui_only = True345
346def elem_id(self, item_id):347"""helper function to generate id for a HTML element, constructs final id out of tab and user-supplied item_id"""348
349need_tabname = self.show(True) == self.show(False)350tabname = ('img2img' if self.is_img2img else 'txt2img') + "_" if need_tabname else ""351
352return f'{tabname}{item_id}'353
354
355current_basedir = paths.script_path356
357
358def basedir():359"""returns the base directory for the current script. For scripts in the main scripts directory,360this is the main directory (where webui.py resides), and for scripts in extensions directory
361(ie extensions/aesthetic/script/aesthetic.py), this is extension's directory (extensions/aesthetic)
362"""
363return current_basedir364
365
366ScriptFile = namedtuple("ScriptFile", ["basedir", "filename", "path"])367
368scripts_data = []369postprocessing_scripts_data = []370ScriptClassData = namedtuple("ScriptClassData", ["script_class", "path", "basedir", "module"])371
372def topological_sort(dependencies):373"""Accepts a dictionary mapping name to its dependencies, returns a list of names ordered according to dependencies.374Ignores errors relating to missing dependeencies or circular dependencies
375"""
376
377visited = {}378result = []379
380def inner(name):381visited[name] = True382
383for dep in dependencies.get(name, []):384if dep in dependencies and dep not in visited:385inner(dep)386
387result.append(name)388
389for depname in dependencies:390if depname not in visited:391inner(depname)392
393return result394
395
396@dataclass
397class ScriptWithDependencies:398script_canonical_name: str399file: ScriptFile400requires: list401load_before: list402load_after: list403
404
405def list_scripts(scriptdirname, extension, *, include_extensions=True):406scripts = {}407
408loaded_extensions = {ext.canonical_name: ext for ext in extensions.active()}409loaded_extensions_scripts = {ext.canonical_name: [] for ext in extensions.active()}410
411# build script dependency map412root_script_basedir = os.path.join(paths.script_path, scriptdirname)413if os.path.exists(root_script_basedir):414for filename in sorted(os.listdir(root_script_basedir)):415if not os.path.isfile(os.path.join(root_script_basedir, filename)):416continue417
418if os.path.splitext(filename)[1].lower() != extension:419continue420
421script_file = ScriptFile(paths.script_path, filename, os.path.join(root_script_basedir, filename))422scripts[filename] = ScriptWithDependencies(filename, script_file, [], [], [])423
424if include_extensions:425for ext in extensions.active():426extension_scripts_list = ext.list_files(scriptdirname, extension)427for extension_script in extension_scripts_list:428if not os.path.isfile(extension_script.path):429continue430
431script_canonical_name = ("builtin/" if ext.is_builtin else "") + ext.canonical_name + "/" + extension_script.filename432relative_path = scriptdirname + "/" + extension_script.filename433
434script = ScriptWithDependencies(435script_canonical_name=script_canonical_name,436file=extension_script,437requires=ext.metadata.get_script_requirements("Requires", relative_path, scriptdirname),438load_before=ext.metadata.get_script_requirements("Before", relative_path, scriptdirname),439load_after=ext.metadata.get_script_requirements("After", relative_path, scriptdirname),440)441
442scripts[script_canonical_name] = script443loaded_extensions_scripts[ext.canonical_name].append(script)444
445for script_canonical_name, script in scripts.items():446# load before requires inverse dependency447# in this case, append the script name into the load_after list of the specified script448for load_before in script.load_before:449# if this requires an individual script to be loaded before450other_script = scripts.get(load_before)451if other_script:452other_script.load_after.append(script_canonical_name)453
454# if this requires an extension455other_extension_scripts = loaded_extensions_scripts.get(load_before)456if other_extension_scripts:457for other_script in other_extension_scripts:458other_script.load_after.append(script_canonical_name)459
460# if After mentions an extension, remove it and instead add all of its scripts461for load_after in list(script.load_after):462if load_after not in scripts and load_after in loaded_extensions_scripts:463script.load_after.remove(load_after)464
465for other_script in loaded_extensions_scripts.get(load_after, []):466script.load_after.append(other_script.script_canonical_name)467
468dependencies = {}469
470for script_canonical_name, script in scripts.items():471for required_script in script.requires:472if required_script not in scripts and required_script not in loaded_extensions:473errors.report(f'Script "{script_canonical_name}" requires "{required_script}" to be loaded, but it is not.', exc_info=False)474
475dependencies[script_canonical_name] = script.load_after476
477ordered_scripts = topological_sort(dependencies)478scripts_list = [scripts[script_canonical_name].file for script_canonical_name in ordered_scripts]479
480return scripts_list481
482
483def list_files_with_name(filename):484res = []485
486dirs = [paths.script_path] + [ext.path for ext in extensions.active()]487
488for dirpath in dirs:489if not os.path.isdir(dirpath):490continue491
492path = os.path.join(dirpath, filename)493if os.path.isfile(path):494res.append(path)495
496return res497
498
499def load_scripts():500global current_basedir501scripts_data.clear()502postprocessing_scripts_data.clear()503script_callbacks.clear_callbacks()504
505scripts_list = list_scripts("scripts", ".py") + list_scripts("modules/processing_scripts", ".py", include_extensions=False)506
507syspath = sys.path508
509def register_scripts_from_module(module):510for script_class in module.__dict__.values():511if not inspect.isclass(script_class):512continue513
514if issubclass(script_class, Script):515scripts_data.append(ScriptClassData(script_class, scriptfile.path, scriptfile.basedir, module))516elif issubclass(script_class, scripts_postprocessing.ScriptPostprocessing):517postprocessing_scripts_data.append(ScriptClassData(script_class, scriptfile.path, scriptfile.basedir, module))518
519# here the scripts_list is already ordered520# processing_script is not considered though521for scriptfile in scripts_list:522try:523if scriptfile.basedir != paths.script_path:524sys.path = [scriptfile.basedir] + sys.path525current_basedir = scriptfile.basedir526
527script_module = script_loading.load_module(scriptfile.path)528register_scripts_from_module(script_module)529
530except Exception:531errors.report(f"Error loading script: {scriptfile.filename}", exc_info=True)532
533finally:534sys.path = syspath535current_basedir = paths.script_path536timer.startup_timer.record(scriptfile.filename)537
538global scripts_txt2img, scripts_img2img, scripts_postproc539
540scripts_txt2img = ScriptRunner()541scripts_img2img = ScriptRunner()542scripts_postproc = scripts_postprocessing.ScriptPostprocessingRunner()543
544
545def wrap_call(func, filename, funcname, *args, default=None, **kwargs):546try:547return func(*args, **kwargs)548except Exception:549errors.report(f"Error calling: {filename}/{funcname}", exc_info=True)550
551return default552
553
554class ScriptRunner:555def __init__(self):556self.scripts = []557self.selectable_scripts = []558self.alwayson_scripts = []559self.titles = []560self.title_map = {}561self.infotext_fields = []562self.paste_field_names = []563self.inputs = [None]564
565self.on_before_component_elem_id = {}566"""dict of callbacks to be called before an element is created; key=elem_id, value=list of callbacks"""567
568self.on_after_component_elem_id = {}569"""dict of callbacks to be called after an element is created; key=elem_id, value=list of callbacks"""570
571def initialize_scripts(self, is_img2img):572from modules import scripts_auto_postprocessing573
574self.scripts.clear()575self.alwayson_scripts.clear()576self.selectable_scripts.clear()577
578auto_processing_scripts = scripts_auto_postprocessing.create_auto_preprocessing_script_data()579
580for script_data in auto_processing_scripts + scripts_data:581try:582script = script_data.script_class()583except Exception:584errors.report(f"Error # failed to initialize Script {script_data.module}: ", exc_info=True)585continue586
587script.filename = script_data.path588script.is_txt2img = not is_img2img589script.is_img2img = is_img2img590script.tabname = "img2img" if is_img2img else "txt2img"591
592visibility = script.show(script.is_img2img)593
594if visibility == AlwaysVisible:595self.scripts.append(script)596self.alwayson_scripts.append(script)597script.alwayson = True598
599elif visibility:600self.scripts.append(script)601self.selectable_scripts.append(script)602
603self.apply_on_before_component_callbacks()604
605def apply_on_before_component_callbacks(self):606for script in self.scripts:607on_before = script.on_before_component_elem_id or []608on_after = script.on_after_component_elem_id or []609
610for elem_id, callback in on_before:611if elem_id not in self.on_before_component_elem_id:612self.on_before_component_elem_id[elem_id] = []613
614self.on_before_component_elem_id[elem_id].append((callback, script))615
616for elem_id, callback in on_after:617if elem_id not in self.on_after_component_elem_id:618self.on_after_component_elem_id[elem_id] = []619
620self.on_after_component_elem_id[elem_id].append((callback, script))621
622on_before.clear()623on_after.clear()624
625def create_script_ui(self, script):626
627script.args_from = len(self.inputs)628script.args_to = len(self.inputs)629
630try:631self.create_script_ui_inner(script)632except Exception:633errors.report(f"Error creating UI for {script.name}: ", exc_info=True)634
635def create_script_ui_inner(self, script):636import modules.api.models as api_models637
638controls = wrap_call(script.ui, script.filename, "ui", script.is_img2img)639script.controls = controls640
641if controls is None:642return643
644script.name = wrap_call(script.title, script.filename, "title", default=script.filename).lower()645
646api_args = []647
648for control in controls:649control.custom_script_source = os.path.basename(script.filename)650
651arg_info = api_models.ScriptArg(label=control.label or "")652
653for field in ("value", "minimum", "maximum", "step"):654v = getattr(control, field, None)655if v is not None:656setattr(arg_info, field, v)657
658choices = getattr(control, 'choices', None) # as of gradio 3.41, some items in choices are strings, and some are tuples where the first elem is the string659if choices is not None:660arg_info.choices = [x[0] if isinstance(x, tuple) else x for x in choices]661
662api_args.append(arg_info)663
664script.api_info = api_models.ScriptInfo(665name=script.name,666is_img2img=script.is_img2img,667is_alwayson=script.alwayson,668args=api_args,669)670
671if script.infotext_fields is not None:672self.infotext_fields += script.infotext_fields673
674if script.paste_field_names is not None:675self.paste_field_names += script.paste_field_names676
677self.inputs += controls678script.args_to = len(self.inputs)679
680def setup_ui_for_section(self, section, scriptlist=None):681if scriptlist is None:682scriptlist = self.alwayson_scripts683
684for script in scriptlist:685if script.alwayson and script.section != section:686continue687
688if script.create_group:689with gr.Group(visible=script.alwayson) as group:690self.create_script_ui(script)691
692script.group = group693else:694self.create_script_ui(script)695
696def prepare_ui(self):697self.inputs = [None]698
699def setup_ui(self):700all_titles = [wrap_call(script.title, script.filename, "title") or script.filename for script in self.scripts]701self.title_map = {title.lower(): script for title, script in zip(all_titles, self.scripts)}702self.titles = [wrap_call(script.title, script.filename, "title") or f"{script.filename} [error]" for script in self.selectable_scripts]703
704self.setup_ui_for_section(None)705
706dropdown = gr.Dropdown(label="Script", elem_id="script_list", choices=["None"] + self.titles, value="None", type="index")707self.inputs[0] = dropdown708
709self.setup_ui_for_section(None, self.selectable_scripts)710
711def select_script(script_index):712if script_index is None:713script_index = 0714selected_script = self.selectable_scripts[script_index - 1] if script_index>0 else None715
716return [gr.update(visible=selected_script == s) for s in self.selectable_scripts]717
718def init_field(title):719"""called when an initial value is set from ui-config.json to show script's UI components"""720
721if title == 'None':722return723
724script_index = self.titles.index(title)725self.selectable_scripts[script_index].group.visible = True726
727dropdown.init_field = init_field728
729dropdown.change(730fn=select_script,731inputs=[dropdown],732outputs=[script.group for script in self.selectable_scripts]733)734
735self.script_load_ctr = 0736
737def onload_script_visibility(params):738title = params.get('Script', None)739if title:740title_index = self.titles.index(title)741visibility = title_index == self.script_load_ctr742self.script_load_ctr = (self.script_load_ctr + 1) % len(self.titles)743return gr.update(visible=visibility)744else:745return gr.update(visible=False)746
747self.infotext_fields.append((dropdown, lambda x: gr.update(value=x.get('Script', 'None'))))748self.infotext_fields.extend([(script.group, onload_script_visibility) for script in self.selectable_scripts])749
750self.apply_on_before_component_callbacks()751
752return self.inputs753
754def run(self, p, *args):755script_index = args[0]756
757if script_index == 0 or script_index is None:758return None759
760script = self.selectable_scripts[script_index-1]761
762if script is None:763return None764
765script_args = args[script.args_from:script.args_to]766processed = script.run(p, *script_args)767
768shared.total_tqdm.clear()769
770return processed771
772def before_process(self, p):773for script in self.alwayson_scripts:774try:775script_args = p.script_args[script.args_from:script.args_to]776script.before_process(p, *script_args)777except Exception:778errors.report(f"Error running before_process: {script.filename}", exc_info=True)779
780def process(self, p):781for script in self.alwayson_scripts:782try:783script_args = p.script_args[script.args_from:script.args_to]784script.process(p, *script_args)785except Exception:786errors.report(f"Error running process: {script.filename}", exc_info=True)787
788def before_process_batch(self, p, **kwargs):789for script in self.alwayson_scripts:790try:791script_args = p.script_args[script.args_from:script.args_to]792script.before_process_batch(p, *script_args, **kwargs)793except Exception:794errors.report(f"Error running before_process_batch: {script.filename}", exc_info=True)795
796def after_extra_networks_activate(self, p, **kwargs):797for script in self.alwayson_scripts:798try:799script_args = p.script_args[script.args_from:script.args_to]800script.after_extra_networks_activate(p, *script_args, **kwargs)801except Exception:802errors.report(f"Error running after_extra_networks_activate: {script.filename}", exc_info=True)803
804def process_batch(self, p, **kwargs):805for script in self.alwayson_scripts:806try:807script_args = p.script_args[script.args_from:script.args_to]808script.process_batch(p, *script_args, **kwargs)809except Exception:810errors.report(f"Error running process_batch: {script.filename}", exc_info=True)811
812def postprocess(self, p, processed):813for script in self.alwayson_scripts:814try:815script_args = p.script_args[script.args_from:script.args_to]816script.postprocess(p, processed, *script_args)817except Exception:818errors.report(f"Error running postprocess: {script.filename}", exc_info=True)819
820def postprocess_batch(self, p, images, **kwargs):821for script in self.alwayson_scripts:822try:823script_args = p.script_args[script.args_from:script.args_to]824script.postprocess_batch(p, *script_args, images=images, **kwargs)825except Exception:826errors.report(f"Error running postprocess_batch: {script.filename}", exc_info=True)827
828def postprocess_batch_list(self, p, pp: PostprocessBatchListArgs, **kwargs):829for script in self.alwayson_scripts:830try:831script_args = p.script_args[script.args_from:script.args_to]832script.postprocess_batch_list(p, pp, *script_args, **kwargs)833except Exception:834errors.report(f"Error running postprocess_batch_list: {script.filename}", exc_info=True)835
836def post_sample(self, p, ps: PostSampleArgs):837for script in self.alwayson_scripts:838try:839script_args = p.script_args[script.args_from:script.args_to]840script.post_sample(p, ps, *script_args)841except Exception:842errors.report(f"Error running post_sample: {script.filename}", exc_info=True)843
844def on_mask_blend(self, p, mba: MaskBlendArgs):845for script in self.alwayson_scripts:846try:847script_args = p.script_args[script.args_from:script.args_to]848script.on_mask_blend(p, mba, *script_args)849except Exception:850errors.report(f"Error running post_sample: {script.filename}", exc_info=True)851
852def postprocess_image(self, p, pp: PostprocessImageArgs):853for script in self.alwayson_scripts:854try:855script_args = p.script_args[script.args_from:script.args_to]856script.postprocess_image(p, pp, *script_args)857except Exception:858errors.report(f"Error running postprocess_image: {script.filename}", exc_info=True)859
860def postprocess_maskoverlay(self, p, ppmo: PostProcessMaskOverlayArgs):861for script in self.alwayson_scripts:862try:863script_args = p.script_args[script.args_from:script.args_to]864script.postprocess_maskoverlay(p, ppmo, *script_args)865except Exception:866errors.report(f"Error running postprocess_image: {script.filename}", exc_info=True)867
868def postprocess_image_after_composite(self, p, pp: PostprocessImageArgs):869for script in self.alwayson_scripts:870try:871script_args = p.script_args[script.args_from:script.args_to]872script.postprocess_image_after_composite(p, pp, *script_args)873except Exception:874errors.report(f"Error running postprocess_image_after_composite: {script.filename}", exc_info=True)875
876def before_component(self, component, **kwargs):877for callback, script in self.on_before_component_elem_id.get(kwargs.get("elem_id"), []):878try:879callback(OnComponent(component=component))880except Exception:881errors.report(f"Error running on_before_component: {script.filename}", exc_info=True)882
883for script in self.scripts:884try:885script.before_component(component, **kwargs)886except Exception:887errors.report(f"Error running before_component: {script.filename}", exc_info=True)888
889def after_component(self, component, **kwargs):890for callback, script in self.on_after_component_elem_id.get(component.elem_id, []):891try:892callback(OnComponent(component=component))893except Exception:894errors.report(f"Error running on_after_component: {script.filename}", exc_info=True)895
896for script in self.scripts:897try:898script.after_component(component, **kwargs)899except Exception:900errors.report(f"Error running after_component: {script.filename}", exc_info=True)901
902def script(self, title):903return self.title_map.get(title.lower())904
905def reload_sources(self, cache):906for si, script in list(enumerate(self.scripts)):907args_from = script.args_from908args_to = script.args_to909filename = script.filename910
911module = cache.get(filename, None)912if module is None:913module = script_loading.load_module(script.filename)914cache[filename] = module915
916for script_class in module.__dict__.values():917if type(script_class) == type and issubclass(script_class, Script):918self.scripts[si] = script_class()919self.scripts[si].filename = filename920self.scripts[si].args_from = args_from921self.scripts[si].args_to = args_to922
923def before_hr(self, p):924for script in self.alwayson_scripts:925try:926script_args = p.script_args[script.args_from:script.args_to]927script.before_hr(p, *script_args)928except Exception:929errors.report(f"Error running before_hr: {script.filename}", exc_info=True)930
931def setup_scrips(self, p, *, is_ui=True):932for script in self.alwayson_scripts:933if not is_ui and script.setup_for_ui_only:934continue935
936try:937script_args = p.script_args[script.args_from:script.args_to]938script.setup(p, *script_args)939except Exception:940errors.report(f"Error running setup: {script.filename}", exc_info=True)941
942def set_named_arg(self, args, script_name, arg_elem_id, value, fuzzy=False):943"""Locate an arg of a specific script in script_args and set its value944Args:
945args: all script args of process p, p.script_args
946script_name: the name target script name to
947arg_elem_id: the elem_id of the target arg
948value: the value to set
949fuzzy: if True, arg_elem_id can be a substring of the control.elem_id else exact match
950Returns:
951Updated script args
952when script_name in not found or arg_elem_id is not found in script controls, raise RuntimeError
953"""
954script = next((x for x in self.scripts if x.name == script_name), None)955if script is None:956raise RuntimeError(f"script {script_name} not found")957
958for i, control in enumerate(script.controls):959if arg_elem_id in control.elem_id if fuzzy else arg_elem_id == control.elem_id:960index = script.args_from + i961
962if isinstance(args, tuple):963return args[:index] + (value,) + args[index + 1:]964elif isinstance(args, list):965args[index] = value966return args967else:968raise RuntimeError(f"args is not a list or tuple, but {type(args)}")969raise RuntimeError(f"arg_elem_id {arg_elem_id} not found in script {script_name}")970
971
972scripts_txt2img: ScriptRunner = None973scripts_img2img: ScriptRunner = None974scripts_postproc: scripts_postprocessing.ScriptPostprocessingRunner = None975scripts_current: ScriptRunner = None976
977
978def reload_script_body_only():979cache = {}980scripts_txt2img.reload_sources(cache)981scripts_img2img.reload_sources(cache)982
983
984reload_scripts = load_scripts # compatibility alias985