diff --git a/TODO.md b/TODO.md index dff1f03a4..d1ff57730 100644 --- a/TODO.md +++ b/TODO.md @@ -48,3 +48,4 @@ Tech that can be integrated as part of the core workflow... ## Random - Bunch of stuff: +- diff --git a/extensions-builtin/sd-extension-system-info b/extensions-builtin/sd-extension-system-info index 2a811ca0c..8046b1544 160000 --- a/extensions-builtin/sd-extension-system-info +++ b/extensions-builtin/sd-extension-system-info @@ -1 +1 @@ -Subproject commit 2a811ca0c8b6913a1a2732bf459287addc9cb4f2 +Subproject commit 8046b1544513cea06d1c41748c22727c930323ab diff --git a/extensions-builtin/sd-webui-controlnet b/extensions-builtin/sd-webui-controlnet index 2e0dc37d2..09cb9a32d 160000 --- a/extensions-builtin/sd-webui-controlnet +++ b/extensions-builtin/sd-webui-controlnet @@ -1 +1 @@ -Subproject commit 2e0dc37d222aaba355a71dac0eda4bb7ca54f05f +Subproject commit 09cb9a32d1051aa827f1bb092cf17fcbf996ed7f diff --git a/modules/cmd_args.py b/modules/cmd_args.py index 2c62342bf..6e658f6c9 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -44,6 +44,8 @@ group.add_argument('--use-directml', default = False, action='store_true', help group.add_argument("--use-cuda", default=False, action='store_true', help="Force use nVidia CUDA backend, default: %(default)s") group.add_argument("--use-rocm", default=False, action='store_true', help="Force use AMD ROCm backend, default: %(default)s") group.add_argument('--subpath', type=str, help='Customize the URL subpath for usage with reverse proxy') +group.add_argument('--backend', type=str, choices=[None, 'original', 'diffusers'], default=None, required=False, help='force backend type') + # removed args are added here as hidden in fixed format for compatbility reasons group.add_argument("-f", action='store_true', help=argparse.SUPPRESS) # allows running as root; implemented outside of webui diff --git a/modules/extras.py b/modules/extras.py index 683064661..3247c27a0 100644 --- a/modules/extras.py +++ b/modules/extras.py @@ -24,9 +24,6 @@ def run_pnginfo(image): for key, text in items.items(): if key != 'UserComment': info += f"
{html.escape(str(key))}: {html.escape(str(text))}
" - if len(info) == 0: - message = "Nothing found in the image." - info = f"

{message}

