File size: 5,020 Bytes
704a244
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
"""
Attempting to map Gradio UI elements present in scripts to allow
converting to pyQt elements on the plugin side.
"""

import logging
from typing import List, Sequence, Tuple

import gradio as gr
import modules

from .config import LOGGER_NAME

log = logging.getLogger(LOGGER_NAME)


def inspect_ui(script: modules.scripts.Script, is_img2img: bool):
    """Get metadata about accepted arguments by inspecting GUI. Needs Gradio Blocks context."""
    elems = script.ui(is_img2img)

    metadata = []
    if not isinstance(elems, Sequence):
        return metadata

    for elem in elems:
        data = {
            "type": "None",
            "label": elem.label,
            "val": elem.value,
            "is_index": False,
        }
        if isinstance(elem, gr.HTML):
            data.update(val="")
        elif isinstance(elem, gr.Markdown):
            data.update(val="")
        elif isinstance(elem, gr.Slider):
            data.update(
                type="range",
                min=elem.minimum,
                max=elem.maximum,
                step=elem.step,
            )
        elif isinstance(elem, gr.Radio):
            data.update(
                type="combo",
                is_index=elem.type == "index",
                opts=elem.choices,
            )
        elif isinstance(elem, gr.Dropdown):
            data.update(
                type="combo",
                is_index=elem.type == "index",
                opts=elem.choices,
            )
        elif isinstance(elem, gr.Textbox):
            data.update(
                type="text",
            )
        elif isinstance(elem, gr.Checkbox):
            data.update(
                type="checkbox",
            )
        elif isinstance(elem, gr.CheckboxGroup):
            data.update(
                type="multiselect",
                is_index=elem.type == "index",
                opts=elem.choices,
            )
        elif isinstance(elem, gr.File):
            data.update(val="")  # unsupported
        else:
            data.update(val="")  # unsupported
        metadata.append(data)

    return metadata


img2img_script_meta = None
txt2img_script_meta = None


def get_scripts_metadata(is_img2img: bool):
    """Get metadata about accepted arguments for scripts."""
    # NOTE: inspect_ui is quite slow, so cache this
    global txt2img_script_meta, img2img_script_meta
    if is_img2img:
        runner = modules.scripts.scripts_img2img
    else:
        runner = modules.scripts.scripts_txt2img
    metadata = {"None": []}
    if (
        is_img2img
        and img2img_script_meta
        and len(img2img_script_meta) - 1 == len(runner.titles)
    ):
        return img2img_script_meta
    elif txt2img_script_meta and len(txt2img_script_meta) - 1 == len(runner.titles):
        return txt2img_script_meta

    with gr.Blocks(visible=False, analytics_enabled=False):
        for name, script in zip(runner.titles, runner.selectable_scripts):
            metadata[name] = inspect_ui(script, is_img2img)
    if is_img2img:
        img2img_script_meta = metadata
    else:
        txt2img_script_meta = metadata
    return metadata


def get_script_info(
    script_name: str, is_img2img: bool
) -> Tuple[int, modules.scripts.Script, List[dict]]:
    """Get index of script, script instance and argument metadata by name.

    Args:
        script_name (str): Exact name of script.
        is_img2img (bool): Whether the script is for img2img or txt2img.

    Raises:
        KeyError: Script cannot be found.

    Returns:
        Tuple[int, Script, List[dict]]: Index of script, script itself and arguments metadata.
    """
    if is_img2img:
        runner = modules.scripts.scripts_img2img
    else:
        runner = modules.scripts.scripts_txt2img
    # in API, index 0 means no script, scripts are indexed from 1 onwards
    names = ["None"] + runner.titles
    if script_name == "None":
        return 0, None, []
    for i, n in enumerate(names):
        if n == script_name:
            script = runner.selectable_scripts[i - 1]
            return i, script, get_scripts_metadata(is_img2img)[n]
    raise KeyError(f"script not found for type {type}: {script_name}")


def process_script_args(
    script_ind: int, script: modules.scripts.Script, meta: List[dict], args: list
) -> list:
    """Get the position arguments required."""
    if script is None:
        return [0]  # 0th element selects which script to use. 0 is None.

    # convert strings back to indexes
    for i, (o, arg) in enumerate(zip(meta, args)):
        if o["is_index"]:
            if isinstance(arg, list):
                args[i] = [o["opts"].index(v) for v in arg]
            else:
                args[i] = o["opts"].index(arg)

    log.info(
        f"Script selected: {script.filename}, Args Range: [{script.args_from}:{script.args_to}]"
    )
    # pad the args like the internal API requires...
    args = [script_ind] + [0] * (script.args_from - 1) + args
    log.info(f"Script args:\n{args}")
    return args