Compare commits

...
Author SHA1 Message Date
AUTOMATIC f2693bec08 prompt editing 2022-09-15 13:10:16 +03:00
AUTOMATIC b28cf84c36 prevent repeating messages about too many tokens 2022-09-15 08:57:03 +03:00
EyeDeckandAUTOMATIC1111 dfb2e830d9 Improved directory sanitization when --hide_ui_dir_config
Fixes an issue where it's still possible to write to arbitrary directories through careful use of \.. or /.. in directory patterns

...and fix the regex to work better

reeeegex
2022-09-15 07:39:57 +03:00
Steve EberhardtandAUTOMATIC1111 4a626f6ea6 Corrected typos in shared.py and README 2022-09-15 07:38:17 +03:00
AUTOMATIC 6a4db7b9a5 Add info about AMD to readme. 2022-09-14 20:37:56 +03:00
AUTOMATIC d51847c184 fix caching for img2imgalt 2022-09-14 19:41:55 +03:00
AUTOMATIC 91c56c51c7 Merge remote-tracking branch 'origin/master' 2022-09-14 19:13:35 +03:00
AUTOMATIC 3030dcfefd added a background color fix for dark scheme users 2022-09-14 19:13:23 +03:00
NebulousDevandAUTOMATIC1111 5dde56afe3 Fixed typo in text attention setting 2022-09-14 18:41:40 +03:00
AUTOMATIC 16fb8d24d4 and make the image not be upscaled in gallery 2022-09-14 18:35:04 +03:00
AUTOMATIC f3de9bf7d9 make the gallery taller 2022-09-14 18:17:24 +03:00
AUTOMATIC 9f267af3f7 added a second style field
added the ability to use {prompt} in styles
added a button to apply style to textbox
rearranged top row for UI
2022-09-14 17:56:21 +03:00
MichokoandAUTOMATIC1111 6153d9d9e9 Update images.py
Better code
2022-09-14 15:51:45 +03:00
MichokoandAUTOMATIC1111 d5520d43fd Update images.py
Handles grids names. Code more robust and doesn't fail if mixed with other files.
2022-09-14 15:51:45 +03:00
MichokoandAUTOMATIC1111 94aeb5dec9 Update images.py
Better computing of images indexes in filenames
2022-09-14 15:51:45 +03:00
DepFAandAUTOMATIC1111 e16d762800 add draw legend toggle 2022-09-14 15:33:37 +03:00
DepFAandAUTOMATIC1111 35229d9488 Add square bracket range+count syntax 2022-09-14 15:33:37 +03:00
Elias OenalandAUTOMATIC1111 3daf9cac46 Removed stray references to shared.device_codeformer. 2022-09-14 15:24:55 +03:00
Elias OenalandAUTOMATIC1111 26f733a026 fix for codeformer switching torch devices on metal systems. 2022-09-14 15:24:55 +03:00
AUTOMATIC bb2732c1c7 updates for exif comments #446 2022-09-14 15:20:05 +03:00
JJandAUTOMATIC1111 c4e90bf689 format exif string
* UserComment needs an ID code at the start of the tag area. This is provided by piexif.helper.UserComment, otherwise an "Warning 	 Invalid EXIF text encoding for UserComment" is thrown upon reading the exif data
2022-09-14 15:14:26 +03:00
AUTOMATIC c9430e53f6 loopback moved to scripts, added support for multiple batches, changed to honor save grids and how grids in web setting 2022-09-14 14:47:54 +03:00
AUTOMATIC c253d6bdab do not die on failing to load script #426 2022-09-14 13:20:24 +03:00
AUTOMATIC f6aa0cdb0b Overall progress incorrect with X/Y plot and batch count > 1 #441 2022-09-14 13:08:05 +03:00
AUTOMATIC 4c51752464 option name updates for #432 2022-09-14 11:31:49 +03:00
jtkelm2andAUTOMATIC1111 493032a7af Update ui.py 2022-09-14 11:27:41 +03:00
jtkelm2andAUTOMATIC1111 df81de0d2f Update ui.py 2022-09-14 11:27:41 +03:00
jtkelm2andAUTOMATIC1111 74c5f7974c Added selected image saving 2022-09-14 11:27:41 +03:00
AUTOMATIC 0cfbd59d6d Merge remote-tracking branch 'origin/master' 2022-09-14 11:18:40 +03:00
AUTOMATIC 928b246c9e [FEATURE REQUEST] Script settings should also be written to the text files #437 2022-09-14 11:08:36 +03:00
orionaskatuandAUTOMATIC1111 a0e819de90 remove model files check 2022-09-14 10:45:25 +03:00
orionaskatuandAUTOMATIC1111 d62fbcc5aa fix on torch_command + tested on debian 2022-09-14 10:45:25 +03:00
orionaskatuandAUTOMATIC1111 7bf76af40a rewrite for launch.py - untested 2022-09-14 10:45:25 +03:00
orionaskatuandAUTOMATIC1111 cf150757b5 webui-user.sh gitignore 2022-09-14 10:45:25 +03:00
orionaskatuandAUTOMATIC1111 efc8ed13e1 install/launch scripts for linux 2022-09-14 10:45:25 +03:00
AUTOMATIC 6bea45d495 prevent making grid if there is no need for it #428 2022-09-14 10:34:44 +03:00
JustAnOkapiandAUTOMATIC1111 21f2a706bb Revert "Update webui-user.bat"
This reverts commit 51a960df1fdd265e7747e19b50e25f359447587c.
2022-09-14 08:58:13 +03:00
JustAnOkapiandAUTOMATIC1111 f9f9d04b5f prevent extras from saving in dir
Extras have none of the vars used in dir names, so they cant be saved into dirs.
+grid code cleanup
2022-09-14 08:58:13 +03:00
JustAnOkapiandAUTOMATIC1111 e73e2ce2fd Update webui-user.bat 2022-09-14 08:58:13 +03:00
JJandAUTOMATIC1111 859fff3700 add webp to file formats with exif saved 2022-09-14 08:49:00 +03:00
camenduruandAUTOMATIC1111 f07d789b79 added cmd arg to load custom styles file 2022-09-14 08:48:42 +03:00
Austere GrimandAUTOMATIC1111 66b09bbfec Typo of prompt 2022-09-14 08:35:27 +03:00
22 changed files with 753 additions and 207 deletions
+1
View File
@@ -14,4 +14,5 @@ __pycache__
/styles.csv
/styles.csv.bak
/webui-user.bat
/webui-user.sh
/interrogate
+41 -6
View File
@@ -70,6 +70,39 @@ RealESRGAN into the directory with ESRGAN models. Thank you.
- _*(optional)*_ place `GFPGANv1.3.pth` into webui directory, next to `webui.bat`.
- run `webui-user.bat` from Windows Explorer. Run it as a normal user, ***not*** as administrator.
### Running on AMD GPUs
See the [wiki article](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Running-using-AMD-GPUs) by [cryzed](https://github.com/cryzed).
### Linux Automatic installation/launch
Prequisites:
- For Debian-based:
```commandline
sudo apt install wget git python3 python3-venv
```
- For Red Hat-based:
```commandline
sudo dnf install wget git python3
```
- If you want to install to default directory `/home/$(whoami)/stable-diffusion-webui/`, you can launch directly:
```commandline
bash <(wget -qO- https://raw.githubusercontent.com/AUTOMATIC1111/stable-diffusion-webui/master/webui.sh)
```
- If you want to customize the installation just `git clone` the repo where you want it,
change the variables in `webui-user.sh` and launch in console `bash webui.sh`.
- place `model.ckpt` into webui directory, next to `webui.py`.
- _*(optional)*_ place `GFPGANv1.3.pth` into webui directory, next to `webui.py`.
- run `bash webui.sh`. Run it as a normal user, ***not*** as root.
#### Troubleshooting
- if your version of Python is not in PATH (or if another version is), edit `webui-user.bat`, and modify the
@@ -79,7 +112,7 @@ You can do this for python, but not for git.
to enable appropriate optimization according to low VRAM guide below (for example, `set COMMANDLINE_ARGS=--medvram --opt-split-attention`).
- to prevent the creation of virtual environment and use your system python, use custom parameter replacing `set VENV_DIR=-` (see below).
- webui.bat installs requirements from files `requirements_versions.txt`, which lists versions for modules specifically compatible with
Python 3.10.6. If you choose to install for a different version of python, using custom parameter `set REQS_FILE=requirements.txt`
Python 3.10.6. If you choose to install for a different version of python, using custom parameter `set REQS_FILE=requirements.txt`
may help (but I still recommend you to just use the recommended version of python).
- if you feel you broke something and want to reinstall from scratch, delete directories: `venv`, `repositories`.
- if you get a green or black screen instead of generated pictures, you have a card that doesn't support half precision
@@ -133,7 +166,7 @@ Here's a list of optimization arguments:
- If you have 4GB VRAM and want to make 512x512 (or maybe up to 640x640) images, use `--medvram`.
- If you have 4GB VRAM and want to make 512x512 images, but you get an out of memory error with `--medvram`, use `--medvram --opt-split-attention` instead.
- If you have 4GB VRAM and want to make 512x512 images, and you still get an out of memory error, use `--lowvram --always-batch-cond-uncond --opt-split-attention` instead.
- If you have 4GB VRAM and want to make images larger than you can with `--medvram`, use `--lowvram --opt-split-attention`.
- If you have 4GB VRAM and want to make images larger than you can with `--medvram`, use `--lowvram --opt-split-attention`.
- If you have more VRAM and want to make larger images than you can usually make (for example 1024x1024 instead of 512x512), use `--medvram --opt-split-attention`. You can use `--lowvram`
also but the effect will likely be barely noticeable.
- Otherwise, do not use any of those.
@@ -141,7 +174,7 @@ also but the effect will likely be barely noticeable.
### Running online
Use the `--share` option to run online. You will get a xxx.app.gradio link. This is the intended way to use the
program in collabs. You may set up authentication for said gradio shared instance with the flag `--gradio-auth username:password`, optionally providing multiple sets of usernames and passwords separated by commas.
program in Colab. You may set up authentication for said gradio shared instance with the flag `--gradio-auth username:password`, optionally providing multiple sets of usernames and passwords separated by commas.
Use `--listen` to make the server listen to network connections. This will allow computers on the local network
to access the UI, and if you configure port forwarding, also computers on the internet.
@@ -150,9 +183,9 @@ Use `--port xxxx` to make the server listen on a specific port, xxxx being the w
all ports below 1024 need root/admin rights, for this reason it is advised to use a port above 1024.
Defaults to port 7860 if available.
### Google collab
### Google Colab
If you don't want or can't run locally, here is a Google colab that allows you to run the webui:
If you don't want or can't run locally, here is a Google Colab that allows you to run the webui:
https://colab.research.google.com/drive/1Iy-xW9t1-OQWhb0hNxueGij8phCyluOh
@@ -309,6 +342,8 @@ After that follow the instructions in the `Manual instructions` section starting
[A list of custom scripts](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Custom-scripts-from-users), along with installation instructions.
### img2img alternative test
- see [this post](https://www.reddit.com/r/StableDiffusion/comments/xboy90/a_better_way_of_doing_img2img_by_finding_the/) on ebaumsworld.com for context.
- find it in scripts section
@@ -330,4 +365,4 @@ After that follow the instructions in the `Manual instructions` section starting
- Idea for SD upscale - https://github.com/jquesnelle/txt2imghd
- CLIP interrogator idea and borrowing some code - https://github.com/pharmapsychotic/clip-interrogator
- Initial Gradio script - posted on 4chan by an Anonymous user. Thank you Anonymous user.
- (You)
- (You)
+2 -4
View File
@@ -47,13 +47,11 @@ def setup_codeformer():
def __init__(self):
self.net = None
self.face_helper = None
if shared.device.type == 'mps': # CodeFormer currently does not support mps backend
shared.device_codeformer = torch.device('cpu')
def create_models(self):
if self.net is not None and self.face_helper is not None:
self.net.to(shared.device)
self.net.to(devices.device_codeformer)
return self.net, self.face_helper
net = net_class(dim_embd=512, codebook_size=1024, n_head=8, n_layers=9, connect_list=['32', '64', '128', '256']).to(devices.device_codeformer)
@@ -66,7 +64,7 @@ def setup_codeformer():
self.net = net
self.face_helper = face_helper
self.net.to(shared.device)
self.net.to(devices.device_codeformer)
return net, face_helper
+8 -2
View File
@@ -7,6 +7,7 @@ import modules.gfpgan_model
from modules.ui import plaintext_to_html
import modules.codeformer_model
import piexif
import piexif.helper
cached_images = {}
@@ -69,7 +70,7 @@ def run_extras(image, gfpgan_visibility, codeformer_visibility, codeformer_weigh
while len(cached_images) > 2:
del cached_images[next(iter(cached_images.keys()))]
images.save_image(image, outpath, "", None, info=info, extension=opts.samples_format, short_filename=True, no_prompt=True, pnginfo_section_name="extras", existing_info=existing_pnginfo)
images.save_image(image, path=outpath, basename="", seed=None, prompt=None, extension=opts.samples_format, info=info, short_filename=True, no_prompt=True, grid=False, pnginfo_section_name="extras", existing_info=existing_pnginfo)
return image, plaintext_to_html(info), ''
@@ -80,7 +81,12 @@ def run_pnginfo(image):
if "exif" in image.info:
exif = piexif.load(image.info["exif"])
exif_comment = (exif or {}).get("Exif", {}).get(piexif.ExifIFD.UserComment, b'')
exif_comment = exif_comment.decode("utf8", 'ignore')
try:
exif_comment = piexif.helper.UserComment.load(exif_comment)
except ValueError:
exif_comment = exif_comment.decode('utf8', errors="ignore")
items['exif comment'] = exif_comment
for field in ['jfif', 'jfif_version', 'jfif_unit', 'jfif_density', 'dpi', 'exif']:
+30 -10
View File
@@ -13,7 +13,7 @@ import string
import modules.shared
from modules import sd_samplers, shared
from modules.shared import opts
from modules.shared import opts, cmd_opts
LANCZOS = (Image.Resampling.LANCZOS if hasattr(Image, 'Resampling') else Image.LANCZOS)
@@ -277,13 +277,33 @@ def apply_filename_pattern(x, p, seed, prompt):
x = x.replace("[model_hash]", shared.sd_model_hash)
x = x.replace("[date]", datetime.date.today().isoformat())
if cmd_opts.hide_ui_dir_config:
x = re.sub(r'^[\\/]+|\.{2,}[\\/]+|[\\/]+\.{2,}', '', x)
return x
def get_next_sequence_number(path, basename):
"""
Determines and returns the next sequence number to use when saving an image in the specified directory.
def save_image(image, path, basename, seed=None, prompt=None, extension='png', info=None, short_filename=False, no_prompt=False, pnginfo_section_name='parameters', p=None, existing_info=None):
# would be better to add this as an argument in future, but will do for now
is_a_grid = basename != ""
The sequence starts at 0.
"""
result = -1
if basename != '':
basename = basename + "-"
prefix_length = len(basename)
for p in os.listdir(path):
if p.startswith(basename):
l = os.path.splitext(p[prefix_length:])[0].split('-') #splits the filename (removing the basename first if one is defined, so the sequence number is always the first element)
try:
result = max(int(l[0]), result)
except ValueError:
pass
return result + 1
def save_image(image, path, basename, seed=None, prompt=None, extension='png', info=None, short_filename=False, no_prompt=False, grid=False, pnginfo_section_name='parameters', p=None, existing_info=None):
if short_filename or prompt is None or seed is None:
file_decoration = ""
elif opts.save_to_dirs:
@@ -307,7 +327,7 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i
else:
pnginfo = None
save_to_dirs = (is_a_grid and opts.grid_save_to_dirs) or (not is_a_grid and opts.save_to_dirs)
save_to_dirs = (grid and opts.grid_save_to_dirs) or (not grid and opts.save_to_dirs and not no_prompt)
if save_to_dirs:
dirname = apply_filename_pattern(opts.directories_filename_pattern or "[prompt_words]", p, seed, prompt)
@@ -315,21 +335,21 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i
os.makedirs(path, exist_ok=True)
filecount = len([x for x in os.listdir(path) if os.path.splitext(x)[1] == '.' + extension])
basecount = get_next_sequence_number(path, basename)
fullfn = "a.png"
fullfn_without_extension = "a"
for i in range(500):
fn = f"{filecount+i:05}" if basename == '' else f"{basename}-{filecount+i:04}"
fn = f"{basecount+i:05}" if basename == '' else f"{basename}-{basecount+i:04}"
fullfn = os.path.join(path, f"{fn}{file_decoration}.{extension}")
fullfn_without_extension = os.path.join(path, f"{fn}{file_decoration}")
if not os.path.exists(fullfn):
break
if extension.lower() in ("jpg", "jpeg"):
if extension.lower() in ("jpg", "jpeg", "webp"):
exif_bytes = piexif.dump({
"Exif": {
piexif.ExifIFD.UserComment: info.encode("utf8"),
}
piexif.ExifIFD.UserComment: piexif.helper.UserComment.dump(info, encoding="unicode")
},
})
else:
exif_bytes = None
+4 -41
View File
@@ -11,10 +11,9 @@ from modules.ui import plaintext_to_html
import modules.images as images
import modules.scripts
def img2img(prompt: str, negative_prompt: str, prompt_style: str, init_img, init_img_with_mask, init_mask, mask_mode, steps: int, sampler_index: int, mask_blur: int, inpainting_fill: int, restore_faces: bool, tiling: bool, mode: int, n_iter: int, batch_size: int, cfg_scale: float, denoising_strength: float, denoising_strength_change_factor: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, height: int, width: int, resize_mode: int, upscaler_index: str, upscale_overlap: int, inpaint_full_res: bool, inpainting_mask_invert: int, *args):
def img2img(prompt: str, negative_prompt: str, prompt_style: str, prompt_style2: str, init_img, init_img_with_mask, init_mask, mask_mode, steps: int, sampler_index: int, mask_blur: int, inpainting_fill: int, restore_faces: bool, tiling: bool, mode: int, n_iter: int, batch_size: int, cfg_scale: float, denoising_strength: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, height: int, width: int, resize_mode: int, upscaler_index: str, upscale_overlap: int, inpaint_full_res: bool, inpainting_mask_invert: int, *args):
is_inpaint = mode == 1
is_loopback = mode == 2
is_upscale = mode == 3
is_upscale = mode == 2
if is_inpaint:
if mask_mode == 0:
@@ -38,7 +37,7 @@ def img2img(prompt: str, negative_prompt: str, prompt_style: str, init_img, init
outpath_grids=opts.outdir_grids or opts.outdir_img2img_grids,
prompt=prompt,
negative_prompt=negative_prompt,
prompt_style=prompt_style,
styles=[prompt_style, prompt_style2],
seed=seed,
subseed=subseed,
subseed_strength=subseed_strength,
@@ -61,46 +60,10 @@ def img2img(prompt: str, negative_prompt: str, prompt_style: str, init_img, init
denoising_strength=denoising_strength,
inpaint_full_res=inpaint_full_res,
inpainting_mask_invert=inpainting_mask_invert,
extra_generation_params={
"Denoising strength change factor": (denoising_strength_change_factor if is_loopback else None)
}
)
print(f"\nimg2img: {prompt}", file=shared.progress_print_out)
if is_loopback:
output_images, info = None, None
history = []
initial_seed = None
initial_info = None
state.job_count = n_iter
for i in range(n_iter):
p.n_iter = 1
p.batch_size = 1
p.do_not_save_grid = True
state.job = f"Batch {i + 1} out of {n_iter}"
processed = process_images(p)
if initial_seed is None:
initial_seed = processed.seed
initial_info = processed.info
init_img = processed.images[0]
p.init_images = [init_img]
p.seed = processed.seed + 1
p.denoising_strength = min(max(p.denoising_strength * denoising_strength_change_factor, 0.1), 1)
history.append(processed.images[0])
grid = images.image_grid(history, batch_size, rows=1)
images.save_image(grid, p.outpath_grids, "grid", initial_seed, prompt, opts.grid_format, info=info, short_filename=not opts.grid_extended_filename, p=p)
processed = Processed(p, history, initial_seed, initial_info)
elif is_upscale:
if is_upscale:
initial_info = None
processing.fix_seed(p)
+13 -12
View File
@@ -12,7 +12,7 @@ import cv2
from skimage import exposure
import modules.sd_hijack
from modules import devices
from modules import devices, prompt_parser
from modules.sd_hijack import model_hijack
from modules.sd_samplers import samplers, samplers_for_img2img
from modules.shared import opts, cmd_opts, state
@@ -46,14 +46,14 @@ def apply_color_correction(correction, image):
class StableDiffusionProcessing:
def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prompt="", prompt_style="None", seed=-1, subseed=-1, subseed_strength=0, seed_resize_from_h=-1, seed_resize_from_w=-1, sampler_index=0, batch_size=1, n_iter=1, steps=50, cfg_scale=7.0, width=512, height=512, restore_faces=False, tiling=False, do_not_save_samples=False, do_not_save_grid=False, extra_generation_params=None, overlay_images=None, negative_prompt=None):
def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prompt="", styles=None, seed=-1, subseed=-1, subseed_strength=0, seed_resize_from_h=-1, seed_resize_from_w=-1, sampler_index=0, batch_size=1, n_iter=1, steps=50, cfg_scale=7.0, width=512, height=512, restore_faces=False, tiling=False, do_not_save_samples=False, do_not_save_grid=False, extra_generation_params=None, overlay_images=None, negative_prompt=None):
self.sd_model = sd_model
self.outpath_samples: str = outpath_samples
self.outpath_grids: str = outpath_grids
self.prompt: str = prompt
self.prompt_for_display: str = None
self.negative_prompt: str = (negative_prompt or "")
self.prompt_style: str = prompt_style
self.styles: str = styles
self.seed: int = seed
self.subseed: int = subseed
self.subseed_strength: float = subseed_strength
@@ -180,9 +180,9 @@ def process_images(p: StableDiffusionProcessing) -> Processed:
modules.sd_hijack.model_hijack.apply_circular(p.tiling)
comments = []
comments = {}
modules.styles.apply_style(p, shared.prompt_styles[p.prompt_style])
shared.prompt_styles.apply_styles(p)
if type(p.prompt) == list:
all_prompts = p.prompt
@@ -247,11 +247,14 @@ def process_images(p: StableDiffusionProcessing) -> Processed:
seeds = all_seeds[n * p.batch_size:(n + 1) * p.batch_size]
subseeds = all_subseeds[n * p.batch_size:(n + 1) * p.batch_size]
uc = p.sd_model.get_learned_conditioning(len(prompts) * [p.negative_prompt])
c = p.sd_model.get_learned_conditioning(prompts)
#uc = p.sd_model.get_learned_conditioning(len(prompts) * [p.negative_prompt])
#c = p.sd_model.get_learned_conditioning(prompts)
uc = prompt_parser.get_learned_conditioning(len(prompts) * [p.negative_prompt], p.steps)
c = prompt_parser.get_learned_conditioning(prompts, p.steps)
if len(model_hijack.comments) > 0:
comments += model_hijack.comments
for comment in model_hijack.comments:
comments[comment] = 1
# we manually generate all input noises because each one should have a specific seed
x = create_random_tensors([opt_C, p.height // opt_f, p.width // opt_f], seeds=seeds, subseeds=subseeds, subseed_strength=p.subseed_strength, seed_resize_from_h=p.seed_resize_from_h, seed_resize_from_w=p.seed_resize_from_w)
@@ -312,12 +315,10 @@ def process_images(p: StableDiffusionProcessing) -> Processed:
state.nextjob()
unwanted_grid_because_of_img_count = len(output_images) < 2 and opts.grid_only_if_multiple
if not p.do_not_save_grid and not unwanted_grid_because_of_img_count:
return_grid = opts.return_grid
if (opts.return_grid or opts.grid_save) and not p.do_not_save_grid and not unwanted_grid_because_of_img_count:
grid = images.image_grid(output_images, p.batch_size)
if return_grid:
if opts.return_grid:
output_images.insert(0, grid)
if opts.grid_save:
+128
View File
@@ -0,0 +1,128 @@
import re
from collections import namedtuple
import torch
import modules.shared as shared
re_prompt = re.compile(r'''
(.*?)
\[
([^]:]+):
(?:([^]:]*):)?
([0-9]*\.?[0-9]+)
]
|
(.+)
''', re.X)
# a prompt like this: "fantasy landscape with a [mountain:lake:0.25] and [an oak:a christmas tree:0.75][ in foreground::0.6][ in background:0.25] [shoddy:masterful:0.5]"
# will be represented with prompt_schedule like this (assuming steps=100):
# [25, 'fantasy landscape with a mountain and an oak in foreground shoddy']
# [50, 'fantasy landscape with a lake and an oak in foreground in background shoddy']
# [60, 'fantasy landscape with a lake and an oak in foreground in background masterful']
# [75, 'fantasy landscape with a lake and an oak in background masterful']
# [100, 'fantasy landscape with a lake and a christmas tree in background masterful']
def get_learned_conditioning_prompt_schedules(prompts, steps):
res = []
cache = {}
for prompt in prompts:
prompt_schedule: list[list[str | int]] = [[steps, ""]]
cached = cache.get(prompt, None)
if cached is not None:
res.append(cached)
for m in re_prompt.finditer(prompt):
plaintext = m.group(1) if m.group(5) is None else m.group(5)
concept_from = m.group(2)
concept_to = m.group(3)
if concept_to is None:
concept_to = concept_from
concept_from = ""
swap_position = float(m.group(4)) if m.group(4) is not None else None
if swap_position is not None:
if swap_position < 1:
swap_position = swap_position * steps
swap_position = int(min(swap_position, steps))
swap_index = None
found_exact_index = False
for i in range(len(prompt_schedule)):
end_step = prompt_schedule[i][0]
prompt_schedule[i][1] += plaintext
if swap_position is not None and swap_index is None:
if swap_position == end_step:
swap_index = i
found_exact_index = True
if swap_position < end_step:
swap_index = i
if swap_index is not None:
if not found_exact_index:
prompt_schedule.insert(swap_index, [swap_position, prompt_schedule[swap_index][1]])
for i in range(len(prompt_schedule)):
end_step = prompt_schedule[i][0]
must_replace = swap_position < end_step
prompt_schedule[i][1] += concept_to if must_replace else concept_from
res.append(prompt_schedule)
cache[prompt] = prompt_schedule
#for t in prompt_schedule:
# print(t)
return res
ScheduledPromptConditioning = namedtuple("ScheduledPromptConditioning", ["end_at_step", "cond"])
ScheduledPromptBatch = namedtuple("ScheduledPromptBatch", ["shape", "schedules"])
def get_learned_conditioning(prompts, steps):
res = []
prompt_schedules = get_learned_conditioning_prompt_schedules(prompts, steps)
cache = {}
for prompt, prompt_schedule in zip(prompts, prompt_schedules):
cached = cache.get(prompt, None)
if cached is not None:
res.append(cached)
texts = [x[1] for x in prompt_schedule]
conds = shared.sd_model.get_learned_conditioning(texts)
cond_schedule = []
for i, (end_at_step, text) in enumerate(prompt_schedule):
cond_schedule.append(ScheduledPromptConditioning(end_at_step, conds[i]))
cache[prompt] = cond_schedule
res.append(cond_schedule)
return ScheduledPromptBatch((len(prompts),) + res[0][0].cond.shape, res)
def reconstruct_cond_batch(c: ScheduledPromptBatch, current_step):
res = torch.zeros(c.shape)
for i, cond_schedule in enumerate(c.schedules):
target_index = 0
for curret_index, (end_at, cond) in enumerate(cond_schedule):
if current_step <= end_at:
target_index = curret_index
break
res[i] = cond_schedule[target_index].cond
return res.to(shared.device)
#get_learned_conditioning_prompt_schedules(["fantasy landscape with a [mountain:lake:0.25] and [an oak:a christmas tree:0.75][ in foreground::0.6][ in background:0.25] [shoddy:masterful:0.5]"], 100)
+3 -3
View File
@@ -42,10 +42,10 @@ def load_scripts(basedir):
if not os.path.isfile(path):
continue
with open(path, "r", encoding="utf8") as file:
text = file.read()
try:
with open(path, "r", encoding="utf8") as file:
text = file.read()
from types import ModuleType
compiled = compile(text, path, 'exec')
module = ModuleType(filename)
+28 -16
View File
@@ -7,6 +7,7 @@ from PIL import Image
import k_diffusion.sampling
import ldm.models.diffusion.ddim
import ldm.models.diffusion.plms
from modules import prompt_parser
from modules.shared import opts, cmd_opts, state
import modules.shared as shared
@@ -53,20 +54,6 @@ def store_latent(decoded):
shared.state.current_image = sample_to_image(decoded)
def p_sample_ddim_hook(sampler_wrapper, x_dec, cond, ts, *args, **kwargs):
if sampler_wrapper.mask is not None:
img_orig = sampler_wrapper.sampler.model.q_sample(sampler_wrapper.init_latent, ts)
x_dec = img_orig * sampler_wrapper.mask + sampler_wrapper.nmask * x_dec
res = sampler_wrapper.orig_p_sample_ddim(x_dec, cond, ts, *args, **kwargs)
if sampler_wrapper.mask is not None:
store_latent(sampler_wrapper.init_latent * sampler_wrapper.mask + sampler_wrapper.nmask * res[1])
else:
store_latent(res[1])
return res
def extended_tdqm(sequence, *args, desc=None, **kwargs):
state.sampling_steps = len(sequence)
@@ -93,6 +80,25 @@ class VanillaStableDiffusionSampler:
self.mask = None
self.nmask = None
self.init_latent = None
self.step = 0
def p_sample_ddim_hook(self, x_dec, cond, ts, unconditional_conditioning, *args, **kwargs):
cond = prompt_parser.reconstruct_cond_batch(cond, self.step)
unconditional_conditioning = prompt_parser.reconstruct_cond_batch(unconditional_conditioning, self.step)
if self.mask is not None:
img_orig = self.sampler.model.q_sample(self.init_latent, ts)
x_dec = img_orig * self.mask + self.nmask * x_dec
res = self.orig_p_sample_ddim(x_dec, cond, ts, unconditional_conditioning=unconditional_conditioning, *args, **kwargs)
if self.mask is not None:
store_latent(self.init_latent * self.mask + self.nmask * res[1])
else:
store_latent(res[1])
self.step += 1
return res
def sample_img2img(self, p, x, noise, conditioning, unconditional_conditioning):
t_enc = int(min(p.denoising_strength, 0.999) * p.steps)
@@ -105,7 +111,7 @@ class VanillaStableDiffusionSampler:
x1 = self.sampler.stochastic_encode(x, torch.tensor([t_enc] * int(x.shape[0])).to(shared.device), noise=noise)
self.sampler.p_sample_ddim = lambda x_dec, cond, ts, *args, **kwargs: p_sample_ddim_hook(self, x_dec, cond, ts, *args, **kwargs)
self.sampler.p_sample_ddim = self.p_sample_ddim_hook
self.mask = p.mask
self.nmask = p.nmask
self.init_latent = p.init_latent
@@ -117,7 +123,7 @@ class VanillaStableDiffusionSampler:
def sample(self, p, x, conditioning, unconditional_conditioning):
for fieldname in ['p_sample_ddim', 'p_sample_plms']:
if hasattr(self.sampler, fieldname):
setattr(self.sampler, fieldname, lambda x_dec, cond, ts, *args, **kwargs: p_sample_ddim_hook(self, x_dec, cond, ts, *args, **kwargs))
setattr(self.sampler, fieldname, self.p_sample_ddim_hook)
self.mask = None
self.nmask = None
self.init_latent = None
@@ -138,8 +144,12 @@ class CFGDenoiser(torch.nn.Module):
self.mask = None
self.nmask = None
self.init_latent = None
self.step = 0
def forward(self, x, sigma, uncond, cond, cond_scale):
cond = prompt_parser.reconstruct_cond_batch(cond, self.step)
uncond = prompt_parser.reconstruct_cond_batch(uncond, self.step)
if shared.batch_cond_uncond:
x_in = torch.cat([x] * 2)
sigma_in = torch.cat([sigma] * 2)
@@ -154,6 +164,8 @@ class CFGDenoiser(torch.nn.Module):
if self.mask is not None:
denoised = self.init_latent * self.mask + self.nmask * denoised
self.step += 1
return denoised
+10 -8
View File
@@ -23,7 +23,7 @@ parser.add_argument("--ckpt", type=str, default=os.path.join(sd_path, sd_model_f
parser.add_argument("--gfpgan-dir", type=str, help="GFPGAN directory", default=('./src/gfpgan' if os.path.exists('./src/gfpgan') else './GFPGAN'))
parser.add_argument("--gfpgan-model", type=str, help="GFPGAN model file name", default='GFPGANv1.3.pth')
parser.add_argument("--no-half", action='store_true', help="do not switch the model to 16-bit floats")
parser.add_argument("--no-progressbar-hiding", action='store_true', help="do not hide progressbar in gradio UI (we hide it because it slows down ML if you have hardware accleration in browser)")
parser.add_argument("--no-progressbar-hiding", action='store_true', help="do not hide progressbar in gradio UI (we hide it because it slows down ML if you have hardware acceleration in browser)")
parser.add_argument("--max-batch-count", type=int, default=16, help="maximum batch count value for the UI")
parser.add_argument("--embeddings-dir", type=str, default=os.path.join(script_path, 'embeddings'), help="embeddings directory for textual inversion (default: embeddings)")
parser.add_argument("--allow-code", action='store_true', help="allow custom script execution from webui")
@@ -45,6 +45,7 @@ parser.add_argument("--ui-settings-file", type=str, help="filename to use for ui
parser.add_argument("--gradio-debug", action='store_true', help="launch gradio with --debug option")
parser.add_argument("--gradio-auth", type=str, help='set gradio authentication like "username:password"; or comma-delimit multiple like "u1:p1,u2:p2,u3:p3"', default=None)
parser.add_argument("--opt-channelslast", action='store_true', help="change memory type for stable diffusion to channels last")
parser.add_argument("--styles-file", type=str, help="filename to use for styles", default=os.path.join(script_path, 'styles.csv'))
cmd_opts = parser.parse_args()
@@ -79,8 +80,8 @@ state = State()
artist_db = modules.artists.ArtistsDatabase(os.path.join(script_path, 'artists.csv'))
styles_filename = os.path.join(script_path, 'styles.csv')
prompt_styles = modules.styles.load_styles(styles_filename)
styles_filename = cmd_opts.styles_file
prompt_styles = modules.styles.StyleDatabase(styles_filename)
interrogator = modules.interrogate.InterrogateModels("interrogate")
@@ -109,10 +110,11 @@ class Options:
"outdir_txt2img_grids": OptionInfo("outputs/txt2img-grids", 'Output directory for txt2img grids', component_args=hide_dirs),
"outdir_img2img_grids": OptionInfo("outputs/img2img-grids", 'Output directory for img2img grids', component_args=hide_dirs),
"outdir_save": OptionInfo("log/images", "Directory for saving images using the Save button", component_args=hide_dirs),
"samples_save": OptionInfo(True, "Save indiviual samples"),
"samples_save": OptionInfo(True, "Always save all generated images"),
"save_selected_only": OptionInfo(False, "When using 'Save' button, only save a single selected image"),
"samples_format": OptionInfo('png', 'File format for individual samples'),
"filter_nsfw": OptionInfo(False, "Filter NSFW content"),
"grid_save": OptionInfo(True, "Save image grids"),
"grid_save": OptionInfo(True, "Always save all generated image grids"),
"return_grid": OptionInfo(True, "Show grid in results for web"),
"grid_format": OptionInfo('png', 'File format for grids'),
"grid_extended_filename": OptionInfo(False, "Add extended info (seed, prompt) to filename when saving grid"),
@@ -124,7 +126,7 @@ class Options:
"add_model_hash_to_info": OptionInfo(False, "Add model hash to generation information"),
"img2img_color_correction": OptionInfo(False, "Apply color correction to img2img results to match original colors."),
"font": OptionInfo("", "Font for image grids that have text"),
"enable_emphasis": OptionInfo(True, "Use (text) to make model pay more attention to text text and [text] to make it pay less attention"),
"enable_emphasis": OptionInfo(True, "Use (text) to make model pay more attention to text and [text] to make it pay less attention"),
"save_txt": OptionInfo(False, "Create a text file next to every image with generation parameters."),
"ESRGAN_tile": OptionInfo(192, "Tile size for upscaling. 0 = no tiling.", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}),
"ESRGAN_tile_overlap": OptionInfo(8, "Tile overlap, in pixels for upscaling. Low values = visible seam.", gr.Slider, {"minimum": 0, "maximum": 48, "step": 1}),
@@ -140,8 +142,8 @@ class Options:
"interrogate_keep_models_in_memory": OptionInfo(False, "Interrogate: keep models in VRAM"),
"interrogate_use_builtin_artists": OptionInfo(True, "Interrogate: use artists from artists.csv"),
"interrogate_clip_num_beams": OptionInfo(1, "Interrogate: num_beams for BLIP", gr.Slider, {"minimum": 1, "maximum": 16, "step": 1}),
"interrogate_clip_min_length": OptionInfo(24, "Interrogate: minimum descripton length (excluding artists, etc..)", gr.Slider, {"minimum": 1, "maximum": 128, "step": 1}),
"interrogate_clip_max_length": OptionInfo(48, "Interrogate: maximum descripton length", gr.Slider, {"minimum": 1, "maximum": 256, "step": 1}),
"interrogate_clip_min_length": OptionInfo(24, "Interrogate: minimum description length (excluding artists, etc..)", gr.Slider, {"minimum": 1, "maximum": 128, "step": 1}),
"interrogate_clip_max_length": OptionInfo(48, "Interrogate: maximum description length", gr.Slider, {"minimum": 1, "maximum": 256, "step": 1}),
"interrogate_clip_dict_limit": OptionInfo(1500, "Interrogate: maximum number of lines in text file (0 = No limit)"),
}
+51 -33
View File
@@ -20,49 +20,67 @@ class PromptStyle(typing.NamedTuple):
negative_prompt: str
def load_styles(path: str) -> dict[str, PromptStyle]:
styles = {"None": PromptStyle("None", "", "")}
def merge_prompts(style_prompt: str, prompt: str) -> str:
if "{prompt}" in style_prompt:
res = style_prompt.replace("{prompt}", prompt)
else:
parts = filter(None, (prompt.strip(), style_prompt.strip()))
res = ", ".join(parts)
return res
def apply_styles_to_prompt(prompt, styles):
for style in styles:
prompt = merge_prompts(style, prompt)
return prompt
class StyleDatabase:
def __init__(self, path: str):
self.no_style = PromptStyle("None", "", "")
self.styles = {"None": self.no_style}
if not os.path.exists(path):
return
if os.path.exists(path):
with open(path, "r", encoding="utf8", newline='') as file:
reader = csv.DictReader(file)
for row in reader:
# Support loading old CSV format with "name, text"-columns
prompt = row["prompt"] if "prompt" in row else row["text"]
negative_prompt = row.get("negative_prompt", "")
styles[row["name"]] = PromptStyle(row["name"], prompt, negative_prompt)
self.styles[row["name"]] = PromptStyle(row["name"], prompt, negative_prompt)
return styles
def apply_styles_to_prompt(self, prompt, styles):
return apply_styles_to_prompt(prompt, [self.styles.get(x, self.no_style).prompt for x in styles])
def apply_negative_styles_to_prompt(self, prompt, styles):
return apply_styles_to_prompt(prompt, [self.styles.get(x, self.no_style).negative_prompt for x in styles])
def merge_prompts(style_prompt: str, prompt: str) -> str:
parts = filter(None, (prompt.strip(), style_prompt.strip()))
return ", ".join(parts)
def apply_styles(self, p: StableDiffusionProcessing) -> None:
if isinstance(p.prompt, list):
p.prompt = [self.apply_styles_to_prompt(prompt, p.styles) for prompt in p.prompt]
else:
p.prompt = self.apply_styles_to_prompt(p.prompt, p.styles)
if isinstance(p.negative_prompt, list):
p.negative_prompt = [self.apply_negative_styles_to_prompt(prompt, p.styles) for prompt in p.negative_prompt]
else:
p.negative_prompt = self.apply_negative_styles_to_prompt(p.negative_prompt, p.styles)
def apply_style(processing: StableDiffusionProcessing, style: PromptStyle) -> None:
if isinstance(processing.prompt, list):
processing.prompt = [merge_prompts(style.prompt, p) for p in processing.prompt]
else:
processing.prompt = merge_prompts(style.prompt, processing.prompt)
def save_styles(self, path: str) -> None:
# Write to temporary file first, so we don't nuke the file if something goes wrong
fd, temp_path = tempfile.mkstemp(".csv")
with os.fdopen(fd, "w", encoding="utf8", newline='') as file:
# _fields is actually part of the public API: typing.NamedTuple is a replacement for collections.NamedTuple,
# and collections.NamedTuple has explicit documentation for accessing _fields. Same goes for _asdict()
writer = csv.DictWriter(file, fieldnames=PromptStyle._fields)
writer.writeheader()
writer.writerows(style._asdict() for k, style in self.styles.items())
if isinstance(processing.negative_prompt, list):
processing.negative_prompt = [merge_prompts(style.negative_prompt, p) for p in processing.negative_prompt]
else:
processing.negative_prompt = merge_prompts(style.negative_prompt, processing.negative_prompt)
def save_styles(path: str, styles: abc.Iterable[PromptStyle]) -> None:
# Write to temporary file first, so we don't nuke the file if something goes wrong
fd, temp_path = tempfile.mkstemp(".csv")
with os.fdopen(fd, "w", encoding="utf8", newline='') as file:
# _fields is actually part of the public API: typing.NamedTuple is a replacement for collections.NamedTuple,
# and collections.NamedTuple has explicit documentation for accessing _fields. Same goes for _asdict()
writer = csv.DictWriter(file, fieldnames=PromptStyle._fields)
writer.writeheader()
writer.writerows(style._asdict() for style in styles)
# Always keep a backup file around
if os.path.exists(path):
shutil.move(path, path + ".bak")
shutil.move(temp_path, path)
# Always keep a backup file around
if os.path.exists(path):
shutil.move(path, path + ".bak")
shutil.move(temp_path, path)
+2 -2
View File
@@ -6,13 +6,13 @@ import modules.processing as processing
from modules.ui import plaintext_to_html
def txt2img(prompt: str, negative_prompt: str, prompt_style: str, steps: int, sampler_index: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, height: int, width: int, *args):
def txt2img(prompt: str, negative_prompt: str, prompt_style: str, prompt_style2: str, steps: int, sampler_index: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, height: int, width: int, *args):
p = StableDiffusionProcessingTxt2Img(
sd_model=shared.sd_model,
outpath_samples=opts.outdir_samples or opts.outdir_txt2img_samples,
outpath_grids=opts.outdir_grids or opts.outdir_txt2img_grids,
prompt=prompt,
prompt_style=prompt_style,
styles=[prompt_style, prompt_style2],
negative_prompt=negative_prompt,
seed=seed,
subseed=subseed,
+85 -34
View File
@@ -80,7 +80,7 @@ def send_gradio_gallery_to_image(x):
return image_from_url_text(x[0])
def save_files(js_data, images):
def save_files(js_data, images, index):
import csv
os.makedirs(opts.outdir_save, exist_ok=True)
@@ -88,6 +88,10 @@ def save_files(js_data, images):
filenames = []
data = json.loads(js_data)
if index > -1 and opts.save_selected_only and (index > 0 or not opts.return_grid): # ensures we are looking at a specific non-grid picture, and we have save_selected_only
images = [images[index]]
data["seed"] += (index - 1 if opts.return_grid else index)
with open(os.path.join(opts.outdir_save, "log.csv"), "a", encoding="utf8", newline='') as file:
at_start = file.tell() == 0
@@ -233,13 +237,20 @@ def add_style(name: str, prompt: str, negative_prompt: str):
return [gr_show(), gr_show()]
style = modules.styles.PromptStyle(name, prompt, negative_prompt)
shared.prompt_styles[style.name] = style
shared.prompt_styles.styles[style.name] = style
# Save all loaded prompt styles: this allows us to update the storage format in the future more easily, because we
# reserialize all styles every time we save them
modules.styles.save_styles(shared.styles_filename, shared.prompt_styles.values())
shared.prompt_styles.save_styles(shared.styles_filename)
update = {"visible": True, "choices": list(shared.prompt_styles), "__type__": "update"}
return [update, update]
update = {"visible": True, "choices": list(shared.prompt_styles.styles), "__type__": "update"}
return [update, update, update, update]
def apply_styles(prompt, prompt_neg, style1_name, style2_name):
prompt = shared.prompt_styles.apply_styles_to_prompt(prompt, [style1_name, style2_name])
prompt_neg = shared.prompt_styles.apply_negative_styles_to_prompt(prompt_neg, [style1_name, style2_name])
return [gr.Textbox.update(value=prompt), gr.Textbox.update(value=prompt_neg), gr.Dropdown.update(value="None"), gr.Dropdown.update(value="None")]
def interrogate(image):
@@ -247,15 +258,46 @@ def interrogate(image):
return gr_show(True) if prompt is None else prompt
def create_toprow(is_img2img):
with gr.Row(elem_id="toprow"):
with gr.Column(scale=4):
with gr.Row():
with gr.Column(scale=8):
with gr.Row():
prompt = gr.Textbox(label="Prompt", elem_id="prompt", show_label=False, placeholder="Prompt", lines=2)
roll = gr.Button('Roll', elem_id="roll", visible=len(shared.artist_db.artists) > 0)
with gr.Column(scale=1, elem_id="style_pos_col"):
prompt_style = gr.Dropdown(label="Style 1", elem_id="style_index", choices=[k for k, v in shared.prompt_styles.styles.items()], value=next(iter(shared.prompt_styles.styles.keys())), visible=len(shared.prompt_styles.styles) > 1)
with gr.Row():
with gr.Column(scale=8):
negative_prompt = gr.Textbox(label="Negative prompt", elem_id="negative_prompt", show_label=False, placeholder="Negative prompt", lines=2)
with gr.Column(scale=1, elem_id="style_neg_col"):
prompt_style2 = gr.Dropdown(label="Style 2", elem_id="style2_index", choices=[k for k, v in shared.prompt_styles.styles.items()], value=next(iter(shared.prompt_styles.styles.keys())), visible=len(shared.prompt_styles.styles) > 1)
with gr.Column(scale=1):
with gr.Row():
submit = gr.Button('Generate', elem_id="generate", variant='primary')
with gr.Row():
if is_img2img:
interrogate = gr.Button('Interrogate', elem_id="interrogate")
else:
interrogate = None
prompt_style_apply = gr.Button('Apply style', elem_id="style_apply")
save_style = gr.Button('Create style', elem_id="style_create")
check_progress = gr.Button('Check progress', elem_id="check_progress", visible=False)
return prompt, roll, prompt_style, negative_prompt, prompt_style2, submit, interrogate, prompt_style_apply, save_style, check_progress
def create_ui(txt2img, img2img, run_extras, run_pnginfo):
with gr.Blocks(analytics_enabled=False) as txt2img_interface:
with gr.Row(elem_id="toprow"):
txt2img_prompt = gr.Textbox(label="Prompt", elem_id="txt2img_prompt", show_label=False, placeholder="Prompt", lines=1)
txt2img_negative_prompt = gr.Textbox(label="Negative prompt", elem_id="txt2img_negative_prompt", show_label=False, placeholder="Negative prompt", lines=1)
txt2img_prompt_style = gr.Dropdown(label="Style", show_label=False, elem_id="style_index", choices=[k for k, v in shared.prompt_styles.items()], value=next(iter(shared.prompt_styles.keys())), visible=len(shared.prompt_styles) > 1)
roll = gr.Button('Roll', elem_id="txt2img_roll", visible=len(shared.artist_db.artists) > 0)
submit = gr.Button('Generate', elem_id="txt2img_generate", variant='primary')
check_progress = gr.Button('Check progress', elem_id="check_progress", visible=False)
txt2img_prompt, roll, txt2img_prompt_style, txt2img_negative_prompt, txt2img_prompt_style2, submit, _, txt2img_prompt_style_apply, txt2img_save_style, check_progress = create_toprow(is_img2img=False)
with gr.Row().style(equal_height=False):
with gr.Column(variant='panel'):
@@ -286,7 +328,6 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
txt2img_preview = gr.Image(elem_id='txt2img_preview', visible=False)
txt2img_gallery = gr.Gallery(label='Output', elem_id='txt2img_gallery').style(grid=4)
with gr.Group():
with gr.Row():
save = gr.Button('Save')
@@ -294,7 +335,6 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
send_to_inpaint = gr.Button('Send to inpaint')
send_to_extras = gr.Button('Send to extras')
interrupt = gr.Button('Interrupt')
txt2img_save_style = gr.Button('Save prompt as style')
progressbar = gr.HTML(elem_id="progressbar")
@@ -302,7 +342,6 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
html_info = gr.HTML()
generation_info = gr.Textbox(visible=False)
txt2img_args = dict(
fn=txt2img,
_js="submit",
@@ -310,6 +349,7 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
txt2img_prompt,
txt2img_negative_prompt,
txt2img_prompt_style,
txt2img_prompt_style2,
steps,
sampler_index,
restore_faces,
@@ -339,7 +379,6 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
outputs=[progressbar, txt2img_preview, txt2img_preview],
)
interrupt.click(
fn=lambda: shared.state.interrupt(),
inputs=[],
@@ -348,9 +387,11 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
save.click(
fn=wrap_gradio_call(save_files),
_js = "(x, y, z) => [x, y, selected_gallery_index()]",
inputs=[
generation_info,
txt2img_gallery,
html_info
],
outputs=[
html_info,
@@ -370,18 +411,12 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
)
with gr.Blocks(analytics_enabled=False) as img2img_interface:
with gr.Row(elem_id="toprow"):
img2img_prompt = gr.Textbox(label="Prompt", elem_id="img2img_prompt", show_label=False, placeholder="Prompt", lines=1)
img2img_negative_prompt = gr.Textbox(label="Negative prompt", elem_id="img2img_negative_prompt", show_label=False, placeholder="Negative prompt", lines=1)
img2img_prompt_style = gr.Dropdown(label="Style", show_label=False, elem_id="style_index", choices=[k for k, v in shared.prompt_styles.items()], value=next(iter(shared.prompt_styles.keys())), visible=len(shared.prompt_styles) > 1)
img2img_interrogate = gr.Button('Interrogate', elem_id="img2img_interrogate", variant='primary')
submit = gr.Button('Generate', elem_id="img2img_generate", variant='primary')
check_progress = gr.Button('Check progress', elem_id="check_progress", visible=False)
img2img_prompt, roll, img2img_prompt_style, img2img_negative_prompt, img2img_prompt_style2, submit, img2img_interrogate, img2img_prompt_style_apply, img2img_save_style, check_progress = create_toprow(is_img2img=True)
with gr.Row().style(equal_height=False):
with gr.Column(variant='panel'):
with gr.Group():
switch_mode = gr.Radio(label='Mode', elem_id="img2img_mode", choices=['Redraw whole image', 'Inpaint a part of image', 'Loopback', 'SD upscale'], value='Redraw whole image', type="index", show_label=False)
switch_mode = gr.Radio(label='Mode', elem_id="img2img_mode", choices=['Redraw whole image', 'Inpaint a part of image', 'SD upscale'], value='Redraw whole image', type="index", show_label=False)
init_img = gr.Image(label="Image for img2img", source="upload", interactive=True, type="pil")
init_img_with_mask = gr.Image(label="Image for inpainting with mask", elem_id="img2maskimg", source="upload", interactive=True, type="pil", tool="sketch", visible=False, image_mode="RGBA")
init_mask = gr.Image(label="Mask", source="upload", interactive=True, type="pil", visible=False)
@@ -415,7 +450,6 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
with gr.Group():
cfg_scale = gr.Slider(minimum=1.0, maximum=30.0, step=0.5, label='CFG Scale', value=7.0)
denoising_strength = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label='Denoising strength', value=0.75)
denoising_strength_change_factor = gr.Slider(minimum=0.9, maximum=1.1, step=0.01, label='Denoising strength change factor', value=1, visible=False)
with gr.Group():
width = gr.Slider(minimum=64, maximum=2048, step=64, label="Width", value=512)
@@ -449,8 +483,7 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
def apply_mode(mode, uploadmask):
is_classic = mode == 0
is_inpaint = mode == 1
is_loopback = mode == 2
is_upscale = mode == 3
is_upscale = mode == 2
return {
init_img: gr_show(not is_inpaint or (is_inpaint and uploadmask == 1)),
@@ -460,12 +493,10 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
mask_mode: gr_show(is_inpaint),
mask_blur: gr_show(is_inpaint),
inpainting_fill: gr_show(is_inpaint),
batch_size: gr_show(not is_loopback),
sd_upscale_upscaler_name: gr_show(is_upscale),
sd_upscale_overlap: gr_show(is_upscale),
inpaint_full_res: gr_show(is_inpaint),
inpainting_mask_invert: gr_show(is_inpaint),
denoising_strength_change_factor: gr_show(is_loopback),
img2img_interrogate: gr_show(not is_inpaint),
}
@@ -480,12 +511,10 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
mask_mode,
mask_blur,
inpainting_fill,
batch_size,
sd_upscale_upscaler_name,
sd_upscale_overlap,
inpaint_full_res,
inpainting_mask_invert,
denoising_strength_change_factor,
img2img_interrogate,
]
)
@@ -511,6 +540,7 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
img2img_prompt,
img2img_negative_prompt,
img2img_prompt_style,
img2img_prompt_style2,
init_img,
init_img_with_mask,
init_mask,
@@ -526,7 +556,6 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
batch_size,
cfg_scale,
denoising_strength,
denoising_strength_change_factor,
seed,
subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w,
height,
@@ -568,9 +597,11 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
save.click(
fn=wrap_gradio_call(save_files),
_js = "(x, y, z) => [x, y, selected_gallery_index()]",
inputs=[
generation_info,
img2img_gallery,
html_info
],
outputs=[
html_info,
@@ -579,15 +610,35 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
]
)
roll.click(
fn=roll_artist,
inputs=[
img2img_prompt,
],
outputs=[
img2img_prompt,
]
)
prompts = [(txt2img_prompt, txt2img_negative_prompt), (img2img_prompt, img2img_negative_prompt)]
style_dropdowns = [(txt2img_prompt_style, txt2img_prompt_style2), (img2img_prompt_style, img2img_prompt_style2)]
dummy_component = gr.Label(visible=False)
for button, (prompt, negative_prompt) in zip([txt2img_save_style, img2img_save_style], [(txt2img_prompt, txt2img_negative_prompt), (img2img_prompt, img2img_negative_prompt)]):
for button, (prompt, negative_prompt) in zip([txt2img_save_style, img2img_save_style], prompts):
button.click(
fn=add_style,
_js="ask_for_style_name",
# Have to pass empty dummy component here, because the JavaScript and Python function have to accept
# the same number of parameters, but we only know the style-name after the JavaScript prompt
inputs=[dummy_component, prompt, negative_prompt],
outputs=[txt2img_prompt_style, img2img_prompt_style],
outputs=[txt2img_prompt_style, img2img_prompt_style, txt2img_prompt_style2, img2img_prompt_style2],
)
for button, (prompt, negative_prompt), (style1, style2) in zip([txt2img_prompt_style_apply, img2img_prompt_style_apply], prompts, style_dropdowns):
button.click(
fn=apply_styles,
inputs=[prompt, negative_prompt, style1, style2],
outputs=[prompt, negative_prompt, style1, style2],
)
with gr.Blocks(analytics_enabled=False) as extras_interface:
+10 -2
View File
@@ -13,7 +13,6 @@ titles = {
"Seed": "A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result",
"Inpaint a part of image": "Draw a mask over an image, and the script will regenerate the masked area with content according to prompt",
"Loopback": "Process an image, use it as an input, repeat. Batch count determins number of iterations.",
"SD upscale": "Upscale image normally, split result into tiles, improve each tile using img2img, merge whole image back",
"Just resize": "Resize image to target resolution. Unless height and width match, you will get incorrect aspect ratio.",
@@ -54,10 +53,19 @@ titles = {
"Resize seed from height": "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution",
"Resize seed from width": "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution",
"Interrogate": "Reconstruct frompt from existing image and put it into the prompt field.",
"Interrogate": "Reconstruct prompt from existing image and put it into the prompt field.",
"Images filename pattern": "Use following tags to define how filenames for images are chosen: [steps], [cfg], [prompt], [prompt_spaces], [width], [height], [sampler], [seed], [model_hash], [prompt_words], [date]; leave empty for default.",
"Directory name pattern": "Use following tags to define how subdirectories for images and grids are chosen: [steps], [cfg], [prompt], [prompt_spaces], [width], [height], [sampler], [seed], [model_hash], [prompt_words], [date]; leave empty for default.",
"Loopback": "Process an image, use it as an input, repeat.",
"Loops": "How many times to repeat processing an image and using it as input for the next iteration",
"Style 1": "Style to apply; styles have components for both positive and negative prompts and apply to both",
"Style 2": "Style to apply; styles have components for both positive and negative prompts and apply to both",
"Apply style": "Insert selected styles into prompt fields",
"Create style": "Save current prompts as a style. If you add the token {prompt} to the text, the style use that as placeholder for your prompt when you use the style in the future.",
}
function gradioApp(){
+22 -10
View File
@@ -1,3 +1,5 @@
from collections import namedtuple
import numpy as np
from tqdm import trange
@@ -56,9 +58,14 @@ def find_noise_for_image(p, cond, uncond, cfg_scale, steps):
return x / x.std()
cache = [None, None, None, None, None]
Cached = namedtuple("Cached", ["noise", "cfg_scale", "steps", "latent", "original_prompt"])
class Script(scripts.Script):
def __init__(self):
self.cache = None
def title(self):
return "img2img alternative test"
@@ -67,7 +74,7 @@ class Script(scripts.Script):
def ui(self, is_img2img):
original_prompt = gr.Textbox(label="Original prompt", lines=1)
cfg = gr.Slider(label="Decode CFG scale", minimum=0.1, maximum=3.0, step=0.1, value=1.0)
cfg = gr.Slider(label="Decode CFG scale", minimum=0.0, maximum=15.0, step=0.1, value=1.0)
st = gr.Slider(label="Decode steps", minimum=1, maximum=150, step=1, value=50)
return [original_prompt, cfg, st]
@@ -77,19 +84,18 @@ class Script(scripts.Script):
p.batch_count = 1
def sample_extra(x, conditioning, unconditional_conditioning):
lat = tuple([int(x*10) for x in p.init_latent.cpu().numpy().flatten().tolist()])
lat = (p.init_latent.cpu().numpy() * 10).astype(int)
if cache[0] is not None and cache[1] == cfg and cache[2] == st and len(cache[3]) == len(lat) and sum(np.array(cache[3])-np.array(lat)) < 100 and cache[4] == original_prompt:
noise = cache[0]
same_params = self.cache is not None and self.cache.cfg_scale == cfg and self.cache.steps == st and self.cache.original_prompt == original_prompt
same_everything = same_params and self.cache.latent.shape == lat.shape and np.abs(self.cache.latent-lat).sum() < 100
if same_everything:
noise = self.cache.noise
else:
shared.state.job_count += 1
cond = p.sd_model.get_learned_conditioning(p.batch_size * [original_prompt])
noise = find_noise_for_image(p, cond, unconditional_conditioning, cfg, st)
cache[0] = noise
cache[1] = cfg
cache[2] = st
cache[3] = lat
cache[4] = original_prompt
self.cache = Cached(noise, cfg, st, lat, original_prompt)
sampler = samplers[p.sampler_index].constructor(p.sd_model)
@@ -98,6 +104,12 @@ class Script(scripts.Script):
p.sample = sample_extra
p.extra_generation_params = {
"Decode prompt": original_prompt,
"Decode CFG scale": cfg,
"Decode steps": st,
}
processed = processing.process_images(p)
return processed
+78
View File
@@ -0,0 +1,78 @@
import numpy as np
from tqdm import trange
import modules.scripts as scripts
import gradio as gr
from modules import processing, shared, sd_samplers, images
from modules.processing import Processed
from modules.sd_samplers import samplers
from modules.shared import opts, cmd_opts, state
class Script(scripts.Script):
def title(self):
return "Loopback"
def show(self, is_img2img):
return is_img2img
def ui(self, is_img2img):
loops = gr.Slider(minimum=1, maximum=32, step=1, label='Loops', value=4)
denoising_strength_change_factor = gr.Slider(minimum=0.9, maximum=1.1, step=0.01, label='Denoising strength change factor', value=1)
return [loops, denoising_strength_change_factor]
def run(self, p, loops, denoising_strength_change_factor):
processing.fix_seed(p)
batch_count = p.n_iter
p.extra_generation_params = {
"Denoising strength change factor": denoising_strength_change_factor,
}
p.batch_size = 1
p.n_iter = 1
output_images, info = None, None
initial_seed = None
initial_info = None
grids = []
all_images = []
state.job_count = loops * batch_count
for n in range(batch_count):
history = []
for i in range(loops):
p.n_iter = 1
p.batch_size = 1
p.do_not_save_grid = True
state.job = f"Iteration {i + 1}/{loops}, batch {n + 1}/{batch_count}"
processed = processing.process_images(p)
if initial_seed is None:
initial_seed = processed.seed
initial_info = processed.info
init_img = processed.images[0]
p.init_images = [init_img]
p.seed = processed.seed + 1
p.denoising_strength = min(max(p.denoising_strength * denoising_strength_change_factor, 0.1), 1)
history.append(processed.images[0])
grid = images.image_grid(history, rows=1)
if opts.grid_save:
images.save_image(grid, p.outpath_grids, "grid", initial_seed, p.prompt, opts.grid_format, info=info, short_filename=not opts.grid_extended_filename, grid=True, p=p)
grids.append(grid)
all_images += history
if opts.return_grid:
all_images = grids + all_images
processed = Processed(p, all_images, initial_seed, initial_info)
return processed
+1 -1
View File
@@ -82,6 +82,6 @@ class Script(scripts.Script):
processed.images.insert(0, grid)
if opts.grid_save:
images.save_image(processed.images[0], p.outpath_grids, "prompt_matrix", prompt=original_prompt, seed=processed.seed, p=p)
images.save_image(processed.images[0], p.outpath_grids, "prompt_matrix", prompt=original_prompt, seed=processed.seed, grid=True, p=p)
return processed
+30 -9
View File
@@ -78,7 +78,7 @@ axis_options = [
]
def draw_xy_grid(xs, ys, x_label, y_label, cell):
def draw_xy_grid(p, xs, ys, x_label, y_label, cell, draw_legend):
res = []
ver_texts = [[images.GridAnnotation(y_label(y))] for y in ys]
@@ -86,7 +86,7 @@ def draw_xy_grid(xs, ys, x_label, y_label, cell):
first_pocessed = None
state.job_count = len(xs) * len(ys)
state.job_count = len(xs) * len(ys) * p.n_iter
for iy, y in enumerate(ys):
for ix, x in enumerate(xs):
@@ -99,7 +99,8 @@ def draw_xy_grid(xs, ys, x_label, y_label, cell):
res.append(processed.images[0])
grid = images.image_grid(res, rows=len(ys))
grid = images.draw_grid_annotations(grid, res[0].width, res[0].height, hor_texts, ver_texts)
if draw_legend:
grid = images.draw_grid_annotations(grid, res[0].width, res[0].height, hor_texts, ver_texts)
first_pocessed.images = [grid]
@@ -109,6 +110,9 @@ def draw_xy_grid(xs, ys, x_label, y_label, cell):
re_range = re.compile(r"\s*([+-]?\s*\d+)\s*-\s*([+-]?\s*\d+)(?:\s*\(([+-]\d+)\s*\))?\s*")
re_range_float = re.compile(r"\s*([+-]?\s*\d+(?:.\d*)?)\s*-\s*([+-]?\s*\d+(?:.\d*)?)(?:\s*\(([+-]\d+(?:.\d*)?)\s*\))?\s*")
re_range_count = re.compile(r"\s*([+-]?\s*\d+)\s*-\s*([+-]?\s*\d+)(?:\s*\[(\d+)\s*\])?\s*")
re_range_count_float = re.compile(r"\s*([+-]?\s*\d+(?:.\d*)?)\s*-\s*([+-]?\s*\d+(?:.\d*)?)(?:\s*\[(\d+(?:.\d*)?)\s*\])?\s*")
class Script(scripts.Script):
def title(self):
return "X/Y plot"
@@ -123,13 +127,14 @@ class Script(scripts.Script):
with gr.Row():
y_type = gr.Dropdown(label="Y type", choices=[x.label for x in current_axis_options], value=current_axis_options[4].label, visible=False, type="index", elem_id="y_type")
y_values = gr.Textbox(label="Y values", visible=False, lines=1)
draw_legend = gr.Checkbox(label='Draw legend', value=True)
return [x_type, x_values, y_type, y_values, draw_legend]
return [x_type, x_values, y_type, y_values]
def run(self, p, x_type, x_values, y_type, y_values):
def run(self, p, x_type, x_values, y_type, y_values, draw_legend):
modules.processing.fix_seed(p)
p.batch_size = 1
p.batch_count = 1
def process_axis(opt, vals):
valslist = [x.strip() for x in vals.split(",")]
@@ -139,6 +144,7 @@ class Script(scripts.Script):
for val in valslist:
m = re_range.fullmatch(val)
mc = re_range_count.fullmatch(val)
if m is not None:
start = int(m.group(1))
@@ -146,6 +152,12 @@ class Script(scripts.Script):
step = int(m.group(3)) if m.group(3) is not None else 1
valslist_ext += list(range(start, end, step))
elif mc is not None:
start = int(mc.group(1))
end = int(mc.group(2))
num = int(mc.group(3)) if mc.group(3) is not None else 1
valslist_ext += [int(x) for x in np.linspace(start = start, stop = end, num = num).tolist()]
else:
valslist_ext.append(val)
@@ -155,12 +167,19 @@ class Script(scripts.Script):
for val in valslist:
m = re_range_float.fullmatch(val)
mc = re_range_count_float.fullmatch(val)
if m is not None:
start = float(m.group(1))
end = float(m.group(2))
step = float(m.group(3)) if m.group(3) is not None else 1
valslist_ext += np.arange(start, end + step, step).tolist()
elif mc is not None:
start = float(mc.group(1))
end = float(mc.group(2))
num = int(mc.group(3)) if mc.group(3) is not None else 1
valslist_ext += np.linspace(start = start, stop = end, num = num).tolist()
else:
valslist_ext.append(val)
@@ -184,14 +203,16 @@ class Script(scripts.Script):
return process_images(pc)
processed = draw_xy_grid(
p,
xs=xs,
ys=ys,
x_label=lambda x: x_opt.format_value(p, x_opt, x),
y_label=lambda y: y_opt.format_value(p, y_opt, y),
cell=cell
cell=cell,
draw_legend=draw_legend
)
if opts.grid_save:
images.save_image(processed.images[0], p.outpath_grids, "xy_grid", prompt=p.prompt, seed=processed.seed, p=p)
images.save_image(processed.images[0], p.outpath_grids, "xy_grid", prompt=p.prompt, seed=processed.seed, grid=True, p=p)
return processed
+27 -14
View File
@@ -1,12 +1,15 @@
.output-html p {margin: 0 0.5em;}
.performance { font-size: 0.85em; color: #444; }
#txt2img_generate, #img2img_generate{
max-width: 13em;
#generate{
min-height: 4.5em;
}
#img2img_interrogate{
max-width: 10em;
#txt2img_gallery, #img2img_gallery{
min-height: 768px;
}
#txt2img_gallery img, #img2img_gallery img{
object-fit: scale-down;
}
#subseed_show{
@@ -18,21 +21,33 @@
height: 100%;
}
#txt2img_roll{
#roll{
min-width: 1em;
max-width: 4em;
margin: 0.5em;
}
#style_index{
min-width: 9em;
max-width: 9em;
padding-left: 0;
padding-right: 0;
#style_apply, #style_create, #interrogate{
margin: 0.75em 0.25em 0.25em 0.25em;
min-width: 3em;
}
#style_pos_col, #style_neg_col{
min-width: 4em !important;
}
#style_index, #style2_index{
margin-top: 1em;
}
.gr-form{
background: transparent;
}
#toprow div{
border: none;
gap: 0;
background: transparent;
}
#resize_mode{
@@ -43,10 +58,10 @@ button{
align-self: stretch !important;
}
#img2img_prompt, #txt2img_prompt, #img2img_negative_prompt, #txt2img_negative_prompt{
#prompt, #negative_prompt{
border: none !important;
}
#img2img_prompt textarea, #txt2img_prompt textarea, #img2img_negative_prompt textarea, #txt2img_negative_prompt textarea{
#prompt textarea, #negative_prompt textarea{
border: none !important;
}
@@ -134,8 +149,6 @@ input[type="range"]{
}
#txt2img_negative_prompt, #img2img_negative_prompt{
flex: 0.3;
min-width: 10em;
}
.progressDiv{
+40
View File
@@ -0,0 +1,40 @@
#!/bin/bash
###########################################
# Change the variables below to your need:#
###########################################
# Install directory without trailing slash
install_dir="/home/$(whoami)"
# Name of the subdirectory (defaults to stable-diffusion-webui)
clone_dir="stable-diffusion-webui"
# Commandline arguments for webui.py, for example: export COMMANDLINE_ARGS=(--medvram --opt-split-attention)
export COMMANDLINE_ARGS=()
# python3 executable
python_cmd="python3"
# git executable
export GIT=""
# python3 venv without trailing slash (defaults to ${install_dir}/${clone_dir}/venv)
venv_dir="venv"
# install command for torch
export TORCH_COMMAND=(python3 -m pip install torch==1.12.1+cu113 --extra-index-url https://download.pytorch.org/whl/cu113)
# Requirements file to use for stable-diffusion-webui
export REQS_FILE=""
# Fixed git repos
export K_DIFFUSION_PACKAGE=""
export GFPGAN_PACKAGE=""
# Fixed git commits
export STABLE_DIFFUSION_COMMIT_HASH=""
export TAMING_TRANSFORMERS_COMMIT_HASH=""
export CODEFORMER_COMMIT_HASH=""
export BLIP_COMMIT_HASH=""
###########################################
+139
View File
@@ -0,0 +1,139 @@
#!/bin/bash
#################################################
# Please do not make any changes to this file, #
# change the variables in webui-user.sh instead #
#################################################
# Read variables from webui-user.sh
# shellcheck source=/dev/null
if [[ -f webui-user.sh ]]
then
source ./webui-user.sh
fi
# Set defaults
# Install directory without trailing slash
if [[ -z "${install_dir}" ]]
then
install_dir="/home/$(whoami)"
fi
# Name of the subdirectory (defaults to stable-diffusion-webui)
if [[ -z "${clone_dir}" ]]
then
clone_dir="stable-diffusion-webui"
fi
# python3 executable
if [[ -z "${python_cmd}" ]]
then
python_cmd="python3"
fi
# git executable
if [[ -z "${GIT}" ]]
then
export GIT="git"
fi
# python3 venv without trailing slash (defaults to ${install_dir}/${clone_dir}/venv)
if [[ -z "${venv_dir}" ]]
then
venv_dir="venv"
fi
# install command for torch
if [[ -z "${TORCH_COMMAND}" ]]
then
export TORCH_COMMAND=(python3 -m pip install torch==1.12.1+cu113 --extra-index-url https://download.pytorch.org/whl/cu113)
fi
# Do not reinstall existing pip packages on Debian/Ubuntu
export PIP_IGNORE_INSTALLED=0
# Pretty print
delimiter="################################################################"
printf "\n%s\n" "${delimiter}"
printf "\e[1m\e[32mInstall script for stable-diffusion + Web UI\n"
printf "\e[1m\e[34mTested on Debian 11 (Bullseye)\e[0m"
printf "\n%s\n" "${delimiter}"
# Do not run as root
if [[ $(id -u) -eq 0 ]]
then
printf "\n%s\n" "${delimiter}"
printf "\e[1m\e[31mERROR: This script must not be launched as root, aborting...\e[0m"
printf "\n%s\n" "${delimiter}"
exit 1
else
printf "\n%s\n" "${delimiter}"
printf "Running on \e[1m\e[32m%s\e[0m user" "$(whoami)"
printf "\n%s\n" "${delimiter}"
fi
if [[ -d .git ]]
then
printf "\n%s\n" "${delimiter}"
printf "Repo already cloned, using it as install directory"
printf "\n%s\n" "${delimiter}"
install_dir="${PWD}/../"
clone_dir="${PWD##*/}"
fi
# Check prequisites
for preq in git python3
do
if ! hash "${preq}" &>/dev/null
then
printf "\n%s\n" "${delimiter}"
printf "\e[1m\e[31mERROR: %s is not installed, aborting...\e[0m" "${preq}"
printf "\n%s\n" "${delimiter}"
exit 1
fi
done
if ! "${python_cmd}" -c "import venv" &>/dev/null
then
printf "\n%s\n" "${delimiter}"
printf "\e[1m\e[31mERROR: python3-venv is not installed, aborting...\e[0m"
printf "\n%s\n" "${delimiter}"
exit 1
fi
printf "\n%s\n" "${delimiter}"
printf "Clone or update stable-diffusion-webui"
printf "\n%s\n" "${delimiter}"
cd "${install_dir}"/ || { printf "\e[1m\e[31mERROR: Can't cd to %s/, aborting...\e[0m" "${install_dir}"; exit 1; }
if [[ -d "${clone_dir}" ]]
then
cd "${clone_dir}"/ || { printf "\e[1m\e[31mERROR: Can't cd to %s/%s/, aborting...\e[0m" "${install_dir}" "${clone_dir}"; exit 1; }
"${GIT}" pull
else
"${GIT}" clone https://github.com/AUTOMATIC1111/stable-diffusion-webui.git "${clone_dir}"
cd "${clone_dir}"/ || { printf "\e[1m\e[31mERROR: Can't cd to %s/%s/, aborting...\e[0m" "${install_dir}" "${clone_dir}"; exit 1; }
fi
printf "\n%s\n" "${delimiter}"
printf "Create and activate python venv"
printf "\n%s\n" "${delimiter}"
cd "${install_dir}"/"${clone_dir}"/ || { printf "\e[1m\e[31mERROR: Can't cd to %s/%s/, aborting...\e[0m" "${install_dir}" "${clone_dir}"; exit 1; }
if [[ ! -d "${venv_dir}" ]]
then
"${python_cmd}" -m venv "${venv_dir}"
first_launch=1
fi
# shellcheck source=/dev/null
if [[ -f "${venv_dir}"/bin/activate ]]
then
source "${venv_dir}"/bin/activate
else
printf "\n%s\n" "${delimiter}"
printf "\e[1m\e[31mERROR: Cannot activate python venv, aborting...\e[0m"
printf "\n%s\n" "${delimiter}"
exit 1
fi
printf "\n%s\n" "${delimiter}"
printf "Launching launch.py..."
printf "\n%s\n" "${delimiter}"
"${python_cmd}" launch.py