Spaces:
Paused
Paused
from PIL import Image | |
import piexif | |
import json | |
import html | |
import re | |
import gzip | |
re_param_code = r'\s*([\w ]+):\s*("(?:\\.|[^\\"])+"|[^,]*)(?:,|$)' | |
re_param = re.compile(re_param_code) | |
re_imagesize = re.compile(r"^(\d+)x(\d+)$") | |
IGNORED_INFO_KEYS = { | |
'jfif', 'jfif_version', 'jfif_unit', 'jfif_density', 'dpi', 'exif', | |
'loop', 'background', 'timestamp', 'duration', 'progressive', 'progression', | |
'icc_profile', 'chromaticity', 'photoshop', 'srgb', 'gamma', 'dpi' | |
} | |
def plaintext_to_html(text, classname=None): | |
content = "<br>\n".join(html.escape(x) for x in text.split('\n')) | |
return f"<p class='{classname}'>{content}</p>" if classname else f"<p>{content}</p>" | |
def to_digit(v): | |
try: | |
return float(v) | |
except: | |
return v | |
def read_stealth_info(image): | |
width, height = image.size | |
pixels = image.load() | |
has_alpha = True if image.mode == 'RGBA' else False | |
mode = None | |
compressed = False | |
binary_data = '' | |
buffer_a = '' | |
buffer_rgb = '' | |
index_a = 0 | |
index_rgb = 0 | |
sig_confirmed = False | |
confirming_signature = True | |
reading_param_len = False | |
reading_param = False | |
read_end = False | |
for x in range(width): | |
for y in range(height): | |
if has_alpha: | |
r, g, b, a = pixels[x, y] | |
buffer_a += str(a & 1) | |
index_a += 1 | |
else: | |
r, g, b = pixels[x, y] | |
buffer_rgb += str(r & 1) | |
buffer_rgb += str(g & 1) | |
buffer_rgb += str(b & 1) | |
index_rgb += 3 | |
if confirming_signature: | |
if index_a == len('stealth_pnginfo') * 8: | |
decoded_sig = bytearray(int(buffer_a[i:i + 8], 2) for i in | |
range(0, len(buffer_a), 8)).decode('utf-8', errors='ignore') | |
if decoded_sig in {'stealth_pnginfo', 'stealth_pngcomp'}: | |
confirming_signature = False | |
sig_confirmed = True | |
reading_param_len = True | |
mode = 'alpha' | |
if decoded_sig == 'stealth_pngcomp': | |
compressed = True | |
buffer_a = '' | |
index_a = 0 | |
else: | |
read_end = True | |
break | |
elif index_rgb == len('stealth_pnginfo') * 8: | |
decoded_sig = bytearray(int(buffer_rgb[i:i + 8], 2) for i in | |
range(0, len(buffer_rgb), 8)).decode('utf-8', errors='ignore') | |
if decoded_sig in {'stealth_rgbinfo', 'stealth_rgbcomp'}: | |
confirming_signature = False | |
sig_confirmed = True | |
reading_param_len = True | |
mode = 'rgb' | |
if decoded_sig == 'stealth_rgbcomp': | |
compressed = True | |
buffer_rgb = '' | |
index_rgb = 0 | |
elif reading_param_len: | |
if mode == 'alpha': | |
if index_a == 32: | |
param_len = int(buffer_a, 2) | |
reading_param_len = False | |
reading_param = True | |
buffer_a = '' | |
index_a = 0 | |
else: | |
if index_rgb == 33: | |
pop = buffer_rgb[-1] | |
buffer_rgb = buffer_rgb[:-1] | |
param_len = int(buffer_rgb, 2) | |
reading_param_len = False | |
reading_param = True | |
buffer_rgb = pop | |
index_rgb = 1 | |
elif reading_param: | |
if mode == 'alpha': | |
if index_a == param_len: | |
binary_data = buffer_a | |
read_end = True | |
break | |
else: | |
if index_rgb >= param_len: | |
diff = param_len - index_rgb | |
if diff < 0: | |
buffer_rgb = buffer_rgb[:diff] | |
binary_data = buffer_rgb | |
read_end = True | |
break | |
else: | |
# impossible | |
read_end = True | |
break | |
if read_end: | |
break | |
if sig_confirmed and binary_data != '': | |
# Convert binary string to UTF-8 encoded text | |
byte_data = bytearray(int(binary_data[i:i + 8], 2) for i in range(0, len(binary_data), 8)) | |
try: | |
if compressed: | |
decoded_data = gzip.decompress(bytes(byte_data)).decode('utf-8') | |
else: | |
decoded_data = byte_data.decode('utf-8', errors='ignore') | |
geninfo = decoded_data | |
except: | |
pass | |
return json.loads(geninfo) | |
def read_info_from_image(image): | |
if image is None: | |
return '', {} | |
elif type(image) == str: | |
image = Image.open(image) | |
items = (image.info or {}).copy() | |
info ='' | |
for field in IGNORED_INFO_KEYS: | |
items.pop(field, None) | |
if len(items) == 0: | |
items = read_stealth_info(image) | |
if "exif" in items: | |
exif = piexif.load(items["exif"]) | |
exif_comment = (exif or {}).get("Exif", {}).get(piexif.ExifIFD.UserComment, b'') | |
try: | |
exif_comment = piexif.helper.UserComment.load(exif_comment) | |
except ValueError: | |
exif_comment = exif_comment.decode('utf8', errors="ignore") | |
if exif_comment: | |
items['exif comment'] = exif_comment | |
geninfo = exif_comment | |
if items.get("Software", None) == "NovelAI": | |
json_info = json.loads(items["Comment"]) | |
geninfo = f"""{items["Description"]} | |
Negative prompt: {json_info["uc"]} | |
Steps: {json_info["steps"]}, Sampler: {json_info['sampler']}, CFG scale: {json_info["scale"]}, Seed: {json_info["seed"]}, Size: {image.width}x{image.height}, Clip skip: 2, ENSD: 31337""" | |
items = {**{'parameters': geninfo}, **items} | |
for key, text in items.items(): | |
info += f""" | |
<div> | |
<p><b>{plaintext_to_html(str(key))}</b></p> | |
<p>{plaintext_to_html(str(text))}</p> | |
</div> | |
""".strip()+"\n" | |
if len(info) == 0: | |
message = "Nothing found in the image." | |
info = f"<div><p>{message}<p></div>" | |
elif 'parameters' in items: | |
res = {} | |
prompt = "" | |
negative_prompt = "" | |
done_with_prompt = False | |
*lines, lastline = items['parameters'].strip().split("\n") | |
if len(re_param.findall(lastline)) < 3: | |
lines.append(lastline) | |
lastline = '' | |
for line in lines: | |
line = line.strip() | |
if line.startswith("Negative prompt:"): | |
done_with_prompt = True | |
line = line[16:].strip() | |
if done_with_prompt: | |
negative_prompt += ("" if negative_prompt == "" else "\n") + line | |
else: | |
prompt += ("" if prompt == "" else "\n") + line | |
res["prompt"] = prompt | |
res["negative prompt"] = negative_prompt | |
for k, v in re_param.findall(lastline): | |
try: | |
if v[0] == '"' and v[-1] == '"': | |
v = to_digit(json.loads(v)) | |
m = re_imagesize.match(v) | |
if m is not None: | |
res[f"{k.lower()}-1"] = to_digit(m.group(1)) | |
res[f"{k.lower()}-2"] = to_digit(m.group(2)) | |
else: | |
res[k.lower()] = v | |
except Exception: | |
print(f"Error parsing \"{k}: {v}\"") | |
return items['parameters'], res | |
else: | |
return info, {} | |
return info, json.loads(items['Comment']) | |
def send_paras(*args): | |
if args[0] is None or len(args[0]) == 0: | |
return args[1:] | |
items, prompt, quality_tags, neg_prompt, seed, scale, width, height, steps, sampler, scheduler, smea, dyn, dyn_threshold, cfg_rescale, variety = args | |
paras = [prompt, quality_tags, neg_prompt, seed, scale, width, height, steps, sampler, scheduler, smea, dyn, dyn_threshold, cfg_rescale, variety] | |
for i, keys in enumerate([('prompt',), ('',), ('uc', 'negative prompt'), ('seed',), ('scale', 'cfg scale'), ('width', 'size-1'), ('height', 'size-2'), ('steps',), ('sampler',), ('noise_schedule',), ('sm',), ('sm_dyn',), ('dynamic_thresholding',), ('cfg_rescale',), ('skip_cfg_above_sigma',)]): | |
for key in keys: | |
if key in items: | |
paras[i] = items[key] | |
prompt, quality_tags, neg_prompt, seed, scale, width, height, steps, sampler, scheduler, smea, dyn, dyn_threshold, cfg_rescale, variety = paras | |
variety = (variety != None) if 'skip_cfg_above_sigma' in items else variety | |
if prompt.endswith(', ' + quality_tags): | |
prompt = prompt[:-(len(quality_tags) + 2)] | |
return prompt, quality_tags, neg_prompt, seed, scale, width, height, steps, sampler, scheduler, smea, dyn, dyn_threshold, cfg_rescale, variety |