" return '', geninfo, info diff --git a/modules/img2img.py b/modules/img2img.py index dda82dfcd..0e0b9bf8c 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -5,7 +5,7 @@ import modules.scripts from modules import sd_samplers, shared from modules.generation_parameters_copypaste import create_override_settings_dict from modules.processing import Processed, StableDiffusionProcessingImg2Img, process_images -from modules.ui import plaintext_to_html +from modules.ui import plaintext_to_html, infotext_to_html import modules.processing as processing from modules.memstats import memory_stats @@ -165,4 +165,4 @@ def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_s p.close() generation_info_js = processed.js() shared.log.debug(f'Processed: {len(processed.images)} Memory: {memory_stats()} img') - return processed.images, generation_info_js, plaintext_to_html(processed.info), plaintext_to_html(processed.comments) + return processed.images, generation_info_js, infotext_to_html(processed.info), plaintext_to_html(processed.comments) diff --git a/modules/postprocessing.py b/modules/postprocessing.py index b880d6749..63d0f359e 100644 --- a/modules/postprocessing.py +++ b/modules/postprocessing.py @@ -48,8 +48,8 @@ def run_postprocessing(extras_mode, image, image_folder: List[tempfile.NamedTemp outpath = output_dir else: outpath = opts.outdir_samples or opts.outdir_extras_samples - infotext = '' for image, name, ext in zip(image_data, image_names, image_ext): + infotext = '' if shared.state.interrupted: shared.log.debug('Postprocess interrupted') break @@ -62,10 +62,12 @@ def run_postprocessing(extras_mode, image, image_folder: List[tempfile.NamedTemp basename = os.path.splitext(os.path.basename(name))[0] else: basename = '' - infotext = ", ".join([k if k == v else f'{k}: {generation_parameters_copypaste.quote(v)}' for k, v in pp.info.items() if v is not None]) _geninfo, items = images.read_info_from_image(image) for k, v in items.items(): pp.image.info[k] = v + if 'parameters' in items: + infotext = items['parameters'] + ', ' + infotext = infotext + ", ".join([k if k == v else f'{k}: {generation_parameters_copypaste.quote(v)}' for k, v in pp.info.items() if v is not None]) pp.image.info["postprocessing"] = infotext if save_output: images.save_image(pp.image, path=outpath, basename=basename, seed=None, prompt=None, extension=ext or opts.samples_format, info=infotext, short_filename=True, no_prompt=True, grid=False, pnginfo_section_name="extras", existing_info=pp.image.info, forced_filename=None) @@ -73,7 +75,7 @@ def run_postprocessing(extras_mode, image, image_folder: List[tempfile.NamedTemp outputs.append(pp.image) devices.torch_gc() - return outputs, ui_common.plaintext_to_html(infotext), '' + return outputs, ui_common.infotext_to_html(infotext), pp.image.info def run_extras(extras_mode, resize_mode, image, image_folder, input_dir, output_dir, show_extras_results, gfpgan_visibility, codeformer_visibility, codeformer_weight, upscaling_resize, upscaling_resize_w, upscaling_resize_h, upscaling_crop, extras_upscaler_1, extras_upscaler_2, extras_upscaler_2_visibility, upscale_first: bool, save_output: bool = True): #pylint: disable=unused-argument diff --git a/modules/processing.py b/modules/processing.py index 4c3cca1db..9e7e7c742 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -19,7 +19,7 @@ from installer import git_commit import modules.sd_hijack from modules import devices, prompt_parser, masking, sd_samplers, lowvram, generation_parameters_copypaste, script_callbacks, extra_networks, sd_vae_approx, scripts # pylint: disable=unused-import from modules.sd_hijack import model_hijack -from modules.shared import opts, cmd_opts, state, log +from modules.shared import opts, cmd_opts, state, log, backend, Backend import modules.shared as shared import modules.paths as paths import modules.face_restoration @@ -220,7 +220,7 @@ class StableDiffusionProcessing: source_image = devices.cond_cast_float(source_image) # HACK: Using introspection as the Depth2Image model doesn't appear to uniquely # identify itself with a field common to all models. The conditioning_key is also hybrid. - if opts.sd_backend == 'Diffusers': # TODO: Diffusers img2img_image_conditioning + if backend == Backend.DIFFUSERS: # TODO: Diffusers img2img_image_conditioning return latent_image.new_zeros(latent_image.shape[0], 5, 1, 1) if isinstance(self.sd_model, LatentDepth2ImageDiffusion): return self.depth2img_image_conditioning(source_image) @@ -522,7 +522,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: assert p.prompt is not None seed = get_fixed_seed(p.seed) subseed = get_fixed_seed(p.subseed) - if opts.sd_backend == 'Original': + if backend == Backend.ORIGINAL: modules.sd_hijack.model_hijack.apply_circular(p.tiling) modules.sd_hijack.model_hijack.clear_comments() comments = {} @@ -573,11 +573,11 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: cache[0] = (required_prompts, steps) return cache[1] - ema_scope_context = p.sd_model.ema_scope if opts.sd_backend == 'Original' else nullcontext + ema_scope_context = p.sd_model.ema_scope if backend == Backend.ORIGINAL else nullcontext with torch.no_grad(), ema_scope_context(): with devices.autocast(): p.init(p.all_prompts, p.all_seeds, p.all_subseeds) - if shared.opts.live_previews_enable and opts.show_progress_type == "Approx NN" and opts.sd_backend == 'Original': + if shared.opts.live_previews_enable and opts.show_progress_type == "Approx NN" and backend == Backend.ORIGINAL: sd_vae_approx.model() if state.job_count == -1: state.job_count = p.n_iter @@ -618,7 +618,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: if p.n_iter > 1: shared.state.job = f"Batch {n+1} out of {p.n_iter}" - if opts.sd_backend == 'Original': + if backend == Backend.ORIGINAL: uc = get_conds_with_caching(prompt_parser.get_learned_conditioning, negative_prompts, p.steps * step_multiplier, cached_uc) c = get_conds_with_caching(prompt_parser.get_multicond_learned_conditioning, prompts, p.steps * step_multiplier, cached_c) if len(model_hijack.comments) > 0: @@ -671,7 +671,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: for i, x_sample in enumerate(x_samples_ddim): p.batch_index = i - if opts.sd_backend == 'Original': + if backend == Backend.ORIGINAL: x_sample = 255. * np.moveaxis(x_sample.cpu().numpy(), 0, 2) x_sample = x_sample.astype(np.uint8) else: diff --git a/modules/sd_models.py b/modules/sd_models.py index e33465be0..7b1444e6a 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -35,7 +35,7 @@ class CheckpointInfo: self.hash = None self.filename = filename abspath = os.path.abspath(filename) - if shared.opts.sd_backend == 'Original': + if shared.backend == shared.Backend.ORIGINAL: if shared.opts.ckpt_dir is not None and abspath.startswith(shared.opts.ckpt_dir): name = abspath.replace(shared.opts.ckpt_dir, '') elif abspath.startswith(model_path): @@ -104,7 +104,7 @@ def checkpoint_tiles(): def list_models(): checkpoints_list.clear() checkpoint_aliases.clear() - if shared.opts.sd_backend == 'Original': + if shared.backend == shared.Backend.ORIGINAL: model_list = modelloader.load_models(model_path=os.path.join(models_path, 'Stable-diffusion'), model_url=None, command_path=shared.opts.ckpt_dir, ext_filter=[".ckpt", ".safetensors"], download_name=None, ext_blacklist=[".vae.ckpt", ".vae.safetensors"]) else: model_list = modelloader.load_diffusers(model_path=os.path.join(models_path, 'Diffusers'), command_path=shared.opts.diffusers_dir) @@ -113,7 +113,7 @@ def list_models(): if checkpoint_info.name is not None: checkpoint_info.register() if shared.cmd_opts.ckpt is not None: - if not os.path.exists(shared.cmd_opts.ckpt) and shared.opts.sd_backend == 'Original': + if not os.path.exists(shared.cmd_opts.ckpt) and shared.backend == shared.Backend.ORIGINAL: if shared.cmd_opts.ckpt.lower() != "none": shared.log.warning(f"Requested checkpoint not found: {shared.cmd_opts.ckpt}") else: @@ -227,7 +227,7 @@ def read_metadata_from_safetensors(filename): def read_state_dict(checkpoint_file, map_location=None): # pylint: disable=unused-argument - if shared.opts.sd_backend == 'Diffusers': + if shared.backend == shared.Backend.DIFFUSERS: return None try: pl_sd = None @@ -376,9 +376,9 @@ class SdModelData: if self.sd_model is None: with self.lock: try: - if shared.opts.sd_backend == 'Original': + if shared.backend == shared.Backend.ORIGINAL: load_model() - elif shared.opts.sd_backend == 'Diffusers': + elif shared.backend == shared.Backend.DIFFUSERS: load_diffuser() else: shared.log.error(f"Unknown Stable Diffusion backend: {shared.opts.sd_backend}") @@ -429,6 +429,12 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No shared.log.info(f'Loading diffuser model: {checkpoint_info.filename}') scheduler = diffusers.UniPCMultistepScheduler.from_pretrained(checkpoint_info.filename, subfolder="scheduler") sd_model = diffusers.DiffusionPipeline.from_pretrained(checkpoint_info.filename, scheduler=scheduler, **diffusor_config) + if shared.cmd_opts.medvram: + sd_model.enable_model_cpu_offload() + if shared.cmd_opts.lowvram: + sd_model.enable_sequential_cpu_offload() + if shared.opts.cross_attention_optimization == "xFormers": + sd_model.enable_xformers_memory_efficient_attention() sd_model.sd_checkpoint_info = checkpoint_info sd_model.sd_model_checkpoint = checkpoint_info.filename sd_model.sd_model_hash = checkpoint_info.hash @@ -550,7 +556,7 @@ def reload_model_weights(sd_model=None, info=None): if sd_model is None or checkpoint_config != sd_model.used_config: del sd_model checkpoints_loaded.clear() - if shared.opts.sd_backend == 'Original': + if shared.backend == shared.Backend.ORIGINAL: load_model(checkpoint_info, already_loaded_state_dict=state_dict, timer=timer) else: load_diffuser(checkpoint_info, already_loaded_state_dict=state_dict, timer=timer) @@ -575,7 +581,7 @@ def unload_model_weights(sd_model=None, _info=None): from modules import sd_hijack if model_data.sd_model: model_data.sd_model.to(devices.cpu) - if shared.opts.sd_backend == 'Original': + if shared.backend == shared.Backend.ORIGINAL: sd_hijack.model_hijack.undo_hijack(model_data.sd_model) model_data.sd_model = None sd_model = None diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index 2a13f5030..8ac3f46ef 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -1,8 +1,8 @@ from modules import sd_samplers_compvis, sd_samplers_kdiffusion, sd_samplers_diffusors, shared from modules.sd_samplers_common import samples_to_image_grid, sample_to_image # pylint: disable=unused-import -from modules.shared import opts +from modules.shared import backend, Backend -if opts.sd_backend == 'Original': +if backend == Backend.ORIGINAL: all_samplers = [ *sd_samplers_kdiffusion.samplers_data_k_diffusion, *sd_samplers_compvis.samplers_data_compvis, @@ -23,7 +23,7 @@ def create_sampler(name, model): else: config = all_samplers[0] assert config is not None, f'bad sampler name: {name}' - if opts.sd_backend == 'Original': + if backend == Backend.ORIGINAL: sampler = config.constructor(model) sampler.config = config return sampler diff --git a/modules/shared.py b/modules/shared.py index 7050ddde7..a1fce4156 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -4,10 +4,10 @@ import time import json import datetime import urllib.request +from enum import Enum import gradio as gr import tqdm import requests -# from ldm.models.diffusion.ddpm import LatentDiffusion from modules import errors, ui_components, shared_items, cmd_args from modules.paths_internal import models_path, script_path, data_path, sd_configs_path, sd_default_config, sd_model_file, default_sd_model_file, extensions_dir, extensions_builtin_dir # pylint: disable=W0611 import modules.interrogate @@ -72,6 +72,11 @@ ui_reorder_categories = [ ] +class Backend(Enum): + ORIGINAL = 1 + DIFFUSERS = 2 + + def reload_hypernetworks(): from modules.hypernetworks import hypernetwork global hypernetworks # pylint: disable=W0603 @@ -634,6 +639,13 @@ opts = Options() config_filename = cmd_opts.config opts.load(config_filename) cmd_opts = cmd_args.compatibility_args(opts, cmd_opts) +if cmd_opts.backend == 'diffusers': + log.info('Overriding backend to Diffusers') + opts.data['sd_backend'] = 'Diffusers' +if cmd_opts.backend == 'original': + log.info('Overriding backend to Diffusers') + opts.data['sd_backend'] = 'Original' +backend = Backend.DIFFUSERS if opts.sd_backend == 'Diffusers' else Backend.ORIGINAL prompt_styles = modules.styles.StyleDatabase(opts.styles_dir) cmd_opts.disable_extension_access = (cmd_opts.share or cmd_opts.listen or (cmd_opts.server_name or False)) and not cmd_opts.insecure diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index 137309d4d..722593525 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -207,7 +207,7 @@ class EmbeddingDatabase: continue def load_textual_inversion_embeddings(self, force_reload=False): - if shared.opts.sd_backend == 'Diffusers': # TODO Diffusers + if shared.backend == shared.Backend.DIFFUSERS: # TODO Diffusers return if not force_reload: need_reload = False diff --git a/modules/txt2img.py b/modules/txt2img.py index e2e37afc5..5b0d3309e 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -3,7 +3,7 @@ from modules import sd_samplers, shared from modules.generation_parameters_copypaste import create_override_settings_dict from modules.processing import StableDiffusionProcessingTxt2Img, process_images # from modules.shared import opts, sd_model, debug -from modules.ui import plaintext_to_html +from modules.ui import plaintext_to_html, infotext_to_html from modules.memstats import memory_stats @@ -58,4 +58,4 @@ def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, step p.close() generation_info_js = processed.js() shared.log.debug(f'Processed: {len(processed.images)} Memory: {memory_stats()} txt') - return processed.images, generation_info_js, plaintext_to_html(processed.info), plaintext_to_html(processed.comments) + return processed.images, generation_info_js, infotext_to_html(processed.info), plaintext_to_html(processed.comments) diff --git a/modules/ui.py b/modules/ui.py index 0e79e983d..66d01be55 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -13,7 +13,7 @@ from modules.call_queue import wrap_gradio_gpu_call, wrap_queued_call, wrap_grad from modules import sd_hijack, sd_models, script_callbacks, ui_extensions, deepbooru, sd_vae, extra_networks, ui_common, ui_postprocessing from modules.ui_components import FormRow, FormColumn, FormGroup, ToolButton, FormHTML # pylint: disable=unused-import from modules.paths import script_path, data_path -from modules.shared import opts, cmd_opts +from modules.shared import opts, cmd_opts, backend, Backend from modules import prompt_parser import modules.codeformer_model import modules.generation_parameters_copypaste as parameters_copypaste @@ -63,6 +63,10 @@ def plaintext_to_html(text): return ui_common.plaintext_to_html(text) +def infotext_to_html(text): + return ui_common.infotext_to_html(text) + + def send_gradio_gallery_to_image(x): if len(x) == 0: return None @@ -204,7 +208,7 @@ def update_token_counter(text, steps): prompt_schedules = [[[steps, text]]] flat_prompts = reduce(lambda list1, list2: list1+list2, prompt_schedules) prompts = [prompt_text for step, prompt_text in flat_prompts] - if opts.sd_backend == 'Original': + if backend == Backend.ORIGINAL: token_count, max_length = max([sd_hijack.model_hijack.get_prompt_lengths(prompt) for prompt in prompts], key=lambda args: args[0]) else: tokenizer = modules.shared.sd_model.tokenizer diff --git a/modules/ui_common.py b/modules/ui_common.py index 5f51f9bf2..cfd9e6ee9 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -1,6 +1,7 @@ import json import html import os +import shutil import platform import subprocess import gradio as gr @@ -8,6 +9,7 @@ from modules import call_queue, shared from modules.generation_parameters_copypaste import image_from_url_text import modules.images + folder_symbol = '\U0001f4c2' # 📂 @@ -16,60 +18,99 @@ def update_generation_info(generation_info, html_info, img_index): generation_info = json.loads(generation_info) if img_index < 0 or img_index >= len(generation_info["infotexts"]): return html_info, gr.update() - return plaintext_to_html(generation_info["infotexts"][img_index]), gr.update() + html_text = infotext_to_html(generation_info["infotexts"][img_index]) + return html_text, gr.update() except Exception: pass - # if the json parse or anything else fails, just return the old html_info return html_info, gr.update() def plaintext_to_html(text): - text = "

