diff --git a/CHANGELOG.md b/CHANGELOG.md index af4307967..abbbc2441 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ - StableDiffusion 3 -## Update for 2024-06-03 +## Update for 2024-06-04 *Note*: New features require `diffusers==0.29.0.dev` @@ -41,6 +41,7 @@ - lower overhead on generate calls - cumulative fixes since the last release - add python version check for torch-directml +- improve metadata/infotext parser ## Update for 2024-06-02 diff --git a/cli/image-exif.py b/cli/image-exif.py index f7268fde8..151a0df99 100755 --- a/cli/image-exif.py +++ b/cli/image-exif.py @@ -4,71 +4,16 @@ import os import io import re import sys -import json +import importlib from PIL import Image, ExifTags, TiffImagePlugin, PngImagePlugin from rich import print # pylint: disable=redefined-builtin -def unquote(text): - if len(text) == 0 or text[0] != '"' or text[-1] != '"': - return text - try: - return json.loads(text) - except Exception: - return text +module_spec = importlib.util.spec_from_file_location('infotext', os.path.join('modules', 'infotext.py')) +infotext = importlib.util.module_from_spec(module_spec) +module_spec.loader.exec_module(infotext) -def parse_generation_parameters(infotext): - if not isinstance(infotext, str): - return {} - re_param = re.compile(r'\s*([\w ]+):\s*("(?:\\"[^,]|\\"|\\|[^\"])+"|[^,]*)(?:,|$)') # multi-word: value - re_size = re.compile(r"^(\d+)x(\d+)$") # int x int - basic_params = ['steps', 'seed', 'width', 'height', 'sampler', 'size', 'cfg scale', 'hires'] # first param is one of those - - sanitized = infotext.replace('prompt:', 'Prompt:').replace('negative prompt:', 'Negative prompt:').replace('Negative Prompt', 'Negative prompt') # cleanup everything in brackets so re_params can work - sanitized = re.sub(r'<[^>]*>', lambda match: ' ' * len(match.group()), sanitized) - sanitized = re.sub(r'\([^)]*\)', lambda match: ' ' * len(match.group()), sanitized) - sanitized = re.sub(r'\{[^}]*\}', lambda match: ' ' * len(match.group()), sanitized) - - params = dict(re_param.findall(sanitized)) - params = { k.strip():params[k].strip() for k in params if k.lower() not in ['hashes', 'lora', 'embeddings', 'prompt', 'negative prompt']} # remove some keys - if len(list(params)) == 0: - first_param = None - else: - try: - first_param, first_param_idx = next((s, i) for i, s in enumerate(params) if any(x in s.lower() for x in basic_params)) - except Exception: - first_param, first_param_idx = next(iter(params)), 0 - if first_param_idx > 0: - for _i in range(first_param_idx): - params.pop(next(iter(params))) - params_idx = sanitized.find(f'{first_param}:') if first_param else -1 - negative_idx = infotext.find("Negative prompt:") - - prompt = infotext[:params_idx] if negative_idx == -1 else infotext[:negative_idx] # prompt can be with or without negative prompt - negative = infotext[negative_idx:params_idx] if negative_idx >= 0 else '' - - for k, v in params.copy().items(): # avoid dict-has-changed - if len(v) > 0 and v[0] == '"' and v[-1] == '"': - v = unquote(v) - m = re_size.match(v) - if v.replace('.', '', 1).isdigit(): - params[k] = float(v) if '.' in v else int(v) - elif v == "True": - params[k] = True - elif v == "False": - params[k] = False - elif m is not None: - params[f"{k}-1"] = int(m.group(1)) - params[f"{k}-2"] = int(m.group(2)) - elif k == 'VAE' and v == 'TAESD': - params["Full quality"] = False - else: - params[k] = v - params["Prompt"] = prompt.replace('Prompt:', '').strip() - params["Negative prompt"] = negative.replace('Negative prompt:', '').strip() - return params - class Exif: # pylint: disable=single-string-used-for-slots __slots__ = ('__dict__') # pylint: disable=superfluous-parens @@ -132,7 +77,7 @@ class Exif: # pylint: disable=single-string-used-for-slots def parse(self): x = self.exif.pop('parameters', None) or self.exif.pop('UserComment', None) - res = parse_generation_parameters(x) + res = infotext.parse(x) return res def get_bytes(self): diff --git a/modules/api/endpoints.py b/modules/api/endpoints.py index 6081c85d0..63c5764c0 100644 --- a/modules/api/endpoints.py +++ b/modules/api/endpoints.py @@ -146,7 +146,7 @@ def get_extensions_list(): return ext_list def post_pnginfo(req: models.ReqImageInfo): - from modules import images, script_callbacks, generation_parameters_copypaste + from modules import images, script_callbacks, infotext if not req.image.strip(): return models.ResImageInfo(info="") image = helpers.decode_base64_to_image(req.image.strip()) @@ -155,6 +155,6 @@ def post_pnginfo(req: models.ReqImageInfo): geninfo, items = images.read_info_from_image(image) if geninfo is None: geninfo = "" - params = generation_parameters_copypaste.parse_generation_parameters(geninfo) + params = infotext.parse(geninfo) script_callbacks.infotext_pasted_callback(geninfo, params) return models.ResImageInfo(info=geninfo, items=items, parameters=params) diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index 97cf6d38f..feebfe50e 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -1,12 +1,11 @@ import base64 import io import os -import re -import json from PIL import Image import gradio as gr from modules.paths import data_path from modules import shared, gr_tempdir, script_callbacks, images +from modules.infotext import parse, mapping, quote, unquote # pylint: disable=unused-import type_of_gr_update = type(gr.update()) @@ -14,7 +13,8 @@ paste_fields = {} registered_param_bindings = [] debug = shared.log.trace if os.environ.get('SD_PASTE_DEBUG', None) is not None else lambda *args, **kwargs: None debug('Trace: PASTE') - +parse_generation_parameters = parse # compatibility +infotext_to_setting_name_mapping = mapping # compatibility class ParamBinding: def __init__(self, paste_button, tabname, source_text_component=None, source_image_component=None, source_tabname=None, override_settings_component=None, paste_field_names=None): @@ -32,21 +32,6 @@ def reset(): paste_fields.clear() -def quote(text): - if ',' not in str(text) and '\n' not in str(text) and ':' not in str(text): - return text - return json.dumps(text, ensure_ascii=False) - - -def unquote(text): - if len(text) == 0 or text[0] != '"' or text[-1] != '"': - return text - try: - return json.loads(text) - except Exception: - return text - - def image_from_url_text(filedata): if filedata is None: return None @@ -187,124 +172,13 @@ def send_image_and_dimensions(x): return img, w, h -def parse_generation_parameters(infotext, no_prompt=False): - if not isinstance(infotext, str): - return {} - debug(f'Parse infotext: {infotext}') - re_param = re.compile(r'\s*([\w ]+):\s*("(?:\\"[^,]|\\"|\\|[^\"])+"|[^,]*)(?:,|$)') # multi-word: value - re_size = re.compile(r"^(\d+)x(\d+)$") # int x int - basic_params = ['steps:', 'seed:', 'width:', 'height:', 'sampler:', 'size:', 'cfg scale:'] # first param is one of those - - infotext = infotext.replace('prompt:', 'Prompt:').replace('negative prompt:', 'Negative prompt:').replace('Negative Prompt', 'Negative prompt') # cleanup everything in brackets so re_params can work - infotext = infotext.replace(' Steps: ', ', Steps: ').replace('\nSteps: ', ', Steps: ') # fix cases where there is no delimiter between prompt and steps - sanitized = infotext - sanitized = re.sub(r'<[^>]*>', lambda match: ' ' * len(match.group()), sanitized) - sanitized = re.sub(r'\([^)]*\)', lambda match: ' ' * len(match.group()), sanitized) - sanitized = re.sub(r'\{[^}]*\}', lambda match: ' ' * len(match.group()), sanitized) - - params = dict(re_param.findall(sanitized)) - debug(f"Parse params: {params}") - params = { k.strip():params[k].strip() for k in params if k.lower() not in ['hashes', 'lora', 'embeddings', 'prompt', 'negative prompt']} # remove some keys - if len(list(params)) == 0: - first_param = None - else: - try: - first_param, first_param_idx = next((s, i) for i, s in enumerate(params) if any(x in s.lower() for x in basic_params)) - except Exception: - first_param, first_param_idx = next(iter(params)), 0 - if first_param_idx > 0: - for _i in range(first_param_idx): - params.pop(next(iter(params))) - params_idx = sanitized.find(f'{first_param}:') if first_param else -1 - negative_idx = infotext.find("Negative prompt:") - if 'Steps:' in sanitized: - params_idx = max(params_idx, sanitized.find('Steps:')) - - if negative_idx == -1: # prompt can be without negative prompt - prompt = infotext[:params_idx] if params_idx > 0 else infotext - else: - prompt = infotext[:negative_idx] - if prompt.startswith('Steps: '): - prompt = '' - if negative_idx >= 0: - negative = infotext[negative_idx:params_idx] if params_idx > 0 else infotext[negative_idx:] - else: - negative = '' - - for k, v in params.copy().items(): # avoid dict-has-changed - if len(v) > 0 and v[0] == '"' and v[-1] == '"': - v = unquote(v) - m = re_size.match(v) - if v.replace('.', '', 1).isdigit(): - params[k] = float(v) if '.' in v else int(v) - elif v == "True": - params[k] = True - elif v == "False": - params[k] = False - elif m is not None: - params[f"{k}-1"] = int(m.group(1)) - params[f"{k}-2"] = int(m.group(2)) - elif k == 'VAE' and v == 'TAESD': - params["Full quality"] = False - else: - params[k] = v - if not no_prompt: - params["Prompt"] = prompt.replace('Prompt:', '').strip(' ,\n') - params["Negative prompt"] = negative.replace('Negative prompt:', '').strip(' ,\n') - debug(f"Parse: {params}") - return params - - -settings_map = {} - - -infotext_to_setting_name_mapping = [ - ('Backend', 'sd_backend'), - ('Model hash', 'sd_model_checkpoint'), - ('Refiner', 'sd_model_refiner'), - ('VAE', 'sd_vae'), - ('Parser', 'prompt_attention'), - ('Color correction', 'img2img_color_correction'), - # Samplers - ('Sampler Eta', 'scheduler_eta'), - ('Sampler ENSD', 'eta_noise_seed_delta'), - ('Sampler order', 'schedulers_solver_order'), - # Samplers diffusers - ('Sampler beta schedule', 'schedulers_beta_schedule'), - ('Sampler beta start', 'schedulers_beta_start'), - ('Sampler beta end', 'schedulers_beta_end'), - ('Sampler DPM solver', 'schedulers_dpm_solver'), - # Samplers original - ('Sampler brownian', 'schedulers_brownian_noise'), - ('Sampler discard', 'schedulers_discard_penultimate'), - ('Sampler dyn threshold', 'schedulers_use_thresholding'), - ('Sampler karras', 'schedulers_use_karras'), - ('Sampler low order', 'schedulers_use_loworder'), - ('Sampler quantization', 'enable_quantization'), - ('Sampler sigma', 'schedulers_sigma'), - ('Sampler sigma min', 's_min'), - ('Sampler sigma max', 's_max'), - ('Sampler sigma churn', 's_churn'), - ('Sampler sigma uncond', 's_min_uncond'), - ('Sampler sigma noise', 's_noise'), - ('Sampler sigma tmin', 's_tmin'), - ('Sampler ENSM', 'initial_noise_multiplier'), # img2img only - ('UniPC skip type', 'uni_pc_skip_type'), - ('UniPC variant', 'uni_pc_variant'), - # Token Merging - ('Mask weight', 'inpainting_mask_weight'), - ('ToMe', 'tome_ratio'), - ('ToDo', 'todo_ratio'), -] - - def create_override_settings_dict(text_pairs): res = {} params = {} for pair in text_pairs: k, v = pair.split(":", maxsplit=1) params[k] = v.strip() - for param_name, setting_name in infotext_to_setting_name_mapping: + for param_name, setting_name in mapping: value = params.get(param_name, None) if value is None: continue @@ -325,7 +199,7 @@ def connect_paste(button, local_paste_fields, input_comp, override_settings_comp prompt = '' else: shared.log.debug(f'Paste prompt: type="current" prompt="{prompt}"') - params = parse_generation_parameters(prompt, no_prompt=False) + params = parse(prompt) script_callbacks.infotext_pasted_callback(prompt, params) res = [] applied = {} diff --git a/modules/infotext.py b/modules/infotext.py new file mode 100644 index 000000000..ed569517d --- /dev/null +++ b/modules/infotext.py @@ -0,0 +1,127 @@ +import os +import re +import json + + +debug = lambda *args, **kwargs: None # pylint: disable=unnecessary-lambda-assignment +re_size = re.compile(r"^(\d+)x(\d+)$") # int x int +re_param = re.compile(r'\s*([\w ]+):\s*("(?:\\"[^,]|\\"|\\|[^\"])+"|[^,]*)(?:,|$)') # multi-word: value + + +def quote(text): + if ',' not in str(text) and '\n' not in str(text) and ':' not in str(text): + return text + return json.dumps(text, ensure_ascii=False) + + +def unquote(text): + if len(text) == 0 or text[0] != '"' or text[-1] != '"': + return text + try: + return json.loads(text) + except Exception: + return text + + +def parse(infotext): + if not isinstance(infotext, str): + return {} + debug(f'Raw: {infotext}') + if 'negative prompt:' not in infotext: + infotext = 'negative prompt: ' + infotext + if 'Prompt:' not in infotext: + infotext = 'prompt: ' + infotext + + remaining = infotext + prompt = remaining[:infotext.lower().find('negative prompt:')] + remaining = remaining.replace(prompt, '') + if prompt.lower().startswith('prompt: '): + prompt = prompt[8:] + # debug(f'Prompt: {prompt}') + + params = ['steps:', 'seed:', 'width:', 'height:', 'sampler:', 'size:', 'cfg scale:'] # first param is one of those + param_idx = [remaining.lower().find(p) for p in params if p in remaining] + param_idx = min(param_idx) if len(param_idx) > 0 else 0 + negative = remaining[:param_idx] if param_idx > 0 else remaining + remaining = remaining.replace(negative, '') + if negative.lower().startswith('negative prompt: '): + negative = negative[16:] + # debug(f'Negative: {negative}') + + params = dict(re_param.findall(remaining)) + params['Prompt'] = prompt + params['Negative prompt'] = negative + for key, val in params.copy().items(): + val = unquote(val).strip(" ,\n\\n") + size = re_size.match(val) + if val.replace('.', '', 1).isdigit(): + params[key] = float(val) if '.' in val else int(val) + elif val == "True": + params[key] = True + elif val == "False": + params[key] = False + elif key == 'VAE' and val == 'TAESD': + params["Full quality"] = False + elif size is not None: + params[f"{key}-1"] = int(size.group(1)) + params[f"{key}-2"] = int(size.group(2)) + elif isinstance(params[key], str): + params[key] = val + debug(f'Param parsed: type={type(params[key])} {key}={params[key]} raw="{val}"') + + return params + + +mapping = [ + ('Backend', 'sd_backend'), + ('Model hash', 'sd_model_checkpoint'), + ('Refiner', 'sd_model_refiner'), + ('VAE', 'sd_vae'), + ('Parser', 'prompt_attention'), + ('Color correction', 'img2img_color_correction'), + # Samplers + ('Sampler Eta', 'scheduler_eta'), + ('Sampler ENSD', 'eta_noise_seed_delta'), + ('Sampler order', 'schedulers_solver_order'), + # Samplers diffusers + ('Sampler beta schedule', 'schedulers_beta_schedule'), + ('Sampler beta start', 'schedulers_beta_start'), + ('Sampler beta end', 'schedulers_beta_end'), + ('Sampler DPM solver', 'schedulers_dpm_solver'), + # Samplers original + ('Sampler brownian', 'schedulers_brownian_noise'), + ('Sampler discard', 'schedulers_discard_penultimate'), + ('Sampler dyn threshold', 'schedulers_use_thresholding'), + ('Sampler karras', 'schedulers_use_karras'), + ('Sampler low order', 'schedulers_use_loworder'), + ('Sampler quantization', 'enable_quantization'), + ('Sampler sigma', 'schedulers_sigma'), + ('Sampler sigma min', 's_min'), + ('Sampler sigma max', 's_max'), + ('Sampler sigma churn', 's_churn'), + ('Sampler sigma uncond', 's_min_uncond'), + ('Sampler sigma noise', 's_noise'), + ('Sampler sigma tmin', 's_tmin'), + ('Sampler ENSM', 'initial_noise_multiplier'), # img2img only + ('UniPC skip type', 'uni_pc_skip_type'), + ('UniPC variant', 'uni_pc_variant'), + # Token Merging + ('Mask weight', 'inpainting_mask_weight'), + ('ToMe', 'tome_ratio'), + ('ToDo', 'todo_ratio'), +] + + +if __name__ == '__main__': + import logging + log = logging.getLogger(__name__) + logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)s | %(message)s') + debug = log.info + + import sys + if len(sys.argv) > 1: + if os.path.exists(sys.argv[1]): + with open(sys.argv[1], 'r', encoding='utf8') as f: + parse(f.read()) + else: + parse(sys.argv[1]) diff --git a/modules/postprocessing.py b/modules/postprocessing.py index 4e307bec3..2288401df 100644 --- a/modules/postprocessing.py +++ b/modules/postprocessing.py @@ -4,7 +4,7 @@ from typing import List from PIL import Image -from modules import shared, images, devices, scripts, scripts_postprocessing, generation_parameters_copypaste +from modules import shared, images, devices, scripts, scripts_postprocessing, infotext from modules.shared import opts @@ -17,7 +17,7 @@ def run_postprocessing(extras_mode, image, image_folder: List[tempfile.NamedTemp image_ext = [] outputs = [] params = {} - infotext = '' + info = '' if extras_mode == 1: for img in image_folder: if isinstance(img, Image.Image): @@ -63,7 +63,7 @@ def run_postprocessing(extras_mode, image, image_folder: List[tempfile.NamedTemp processed_images = [] for image, name, ext in zip(image_data, image_names, image_ext): # pylint: disable=redefined-argument-from-local shared.log.debug(f'Process: image={image} {args}') - infotext = '' + info = '' if shared.state.interrupted: shared.log.debug('Postprocess interrupted') break @@ -73,27 +73,27 @@ def run_postprocessing(extras_mode, image, image_folder: List[tempfile.NamedTemp pp = scripts_postprocessing.PostprocessedImage(image.convert("RGB")) scripts.scripts_postproc.run(pp, args) geninfo, items = images.read_info_from_image(image) - params = generation_parameters_copypaste.parse_generation_parameters(geninfo) + params = infotext.parse(geninfo) 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 + info = items['parameters'] + ', ' + info = info + ", ".join([k if k == v else f'{k}: {info.quote(v)}' for k, v in pp.info.items() if v is not None]) + pp.image.info["postprocessing"] = info processed_images.append(pp.image) if save_output: if opts.use_original_name_batch and name is not None: forced_filename = os.path.splitext(os.path.basename(name))[0] - images.save_image(pp.image, path=outpath, 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=forced_filename) + images.save_image(pp.image, path=outpath, extension=ext or opts.samples_format, info=info, short_filename=True, no_prompt=True, grid=False, pnginfo_section_name="extras", existing_info=pp.image.info, forced_filename=forced_filename) else: - images.save_image(pp.image, path=outpath, 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) + images.save_image(pp.image, path=outpath, extension=ext or opts.samples_format, info=info, short_filename=True, no_prompt=True, grid=False, pnginfo_section_name="extras", existing_info=pp.image.info) if extras_mode != 2 or show_extras_results: outputs.append(pp.image) image.close() scripts.scripts_postproc.postprocess(processed_images, args) devices.torch_gc() - return outputs, infotext, params + return outputs, info, params 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/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index 7f6b90d6d..bfe267e2e 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -130,8 +130,6 @@ def get_tokens(msg, prompt): def encode_prompts(pipe, p, prompts: list, negative_prompts: list, steps: int, clip_skip: typing.Optional[int] = None): - if getattr(p, 'hidiffusion', False) is True: - cache.clear() if 'StableDiffusion' not in pipe.__class__.__name__ and 'DemoFusion' not in pipe.__class__.__name__ and 'StableCascade' not in pipe.__class__.__name__: shared.log.warning(f"Prompt parser not supported: {pipe.__class__.__name__}") return diff --git a/modules/styles.py b/modules/styles.py index 511cfc425..d118800e8 100644 --- a/modules/styles.py +++ b/modules/styles.py @@ -6,7 +6,7 @@ import csv import json import time import random -from modules import files_cache, shared +from modules import files_cache, shared, infotext class Style(): @@ -132,12 +132,11 @@ def apply_styles_to_extra(p, style: Style): name_exclude = [ 'size', ] - from modules.generation_parameters_copypaste import parse_generation_parameters reference_style = get_reference_style() - extra = parse_generation_parameters(reference_style) if shared.opts.extra_network_reference else {} + extra = infotext.parse(reference_style) if shared.opts.extra_network_reference else {} style_extra = apply_wildcards_to_prompt(style.extra, [style.wildcards], silent=True) - extra.update(parse_generation_parameters(style_extra)) + extra.update(infotext.parse(style_extra)) extra.pop('Prompt', None) extra.pop('Negative prompt', None) fields = [] diff --git a/modules/ui_common.py b/modules/ui_common.py index ec1d7f505..9ad1e14c1 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -6,7 +6,7 @@ import platform import subprocess from functools import reduce import gradio as gr -from modules import call_queue, shared, prompt_parser, ui_sections, ui_symbols, ui_components, generation_parameters_copypaste, images, scripts, script_callbacks +from modules import call_queue, shared, prompt_parser, ui_sections, ui_symbols, ui_components, generation_parameters_copypaste, images, scripts, script_callbacks, infotext folder_symbol = ui_symbols.folder @@ -23,8 +23,8 @@ 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, generation_info - infotext = generation_info["infotexts"][img_index] - html_info_formatted = infotext_to_html(infotext) + info = generation_info["infotexts"][img_index] + html_info_formatted = infotext_to_html(info) return html_info, html_info_formatted except Exception: pass @@ -37,7 +37,7 @@ def plaintext_to_html(text): def infotext_to_html(text): - res = generation_parameters_copypaste.parse_generation_parameters(text) + res = infotext.parse(text) prompt = res.get('Prompt', '') negative = res.get('Negative prompt', '') res.pop('Prompt', None) @@ -169,7 +169,7 @@ def save_files(js_data, files, html_info, index): if (js_data is None or len(js_data) == 0) and image is not None and image.info is not None: info = image.info.pop('parameters', None) or image.info.pop('UserComment', None) geninfo, _ = images.read_info_from_image(image) - items = generation_parameters_copypaste.parse_generation_parameters(geninfo) + items = infotext.parse(geninfo) p = PObject(items) fullfn, txt_fullfn = images.save_image(image, shared.opts.outdir_save, "", seed=p.all_seeds[i], prompt=p.all_prompts[i], info=info, extension=shared.opts.samples_format, grid=is_grid, p=p) if fullfn is None: diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 4f0b48d26..8f93aef24 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -16,7 +16,7 @@ from collections import OrderedDict import gradio as gr from PIL import Image from starlette.responses import FileResponse, JSONResponse -from modules import paths, shared, scripts, files_cache, errors +from modules import paths, shared, scripts, files_cache, errors, infotext from modules.ui_components import ToolButton import modules.ui_symbols as symbols @@ -877,28 +877,26 @@ def create_ui(container, button_parent, tabname, skip_indexing = False): return ui_refresh_click(title) def ui_save_click(): - from modules import generation_parameters_copypaste filename = os.path.join(paths.data_path, "params.txt") if os.path.exists(filename): with open(filename, "r", encoding="utf8") as file: prompt = file.read() else: prompt = '' - params = generation_parameters_copypaste.parse_generation_parameters(prompt) + params = infotext.parse(prompt) res = show_details(text=None, img=None, desc=None, info=None, meta=None, parameters=None, description=None, prompt=None, negative=None, wildcards=None, params=params) return res def ui_quicksave_click(name): if name is None: return - from modules import generation_parameters_copypaste fn = os.path.join(paths.data_path, "params.txt") if os.path.exists(fn): with open(fn, "r", encoding="utf8") as file: prompt = file.read() else: prompt = '' - params = generation_parameters_copypaste.parse_generation_parameters(prompt) + params = infotext.parse(prompt) fn = os.path.join(shared.opts.styles_dir, os.path.splitext(name)[0] + '.json') prompt = params.get('Prompt', '') item = {