forked from adieyal/sd-dynamic-prompts
-
Notifications
You must be signed in to change notification settings - Fork 24
/
Copy pathdynamic_prompting.py
170 lines (129 loc) · 5.18 KB
/
dynamic_prompting.py
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
163
164
165
166
167
168
169
170
import os
from glob import glob
from pathlib import Path
import logging
import math
import re, random
import gradio as gr
import modules.scripts as scripts
from modules.processing import process_images, fix_seed
from modules.shared import opts
logging.basicConfig(level=logging.WARNING)
logger = logging.getLogger(__name__)
WILDCARD_DIR = getattr(opts, "wildcard_dir", "scripts/wildcards")
MAX_RECURSIONS = 20
VERSION = "0.4.3"
re_wildcard = re.compile(r"__(.*?)__")
re_combinations = re.compile(r"\{([^{}]*)}")
DEFAULT_NUM_COMBINATIONS = 1
def replace_combinations(match):
if match is None or len(match.groups()) == 0:
logger.warning("Unexpected missing combination")
return ""
variants = [s.strip() for s in match.groups()[0].split("|")]
if len(variants) > 0:
first = variants[0].split("$$")
quantity = DEFAULT_NUM_COMBINATIONS
if len(first) == 2: # there is a $$
prefix_num, first_variant = first
variants[0] = first_variant
try:
prefix_ints = [int(i) for i in prefix_num.split("-")]
if len(prefix_ints) == 1:
quantity = prefix_ints[0]
elif len(prefix_ints) == 2:
prefix_low = min(prefix_ints)
prefix_high = max(prefix_ints)
quantity = random.randint(prefix_low, prefix_high)
else:
raise
except Exception:
logger.warning(f"Unexpected combination formatting, expected $$ prefix to be a number or interval. Defaulting to {DEFAULT_NUM_COMBINATIONS}")
try:
picked = random.sample(variants, quantity)
return ", ".join(picked)
except ValueError as e:
logger.exception(e)
return ""
return ""
def replace_wildcard(match):
if match is None or len(match.groups()) == 0:
logger.warning("Expected match to contain a filename")
return ""
wildcard_dir = Path(WILDCARD_DIR)
if not wildcard_dir.exists():
wildcard_dir.mkdir()
wildcard = match.groups()[0]
wildcard_path = wildcard_dir / f"{wildcard}.txt"
if not wildcard_path.exists():
logger.warning(f"Missing file {wildcard_path}")
return ""
options = [line.strip() for line in wildcard_path.open(errors="ignore")]
return random.choice(options)
def pick_wildcards(template):
return re_wildcard.sub(replace_wildcard, template)
def pick_variant(template):
if template is None:
return None
return re_combinations.sub(replace_combinations, template)
def generate_prompt(template):
old_prompt = template
counter = 0
while True:
counter += 1
if counter > MAX_RECURSIONS:
raise Exception("Too many recursions, something went wrong with generating the prompt")
prompt = pick_variant(old_prompt)
prompt = pick_wildcards(prompt)
if prompt == old_prompt:
logger.info(f"Prompt: {prompt}")
return prompt
old_prompt = prompt
class Script(scripts.Script):
def title(self):
return f"Dynamic Prompting v{VERSION}"
def ui(self, is_img2img):
html = f"""
<h3><strong>Combinations</strong></h3>
Choose a number of terms from a list, in this case we choose two artists
<code>{{2$$artist1|artist2|artist3}}</code>
If $$ is not provided, then 1$$ is assumed.
<br>
A range can be provided:
<code>{{1-3$$artist1|artist2|artist3}}</code>
In this case, a random number of artists between 1 and 3 is chosen.
<br/><br/>
<h3><strong>Wildcards</strong></h3>
<p>Available wildcards</p>
<ul>
"""
for path in Path(WILDCARD_DIR).glob("*.txt"):
filename = path.name
wildcard = "__" + filename.replace(".txt", "") + "__"
html += f"<li>{wildcard}</li>"
html += "</ul>"
html += f"""
<br/>
<code>WILDCARD_DIR: {WILDCARD_DIR}</code><br/>
<small>You can add more wildcards by creating a text file with one term per line and name is mywildcards.txt. Place it in {WILDCARD_DIR}. <code>__mywildcards__</code> will then become available.</small>
"""
info = gr.HTML(html)
return [info]
def run(self, p, info):
fix_seed(p)
original_prompt = p.prompt[0] if type(p.prompt) == list else p.prompt
original_seed = p.seed
all_prompts = [
generate_prompt(original_prompt) for _ in range(p.n_iter)
]
all_seeds = [int(p.seed) + (x if p.subseed_strength == 0 else 0) for x in range(len(all_prompts))]
p.n_iter = math.ceil(len(all_prompts) / p.batch_size)
p.do_not_save_grid = True
print(f"Prompt matrix will create {len(all_prompts)} images using a total of {p.n_iter} batches.")
p.prompt = all_prompts
p.seed = all_seeds
p.prompt_for_display = original_prompt
processed = process_images(p)
p.prompt = original_prompt
p.seed = original_seed
return processed