" + "
\n".join([f"{html.escape(x)}" for x in text.split('\n')]) + "

" - return text + res = '

' + "
\n".join([f"{html.escape(x)}" for x in text.split('\n')]) + '

' + return res + + +def infotext_to_html(text): + res = '

Prompt: ' + html.escape(text).replace('\n', '
') + '

' + sections = res.split('Steps:') # before and after prompt+negprompt' + if len(sections) > 1: + res = sections[0] + '
Steps: ' + sections[1].strip().replace(', ', ' | ') + res = res.replace('

', '
') + return res + + +def delete_files(js_data, images, _do_make_zip, index): + try: + data = json.loads(js_data) + except Exception: + data = { 'index_of_first_image': 0 } + start_index = 0 + if index > -1 and shared.opts.save_selected_only and (index >= data['index_of_first_image']): + images = [images[index]] + start_index = index + filenames = [] + filenames = [] + fullfns = [] + for _image_index, filedata in enumerate(images, start_index): + if 'name' in filedata and os.path.isfile(filedata['name']): + fullfn = filedata['name'] + filenames.append(os.path.basename(fullfn)) + try: + os.remove(fullfn) + fullfns.append(fullfn) + shared.log.info(f"Deleting image: {fullfn}") + except Exception as e: + shared.log.error(f'Error deleting file: {fullfn} {e}') + images = [image for image in images if image['name'] not in fullfns] + return images, plaintext_to_html(f"Deleted: {filenames[0] if len(filenames) > 0 else 'none'}") def save_files(js_data, images, do_make_zip, index): - if js_data is None or len(js_data) == 0: - return - filenames = [] - fullfns = [] + os.makedirs(shared.opts.outdir_save, exist_ok=True) - #quick dictionary to class object conversion. Its necessary due apply_filename_pattern requiring it - class MyObject: + class MyObject: #quick dictionary to class object conversion. Its necessary due apply_filename_pattern requiring it def __init__(self, d=None): if d is not None: for key, value in d.items(): setattr(self, key, value) - data = json.loads(js_data) + try: + data = json.loads(js_data) + except Exception: + data = { 'index_of_first_image': 0 } p = MyObject(data) - path = shared.opts.outdir_save - save_to_dirs = shared.opts.use_save_to_dirs_for_ui - extension: str = shared.opts.samples_format start_index = 0 - if index > -1 and shared.opts.save_selected_only and (index >= data["index_of_first_image"]): # ensures we are looking at a specific non-grid picture, and we have save_selected_only + if index > -1 and shared.opts.save_selected_only and (index >= data['index_of_first_image']): # ensures we are looking at a specific non-grid picture, and we have save_selected_only # pylint: disable=no-member images = [images[index]] start_index = index - os.makedirs(shared.opts.outdir_save, exist_ok=True) + filenames = [] + fullfns = [] for image_index, filedata in enumerate(images, start_index): - image = image_from_url_text(filedata) - is_grid = image_index < p.index_of_first_image # pylint: disable=no-member - i = 0 if is_grid else (image_index - p.index_of_first_image) # pylint: disable=no-member - if len(p.all_seeds) <= i: # pylint: disable=no-member - p.all_seeds.append(p.seed) # pylint: disable=no-member - if len(p.all_prompts) <= i: # pylint: disable=no-member - p.all_prompts.append(p.prompt) # pylint: disable=no-member - fullfn, txt_fullfn = modules.images.save_image(image, path, "", seed=p.all_seeds[i], prompt=p.all_prompts[i], extension=extension, info=p.infotexts[image_index], grid=is_grid, p=p, save_to_dirs=save_to_dirs) # pylint: disable=no-member - if fullfn is None: - continue - filename = os.path.relpath(fullfn, path) - filenames.append(filename) - fullfns.append(fullfn) - if txt_fullfn: - filenames.append(os.path.basename(txt_fullfn)) - fullfns.append(txt_fullfn) + if 'name' in filedata and os.path.isfile(filedata['name']): + fullfn = filedata['name'] + filenames.append(os.path.basename(fullfn)) + fullfns.append(fullfn) + shutil.copy(fullfn, shared.opts.outdir_save) + shared.log.info(f"Copying image: {fullfn} -> {shared.opts.outdir_save}") + else: + image = image_from_url_text(filedata) + is_grid = image_index < p.index_of_first_image # pylint: disable=no-member + i = 0 if is_grid else (image_index - p.index_of_first_image) # pylint: disable=no-member + if len(p.all_seeds) <= i: # pylint: disable=no-member + p.all_seeds.append(p.seed) # pylint: disable=no-member + if len(p.all_prompts) <= i: # pylint: disable=no-member + p.all_prompts.append(p.prompt) # pylint: disable=no-member + fullfn, txt_fullfn = modules.images.save_image(image, shared.opts.outdir_save, "", seed=p.all_seeds[i], prompt=p.all_prompts[i], extension=shared.opts.samples_format, info=p.infotexts[image_index], grid=is_grid, p=p, save_to_dirs=shared.opts.use_save_to_dirs_for_ui) # pylint: disable=no-member + if fullfn is None: + continue + filename = os.path.relpath(fullfn, shared.opts.outdir_save) + filenames.append(filename) + fullfns.append(fullfn) + if txt_fullfn: + filenames.append(os.path.basename(txt_fullfn)) + fullfns.append(txt_fullfn) if do_make_zip: - zip_filepath = os.path.join(path, "images.zip") + zip_filepath = os.path.join(shared.opts.outdir_save, "images.zip") from zipfile import ZipFile with ZipFile(zip_filepath, "w") as zip_file: for i in range(len(fullfns)): @@ -105,87 +146,47 @@ def create_output_panel(tabname, outdir): with gr.Group(elem_id=f"{tabname}_gallery_container"): result_gallery = gr.Gallery(value=['html/logo.png'], label='Output', show_label=False, elem_id=f"{tabname}_gallery").style(preview=False, container=False, columns=[1,2,3,4,5,6]) # <576px, <768px, <992px, <1200px, <1400px, >1400px - generation_info = None with gr.Column(): with gr.Row(elem_id=f"image_buttons_{tabname}", elem_classes="image-buttons"): open_folder_button = gr.Button('show', visible=not shared.cmd_opts.hide_ui_dir_config) - - if tabname != "extras": - save = gr.Button('save', elem_id=f'save_{tabname}') - save_zip = gr.Button('zip', elem_id=f'save_zip_{tabname}') - + save = gr.Button('save', elem_id=f'save_{tabname}') + save_zip = gr.Button('zip', elem_id=f'save_zip_{tabname}') + delete = gr.Button('delete', elem_id=f'delete_{tabname}') buttons = parameters_copypaste.create_buttons(["img2img", "inpaint", "extras"]) - open_folder_button.click( - fn=lambda: open_folder(shared.opts.outdir_samples or outdir), - inputs=[], - outputs=[], - ) - - if tabname != "extras": - download_files = gr.File(None, file_count="multiple", interactive=False, show_label=False, visible=False, elem_id=f'download_files_{tabname}') - - with gr.Group(): - html_info = gr.HTML(elem_id=f'html_info_{tabname}', elem_classes="infotext") - html_log = gr.HTML(elem_id=f'html_log_{tabname}') - - generation_info = gr.Textbox(visible=False, elem_id=f'generation_info_{tabname}') - if tabname == 'txt2img' or tabname == 'img2img': - generation_info_button = gr.Button(visible=False, elem_id=f"{tabname}_generation_info_button") - generation_info_button.click( - fn=update_generation_info, - _js="function(x, y, z){ return [x, y, selected_gallery_index()] }", - inputs=[generation_info, html_info, html_info], - outputs=[html_info, html_info], - show_progress=False, - ) - - save.click( - fn=call_queue.wrap_gradio_call(save_files), - _js="(x, y, z, w) => [x, y, false, selected_gallery_index()]", - inputs=[ - generation_info, - result_gallery, - html_info, - html_info, - ], - outputs=[ - download_files, - html_log, - ], - show_progress=False, - ) - - save_zip.click( - fn=call_queue.wrap_gradio_call(save_files), - _js="(x, y, z, w) => [x, y, true, selected_gallery_index()]", - inputs=[ - generation_info, - result_gallery, - html_info, - html_info, - ], - outputs=[ - download_files, - html_log, - ] - ) - - else: - html_info_x = gr.HTML(elem_id=f'html_info_x_{tabname}') + open_folder_button.click(fn=lambda: open_folder(shared.opts.outdir_samples or outdir), inputs=[], outputs=[]) + download_files = gr.File(None, file_count="multiple", interactive=False, show_label=False, visible=False, elem_id=f'download_files_{tabname}') + with gr.Group(): html_info = gr.HTML(elem_id=f'html_info_{tabname}', elem_classes="infotext") html_log = gr.HTML(elem_id=f'html_log_{tabname}') + generation_info = gr.Textbox(visible=False, elem_id=f'generation_info_{tabname}') + generation_info_button = gr.Button(visible=False, elem_id=f"{tabname}_generation_info_button") + generation_info_button.click(fn=update_generation_info, _js="function(x, y, z){ return [x, y, selected_gallery_index()] }", show_progress=False, + inputs=[generation_info, html_info, html_info], + outputs=[html_info, html_info], + ) + save.click(fn=call_queue.wrap_gradio_call(save_files), _js="(x, y, z, w) => [x, y, false, selected_gallery_index()]", show_progress=False, + inputs=[generation_info, result_gallery, html_info, html_info], + outputs=[download_files, html_log], + ) + save_zip.click(fn=call_queue.wrap_gradio_call(save_files), _js="(x, y, z, w) => [x, y, true, selected_gallery_index()]", + inputs=[generation_info, result_gallery, html_info, html_info], + outputs=[download_files, html_log], + ) + delete.click(fn=call_queue.wrap_gradio_call(delete_files), _js="(x, y, z, w) => [x, y, true, selected_gallery_index()]", + inputs=[generation_info, result_gallery, html_info, html_info], + outputs=[result_gallery, html_log], + ) - paste_field_names = [] if tabname == "txt2img": paste_field_names = modules.scripts.scripts_txt2img.paste_field_names elif tabname == "img2img": paste_field_names = modules.scripts.scripts_img2img.paste_field_names - + else: + paste_field_names = [] for paste_tabname, paste_button in buttons.items(): parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding( - paste_button=paste_button, tabname=paste_tabname, source_tabname="txt2img" if tabname == "txt2img" else None, source_image_component=result_gallery, + paste_button=paste_button, tabname=paste_tabname, source_tabname=("txt2img" if tabname == "txt2img" else None), source_image_component=result_gallery, paste_field_names=paste_field_names )) - - return result_gallery, generation_info if tabname != "extras" else html_info_x, html_info, html_log + return result_gallery, generation_info, html_info, html_log diff --git a/modules/ui_postprocessing.py b/modules/ui_postprocessing.py index 9a56d26e8..860316e49 100644 --- a/modules/ui_postprocessing.py +++ b/modules/ui_postprocessing.py @@ -3,13 +3,18 @@ from modules import scripts_postprocessing, scripts, shared, gfpgan_model, codef import modules.generation_parameters_copypaste as parameters_copypaste from modules.call_queue import wrap_gradio_gpu_call, wrap_queued_call, wrap_gradio_call # pylint: disable=unused-import from modules.extras import run_pnginfo +from modules.ui_common import infotext_to_html + + +def wrap_pnginfo(image): + _, geninfo, info = run_pnginfo(image) + return '', infotext_to_html(geninfo), info def submit_click(tab_index, extras_image, image_batch, extras_batch_input_dir, extras_batch_output_dir, show_extras_results, *script_inputs): - result_images, html_info_x, html_info = postprocessing.run_postprocessing(tab_index, extras_image, image_batch, extras_batch_input_dir, extras_batch_output_dir, show_extras_results, *script_inputs) - if result_images is not None and len(result_images) > 0: - _html_info, _generation_info, html_info_x = run_pnginfo(result_images[0]) - return result_images, html_info_x, html_info + + result_images, geninfo, _js_info = postprocessing.run_postprocessing(tab_index, extras_image, image_batch, extras_batch_input_dir, extras_batch_output_dir, show_extras_results, *script_inputs) + return result_images, geninfo, '{}', '' def create_ui(): @@ -37,32 +42,21 @@ def create_ui(): skip = gr.Button('Skip', elem_id=f"{id_part}_skip", variant='secondary') skip.click(fn=lambda: shared.state.skip(), inputs=[], outputs=[]) interrupt.click(fn=lambda: shared.state.interrupt(), inputs=[], outputs=[]) - result_images, html_info_x, html_info, _html_log = ui_common.create_output_panel("extras", shared.opts.outdir_extras_samples) - html_info = gr.HTML(elem_id="pnginfo_html_info") - generation_info = gr.Textbox(elem_id="pnginfo_generation_info", label="Parameters", visible=False) - generation_info_pretty = gr.Textbox(elem_id="pnginfo_generation_info_pretty", label="Parameters") - gr.HTML('Full metadata') - html2_info = gr.HTML(elem_id="pnginfo_html2_info") + result_images, generation_info, html_info, html_log = ui_common.create_output_panel("extras", shared.opts.outdir_extras_samples) + gr.HTML('File metadata') + exif_info = gr.HTML(elem_id="pnginfo_html_info") for tabname, button in buttons.items(): - parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding(paste_button=button, tabname=tabname, source_text_component=generation_info, source_image_component=extras_image)) - - def pretty_geninfo(generation_info: str): - if generation_info is None: - return '' - sections = generation_info.split('Steps:') - if len(sections) > 1: - param = sections[0].strip() + '\nSteps:' + sections[1].strip().replace(', ', '\n') - return param - return generation_info + parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding(paste_button=button, tabname=tabname, source_text_component=html_info, source_image_component=extras_image)) tab_single.select(fn=lambda: 0, inputs=[], outputs=[tab_index]) tab_batch.select(fn=lambda: 1, inputs=[], outputs=[tab_index]) tab_batch_dir.select(fn=lambda: 2, inputs=[], outputs=[tab_index]) - generation_info.change(fn=pretty_geninfo, inputs=[generation_info], outputs=[generation_info_pretty]) + # html_info.change(fn=pretty_geninfo, inputs=[html_info], outputs=[html_info_pretty]) + _dummy = gr.HTML(visible=False) extras_image.change( - fn=wrap_gradio_call(run_pnginfo), + fn=wrap_gradio_call(wrap_pnginfo), inputs=[extras_image], - outputs=[html_info, generation_info, html2_info], + outputs=[_dummy, html_info, exif_info], ) submit.click( fn=call_queue.wrap_gradio_gpu_call(submit_click, extra_outputs=[None, '']), @@ -73,12 +67,13 @@ def create_ui(): extras_batch_input_dir, extras_batch_output_dir, show_extras_results, - *script_inputs + *script_inputs, ], outputs=[ result_images, - html_info_x, html_info, + generation_info, + html_log, ] )