from PIL import Image, ExifTags, PngImagePlugin from sd_parsers import ParserManager from io import BytesIO from pathlib import Path from os import path import sys import time from typing import Union #def format_metadata(params: dict) -> str: def format_metadata(params): lines = [] lines.append(params['prompt']) loras = params.get('lora', {}) lorainfo=[] for name, props in sorted(loras.items()): weight = props.get('weight') weight = float(weight) if weight else 0.0 if name and weight: lorainfo.append(''.format(name,weight)) if lorainfo: lines.append(''.join(lorainfo)) negative_prompt = params.get('negative_prompt') if negative_prompt is not None: lines.append('Negative prompt: ' + negative_prompt) info = [] info.append(('Steps', params['num_inference_steps'])) sampler = params.get('sampler') if sampler: info.append(('Sampler', sampler)) info.append(('CFG Scale', params['guidance_scale'])) seed = params.get('seed', -1) if seed != -1: info.append(('Seed', seed)) width, height = params.get('width'), params.get('height') if width and height: info.append(('Size', '{}x{}'.format(width, height))) model = params.get('model') if model is not None: if model.endswith('.safetensors'): # filename without extension model = path.basename(model).rsplit('.', 1)[0] # else assume it's a model id info.append(('Model', model)) mode = params.get('mode') # Img2Img, Txt2Img if mode is not None: info.append(('Mode', mode)) if mode == 'Img2Img': strength = params.get('strength') if strength is not None: info.append(('Denoising strength', strength)) clip_skip = params.get('clip_skip', 0) if clip_skip >= 1: info.append(('Clip skip', clip_skip+1)) controlnet_model = params.get('cnet_model', '') controlnet_conditioning_scale = params.get('controlnet_conditioning_scale', 0.0) if controlnet_model and controlnet_conditioning_scale >= 0.001: info.append(('ControlNet', controlnet_conditioning_scale)) info.append(('ControlNet Conditioning Scale', controlnet_conditioning_scale)) if params.get('guess_mode'): info.append(('ControlNet Guess Mode', 1)) vae = params.get('vae') if vae is not None: info.append(('VAE', vae)) for (opt, meta) in [ ('compel', 'Compel'), ('freeu', 'FreeU'), ('resadapter_norm', 'ResAdapter Normalization'), ('vae_tiling', 'VAE Tiling'), ]: if params.get(opt): info.append((meta, 1)) for (opt, meta) in [ ('eta', 'Eta'), ('pag_adaptive_scale', 'PAG Adaptive Scale'), ('pag_scale', 'PAG Scale'), ('sag_scale', 'SAG Scale'), ('token_merging_ratio', 'Token Merging Ratio'), ]: val = params.get(opt, 0.0) if val and val >= 0.01: info.append((meta, val)) lines.append(', '.join(['{}: {}'.format(k, v) for (k, v) in info])) return '\n'.join(lines) #def add_metadata_to_pil_image(pil_image: Image, params: Union[dict, str])-> None: def add_metadata_to_pil_image(pil_image, params): '''add generation parameters to the image info fields, in a Gradio-compatible way''' if isinstance(params, str): metadata = params else: metadata = format_metadata(params) pil_image.info['parameters'] = metadata # borrowed from piexif usercomment = b'UNICODE\0' + metadata.encode('utf_16_be', errors='replace') # The PIL Exif encoder detects both bytes and bytearrays as sequences of # integers, so they get encoded with the wrong type, and most tools won't # interpret that as text. A list wrapping a bytearray dodges those # heuristics, correctly storing the data as a byte sequence. usercomment = [bytearray(usercomment)] exif = pil_image.getexif() exif.setdefault(ExifTags.IFD.Exif, {})[ExifTags.Base.UserComment] = usercomment pil_image.info['exif'] = exif.tobytes() def get_image_meta_str(image): res = [] if image.filename: res.append('Filename: {}'.format(image.filename)) if image.format: res.append('Format: {}'.format(image.format)) res.append('Size: {}x{}'.format(*image.size)) info = ParserManager().parse(image) if info: for prompt in info.prompts: if prompt: res.append('Prompt: {}'.format(prompt)) for negative_prompt in info.negative_prompts: if negative_prompt: res.append('Negative Prompt: {}'.format(negative_prompt)) for sampler in info.samplers: if sampler.name: res.append('Sampler: {}'.format(sampler.name)) seed = sampler.parameters.get('seed') if seed: res.append('Seed: {}'.format(seed)) steps = sampler.parameters.get('steps') if steps: res.append('Steps: {}'.format(steps)) if sampler.model and sampler.model.name: res.append('Model: {}'.format(sampler.model.name)) for (meta, value) in sorted(info.metadata.items()): res.append('{}: {}'.format(meta, value)) return '\n'.join(res) #def extract_meta_for_saving(pil_image: Image): def extract_meta_for_saving(pil_image): meta_args = {} exif = pil_image.info.get('exif') if exif: meta_args["exif"] = exif pngtexts = [ (key, value) for key, value in pil_image.info.items() if isinstance(key, str) and isinstance(value, str) ] if pngtexts: pnginfo = PngImagePlugin.PngInfo() for key, value in pngtexts: pnginfo.add_text(key, value) meta_args["pnginfo"] = pnginfo return meta_args def filebytes(pil_image, format): with BytesIO() as output_bytes: meta_params = extract_meta_for_saving(pil_image) if format in ['jpeg', 'jpg', 'webp']: meta_params['quality'] = 90 pil_image.save(output_bytes, format=format, **meta_params) return output_bytes.getvalue() def open_file_timestamp(basename, directory='.', extension='.png'): output = Path(directory) count = 0 opt = True spbasename = basename.rsplit('-', 1) if len(spbasename) == 2 and len(spbasename[1]) == 4 and spbasename[1].isdigit(): count = int(spbasename[1]) basename = spbasename[0] opt = False def getfilepath(count): extra = '-{:04}'.format(count) return output / '{}{}.{}'.format(basename, extra, extension) filepath = getfilepath(count) # otimizacao if opt: existing = sorted(output.glob(basename + '*.' + extension)) if existing: count = 1 last = existing[-1].name[:len(extension)] sepnum = (last.rsplit('-', 1)+[''])[1] if sepnum.isdigit(): count = int(sepnum) filepath = getfilepath(count) while True: try: return open(filepath, 'xb') except FileExistsError: count += 1 filepath = getfilepath(count) def save_image_with_meta(pil_image, basename, directory, format="png"): filedata = filebytes(pil_image, format) with open_file_timestamp(basename, directory, format) as outfile: outfile.write(filedata) return outfile.name def save_image_timestamp(pil_image, directory, format="png"): outdir = Path(directory) basename = time.strftime('%Y%m%d-0001') return save_image_with_meta(pil_image, basename, outdir, format)