improve metadata parser

This commit is contained in:
Vladimir Mandic
2024-06-06 11:24:31 -04:00
parent 8a9c10e665
commit 9bc217644d
10 changed files with 162 additions and 220 deletions
+2 -1
View File
@@ -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
+5 -60
View File
@@ -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):
+2 -2
View File
@@ -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)
+5 -131
View File
@@ -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 = {}
+127
View File
@@ -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])
+10 -10
View File
@@ -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
-2
View File
@@ -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
+3 -4
View File
@@ -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 = []
+5 -5
View File
@@ -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:
+3 -5
View File
@@ -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 = {