sd-dynamic-prompts/scripts/dynamic_prompting.py

369 lines
12 KiB
Python

from __future__ import annotations
import logging
from string import Template
from pathlib import Path
import math
import gradio as gr
import modules.scripts as scripts
from modules.processing import process_images, fix_seed, Processed
from modules.shared import opts, OptionInfo
from modules.devices import get_optimal_device
from dynamicprompts.wildcardmanager import WildcardManager
from prompts.uicreation import UiCreation
from dynamicprompts.generators.promptgenerator import GeneratorException
from ui import constants
from prompts.utils import slugify, get_unique_path
from prompts import prompt_writer
from prompts.generator_builder import GeneratorBuilder
from ui import wildcards_tab, save_params
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
is_debug = getattr(opts, "is_debug", False)
if is_debug:
logger.setLevel(logging.DEBUG)
base_dir = Path(scripts.basedir())
wildcard_dir = getattr(opts, "wildcard_dir", None)
if wildcard_dir is None:
WILDCARD_DIR = base_dir / "wildcards"
else:
WILDCARD_DIR = Path(wildcard_dir)
VERSION = "2.2.3"
wildcard_manager = WildcardManager(WILDCARD_DIR)
wildcards_tab.initialize(wildcard_manager)
save_params.initialize()
device = 0 if get_optimal_device() == "cuda" else -1
generator_builder = GeneratorBuilder(wildcard_manager)
class Script(scripts.Script):
def title(self):
return f"Dynamic Prompts v{VERSION}"
def show(self, is_img2img):
return scripts.AlwaysVisible
def ui(self, is_img2img):
ui_creation = UiCreation(wildcard_manager)
wildcard_html = ui_creation.probe()
html_path = base_dir / "helptext.html"
html = html_path.open().read()
html = Template(html).substitute(
wildcard_html=wildcard_html, WILDCARD_DIR=WILDCARD_DIR
)
jinja_html_path = base_dir / "jinja_help.html"
jinja_help = jinja_html_path.open().read()
with gr.Group(elem_id="dynamic-prompting"):
with gr.Accordion("Dynamic Prompts", open=False):
is_enabled = gr.Checkbox(
label="Dynamic Prompts enabled",
value=True,
elem_id="dynamic-prompts-enabled",
)
with gr.Group():
is_combinatorial = gr.Checkbox(
label="Combinatorial generation",
value=False,
elem_id="is-combinatorial",
)
combinatorial_batches = gr.Slider(
label="Combinatorial batches",
min=1,
max=10,
step=1,
value=1,
elem_id="combinatorial-times",
)
with gr.Box():
with gr.Group():
is_magic_prompt = gr.Checkbox(
label="Magic prompt", value=False, elem_id="is-magicprompt"
)
magic_prompt_length = gr.Slider(
label="Max magic prompt length",
value=100,
minimum=30,
maximum=300,
step=10,
)
magic_temp_value = gr.Slider(
label="Magic prompt creativity",
value=0.7,
minimum=0.1,
maximum=3.0,
step=0.10,
)
is_feeling_lucky = gr.Checkbox(
label="I'm feeling lucky",
value=False,
elem_id="is-feelinglucky",
)
is_attention_grabber = gr.Checkbox(
label="Attention grabber",
value=False,
elem_id="is-attention-grabber",
)
min_attention = gr.Slider(
label="Minimum attention",
value=1.1,
minimum=-1,
maximum=2,
step=0.1,
)
max_attention = gr.Slider(
label="Maximum attention",
value=1.5,
minimum=-1,
maximum=2,
step=0.1,
)
write_prompts = gr.Checkbox(
label="Write prompts to file", value=False, elem_id="write-prompts"
)
no_image_generation = gr.Checkbox(
label="Don't generate images",
value=False,
elem_id="no-image-generation",
)
with gr.Accordion("Help", open=False):
info = gr.HTML(html)
with gr.Group():
with gr.Accordion("Jinja2 templates", open=False):
enable_jinja_templates = gr.Checkbox(
label="Enable Jinja2 templates",
value=False,
elem_id="enable-jinja-templates",
)
with gr.Accordion("Help for Jinja2 templates", open=False):
jinja_info = gr.HTML(jinja_help)
with gr.Group():
with gr.Accordion("Advanced options", open=False):
unlink_seed_from_prompt = gr.Checkbox(
label="Unlink seed from prompt",
value=False,
elem_id="unlink-seed-from-prompt",
)
disable_negative_prompt = gr.Checkbox(
label="Disable negative prompt",
value=False,
elem_id="disable-negative-prompt",
)
use_fixed_seed = gr.Checkbox(
label="Fixed seed", value=False, elem_id="is-fixed-seed"
)
ignore_whitespace = gr.Checkbox(
label="Ignore whitespace",
value=False,
elem_id="ignore_whitespace",
)
write_raw_template = gr.Checkbox(
label="Write raw prompt to image",
value=False,
elem_id="write-raw-template",
)
return [
info,
is_enabled,
is_combinatorial,
combinatorial_batches,
is_magic_prompt,
is_feeling_lucky,
is_attention_grabber,
min_attention,
max_attention,
magic_prompt_length,
magic_temp_value,
use_fixed_seed,
write_prompts,
unlink_seed_from_prompt,
disable_negative_prompt,
enable_jinja_templates,
no_image_generation,
ignore_whitespace,
write_raw_template
]
def process(
self,
p,
info,
is_enabled,
is_combinatorial,
combinatorial_batches,
is_magic_prompt,
is_feeling_lucky,
is_attention_grabber,
min_attention,
max_attention,
magic_prompt_length,
magic_temp_value,
use_fixed_seed,
write_prompts,
unlink_seed_from_prompt,
disable_negative_prompt,
enable_jinja_templates,
no_image_generation,
ignore_whitespace,
write_raw_template
):
if not is_enabled:
logger.debug("Dynamic prompts disabled - exiting")
return p
self._p = p
context = p
option = OptionInfo(write_raw_template, "Write raw template to image")
opts.add_option(constants.OPTION_WRITE_RAW_TEMPLATE, option)
fix_seed(p)
original_prompt = p.all_prompts[0] if len(p.all_prompts) > 0 else p.prompt
original_negative_prompt = (
p.all_negative_prompts[0]
if len(p.all_negative_prompts) > 0
else p.negative_prompt
)
original_seed = p.seed
num_images = p.n_iter * p.batch_size
try:
combinatorial_batches = int(combinatorial_batches)
if combinatorial_batches < 1:
combinatorial_batches = 1
except (ValueError, TypeError):
combinatorial_batches = 1
try:
logger.debug("Creating generator")
generator_builder = (
GeneratorBuilder(wildcard_manager, ignore_whitespace=ignore_whitespace)
.set_is_feeling_lucky(is_feeling_lucky)
.set_is_attention_grabber(
is_attention_grabber, min_attention, max_attention
)
.set_is_jinja_template(enable_jinja_templates)
.set_is_combinatorial(is_combinatorial, combinatorial_batches)
.set_is_magic_prompt(
is_magic_prompt, magic_prompt_length, magic_temp_value
)
.set_is_dummy(False)
)
generator = generator_builder.create_generator(
original_seed, context, unlink_seed_from_prompt
)
all_prompts = generator.generate(original_prompt, num_images)
total_prompts = len(all_prompts)
if disable_negative_prompt:
all_negative_prompts = [""]
else:
all_negative_prompts = generator.generate(
original_negative_prompt, num_images
)
if len(all_negative_prompts) < total_prompts:
all_negative_prompts = all_negative_prompts * (
total_prompts // len(all_negative_prompts) + 1
)
all_negative_prompts = all_negative_prompts[:total_prompts]
except GeneratorException as e:
logger.exception(e)
all_prompts = [str(e)]
all_negative_prompts = [str(e)]
updated_count = len(all_prompts)
p.n_iter = math.ceil(updated_count / p.batch_size)
if use_fixed_seed:
all_seeds = [original_seed] * updated_count
else:
all_seeds = [
int(p.seed) + (x if p.subseed_strength == 0 else 0)
for x in range(updated_count)
]
logger.info(
f"Prompt matrix will create {updated_count} images in a total of {p.n_iter} batches."
)
try:
if write_prompts:
prompt_filename = get_unique_path(
Path(p.outpath_samples), slugify(original_prompt), suffix="csv"
)
prompt_writer.write_prompts(
prompt_filename,
original_prompt,
original_negative_prompt,
all_prompts,
all_negative_prompts,
)
except Exception as e:
logger.error(f"Failed to write prompts to file: {e}")
p.all_prompts = all_prompts
p.all_negative_prompts = all_negative_prompts
if no_image_generation:
logger.debug("No image generation requested - exiting")
# Need a minimum of batch size images to avoid errors
p.batch_size = 1
p.all_prompts = all_prompts[0:1]
p.all_seeds = all_seeds
p.prompt_for_display = original_prompt
p.prompt = original_prompt
p.seed = original_seed
wildcard_manager.ensure_directory()