From c5a15c7321683e64fbacd0d3c6809e01d6616a5d Mon Sep 17 00:00:00 2001 From: Manuel Schmid Date: Mon, 15 Jan 2024 18:28:33 +0100 Subject: [PATCH 01/12] feat: add metadata logging for images inspired by https://github.com/MoonRide303/Fooocus-MRE --- modules/async_worker.py | 70 ++++++++++++++++++++++++++++++++++++++- modules/private_logger.py | 12 +++++-- 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/modules/async_worker.py b/modules/async_worker.py index b2af6712..4221f70c 100644 --- a/modules/async_worker.py +++ b/modules/async_worker.py @@ -16,6 +16,7 @@ def worker(): import traceback import math + import json import numpy as np import torch import time @@ -354,6 +355,9 @@ def worker(): progressbar(async_task, 1, 'Initializing ...') + raw_prompt = prompt + raw_negative_prompt = negative_prompt + if not skip_prompt_processing: prompts = remove_empty_str([safe_str(p) for p in prompt.splitlines()], default='') @@ -774,6 +778,70 @@ def worker(): if inpaint_worker.current_task is not None: imgs = [inpaint_worker.current_task.post_process(x) for x in imgs] + metadata = { + 'prompt': raw_prompt, 'negative_prompt': raw_negative_prompt, 'styles': str(raw_style_selections), + 'real_prompt': task['log_positive_prompt'], 'real_negative_prompt': task['log_negative_prompt'], + 'seed': task['task_seed'], 'width': width, 'height': height, + 'sampler': sampler_name, 'scheduler': scheduler_name, 'performance': performance_selection, + 'steps': steps, 'refiner_switch': refiner_switch, 'sharpness': sharpness, 'cfg': cfg_scale, + 'base_model': base_model_name, 'refiner_model': refiner_model_name, + 'freeu': advanced_parameters.freeu_enabled, + 'img2img': input_image_checkbox, + 'prompt_expansion': task['expansion'] + } + + if advanced_parameters.freeu_enabled: + metadata |= { + 'freeu_b1': advanced_parameters.freeu_b1, 'freeu_b2': advanced_parameters.freeu_b2, 'freeu_s1': advanced_parameters.freeu_s1, 'freeu_s2': advanced_parameters.freeu_s2 + } + + if 'vary' in goals: + metadata |= { + 'uov_method': uov_method, 'denoising_strength': denoising_strength, + #'uov_input_image': raw_uov_input_image + } + + if 'upscale' in goals: + metadata |= { + 'uov_method': uov_method, 'scale': f, + #'uov_input_image': uov_input_image + } + + if 'inpaint' in goals: + if len(outpaint_selections) > 0: + metadata |= { + 'outpaint_selections': outpaint_selections + } + else: + metadata |= { + 'inpaint_additional_prompt': inpaint_additional_prompt, 'inpaint_mask_upload': advanced_parameters.inpaint_mask_upload_checkbox, 'invert_mask': advanced_parameters.invert_mask_checkbox, + 'inpaint_disable_initial_latent': advanced_parameters.inpaint_disable_initial_latent, 'inpaint_engine': advanced_parameters.inpaint_engine, + 'inpaint_strength': advanced_parameters.inpaint_strength, 'inpaint_respective_field': advanced_parameters.inpaint_respective_field, + #'inpaint_image': inpaint_image, 'inpaint_mask': inpaint_mask + } + + if 'cn' in goals: + metadata |= { + 'canny_low_threshold': advanced_parameters.canny_low_threshold, 'canny_high_threshold': advanced_parameters.canny_high_threshold, + } + + ip_list = {x: [] for x in flags.ip_list} + cn_task_index = 1 + for cn_type in ip_list: + for cn_task in cn_tasks[cn_type]: + cn_img, cn_stop, cn_weight = cn_task + metadata |= { + # TODO check (A1111) compatibility + f'image_prompt_{cn_task_index}': { + 'cn_type': cn_type, 'cn_stop': cn_stop, 'cn_weight': cn_weight, + #'cn_image': cn_img + } + } + cn_task_index += 1 + + metadata |= {'software': f'Fooocus v{fooocus_version.version}'} + metadata_string = json.dumps(metadata, ensure_ascii=False) + for x in imgs: d = [ ('Prompt', task['log_positive_prompt']), @@ -799,7 +867,7 @@ def worker(): if n != 'None': d.append((f'LoRA {li + 1}', f'{n} : {w}')) d.append(('Version', 'v' + fooocus_version.version)) - log(x, d) + log(x, d, metadata_string, True) yield_result(async_task, imgs, do_not_show_finished_images=len(tasks) == 1) except ldm_patched.modules.model_management.InterruptProcessingException as e: diff --git a/modules/private_logger.py b/modules/private_logger.py index 968bd4f5..9cfbfc92 100644 --- a/modules/private_logger.py +++ b/modules/private_logger.py @@ -5,6 +5,7 @@ import json import urllib.parse from PIL import Image +from PIL.PngImagePlugin import PngInfo from modules.util import generate_temp_filename @@ -18,13 +19,20 @@ def get_current_html_path(): return html_name -def log(img, dic): +def log(img, dic, metadata=None, save_metadata_to_image=False): if args_manager.args.disable_image_log: return date_string, local_temp_filename, only_name = generate_temp_filename(folder=modules.config.path_outputs, extension='png') os.makedirs(os.path.dirname(local_temp_filename), exist_ok=True) - Image.fromarray(img).save(local_temp_filename) + + if save_metadata_to_image: + pnginfo = PngInfo() + pnginfo.add_text("Comment", metadata) + else: + pnginfo = None + Image.fromarray(img).save(local_temp_filename, pnginfo=pnginfo) + html_name = os.path.join(os.path.dirname(local_temp_filename), 'log.html') css_styles = ( From 8d56318bed0275bcf2d9fed46cf9e3b6e6ae296f Mon Sep 17 00:00:00 2001 From: Manuel Schmid Date: Mon, 15 Jan 2024 18:54:49 +0100 Subject: [PATCH 02/12] feat: add config and checkbox for save_metadata_to_images --- modules/async_worker.py | 7 ++++--- modules/config.py | 6 ++++++ webui.py | 3 +++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/modules/async_worker.py b/modules/async_worker.py index 4221f70c..f63d2416 100644 --- a/modules/async_worker.py +++ b/modules/async_worker.py @@ -141,6 +141,7 @@ def worker(): inpaint_input_image = args.pop() inpaint_additional_prompt = args.pop() inpaint_mask_image_upload = args.pop() + save_metadata_to_images = args.pop() cn_tasks = {x: [] for x in flags.ip_list} for _ in range(4): @@ -839,8 +840,8 @@ def worker(): } cn_task_index += 1 - metadata |= {'software': f'Fooocus v{fooocus_version.version}'} - metadata_string = json.dumps(metadata, ensure_ascii=False) + metadata |= {'software': f'Fooocus v{fooocus_version.version}'} + metadata_string = json.dumps(metadata, ensure_ascii=False) for x in imgs: d = [ @@ -867,7 +868,7 @@ def worker(): if n != 'None': d.append((f'LoRA {li + 1}', f'{n} : {w}')) d.append(('Version', 'v' + fooocus_version.version)) - log(x, d, metadata_string, True) + log(x, d, metadata_string, save_metadata_to_images) yield_result(async_task, imgs, do_not_show_finished_images=len(tasks) == 1) except ldm_patched.modules.model_management.InterruptProcessingException as e: diff --git a/modules/config.py b/modules/config.py index c7af33db..7d760edf 100644 --- a/modules/config.py +++ b/modules/config.py @@ -315,6 +315,11 @@ example_inpaint_prompts = get_config_item_or_set_default( ], validator=lambda x: isinstance(x, list) and all(isinstance(v, str) for v in x) ) +default_save_metadata_to_images = get_config_item_or_set_default( + key='default_save_metadata_to_images', + default_value=False, + validator=lambda x: isinstance(x, bool) +) example_inpaint_prompts = [[x] for x in example_inpaint_prompts] @@ -334,6 +339,7 @@ possible_preset_keys = [ "default_prompt_negative", "default_styles", "default_aspect_ratio", + "default_save_metadata_to_images", "checkpoint_downloads", "embeddings_downloads", "lora_downloads", diff --git a/webui.py b/webui.py index 581e3101..1040f876 100644 --- a/webui.py +++ b/webui.py @@ -381,6 +381,8 @@ with shared.gradio_root: info='Set as negative number to disable. For developer debugging.') disable_preview = gr.Checkbox(label='Disable Preview', value=False, info='Disable preview during generation.') + save_metadata_to_images = gr.Checkbox(label='Save Metadata to Images', value=modules.config.default_save_metadata_to_images, + info='Adds parameters to generated images allowing manual regeneration.') with gr.Tab(label='Control'): debugging_cn_preprocessor = gr.Checkbox(label='Debug Preprocessors', value=False, @@ -528,6 +530,7 @@ with shared.gradio_root: ctrls += [input_image_checkbox, current_tab] ctrls += [uov_method, uov_input_image] ctrls += [outpaint_selections, inpaint_input_image, inpaint_additional_prompt, inpaint_mask_image] + ctrls += [save_metadata_to_images] ctrls += ip_ctrls state_is_generating = gr.State(False) From 493e484122c1fd943c81664464a4397f8fbb2892 Mon Sep 17 00:00:00 2001 From: Manuel Schmid Date: Mon, 15 Jan 2024 19:07:19 +0100 Subject: [PATCH 03/12] feat: add argument disable_metadata --- args_manager.py | 5 ++++- modules/async_worker.py | 7 ++++--- webui.py | 11 ++++++++--- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/args_manager.py b/args_manager.py index e5e76753..f675eb56 100644 --- a/args_manager.py +++ b/args_manager.py @@ -18,7 +18,10 @@ args_parser.parser.add_argument("--disable-image-log", action='store_true', help="Prevent writing images and logs to hard drive.") args_parser.parser.add_argument("--disable-analytics", action='store_true', - help="Disables analytics for Gradio", default=False) + help="Disables analytics for Gradio.") + +args_parser.parser.add_argument("--disable-metadata", action='store_true', + help="Disables saving metadata to images.") args_parser.parser.set_defaults( disable_cuda_malloc=True, diff --git a/modules/async_worker.py b/modules/async_worker.py index f63d2416..3e0c1bac 100644 --- a/modules/async_worker.py +++ b/modules/async_worker.py @@ -36,6 +36,7 @@ def worker(): import extras.ip_adapter as ip_adapter import extras.face_crop import fooocus_version + import args_manager from modules.sdxl_styles import apply_style, apply_wildcards, fooocus_expansion from modules.private_logger import log @@ -141,7 +142,7 @@ def worker(): inpaint_input_image = args.pop() inpaint_additional_prompt = args.pop() inpaint_mask_image_upload = args.pop() - save_metadata_to_images = args.pop() + save_metadata_to_images = args.pop() if not args_manager.args.disable_metadata else False cn_tasks = {x: [] for x in flags.ip_list} for _ in range(4): @@ -840,8 +841,8 @@ def worker(): } cn_task_index += 1 - metadata |= {'software': f'Fooocus v{fooocus_version.version}'} - metadata_string = json.dumps(metadata, ensure_ascii=False) + metadata |= {'software': f'Fooocus v{fooocus_version.version}'} + metadata_string = json.dumps(metadata, ensure_ascii=False) for x in imgs: d = [ diff --git a/webui.py b/webui.py index 1040f876..e3814ff9 100644 --- a/webui.py +++ b/webui.py @@ -381,8 +381,10 @@ with shared.gradio_root: info='Set as negative number to disable. For developer debugging.') disable_preview = gr.Checkbox(label='Disable Preview', value=False, info='Disable preview during generation.') - save_metadata_to_images = gr.Checkbox(label='Save Metadata to Images', value=modules.config.default_save_metadata_to_images, - info='Adds parameters to generated images allowing manual regeneration.') + + if not args_manager.args.disable_metadata: + save_metadata_to_images = gr.Checkbox(label='Save Metadata to Images', value=modules.config.default_save_metadata_to_images, + info='Adds parameters to generated images allowing manual regeneration.') with gr.Tab(label='Control'): debugging_cn_preprocessor = gr.Checkbox(label='Debug Preprocessors', value=False, @@ -530,7 +532,10 @@ with shared.gradio_root: ctrls += [input_image_checkbox, current_tab] ctrls += [uov_method, uov_input_image] ctrls += [outpaint_selections, inpaint_input_image, inpaint_additional_prompt, inpaint_mask_image] - ctrls += [save_metadata_to_images] + + if not args_manager.args.disable_metadata: + ctrls += [save_metadata_to_images] + ctrls += ip_ctrls state_is_generating = gr.State(False) From 191f8148e431f5018fd026a165afb9c175e7653d Mon Sep 17 00:00:00 2001 From: Manuel Schmid Date: Mon, 15 Jan 2024 20:47:26 +0100 Subject: [PATCH 04/12] feat: add support for A1111 metadata schema https://github.com/AUTOMATIC1111/stable-diffusion-webui/blob/cf2772fab0af5573da775e7437e6acdca424f26e/modules/processing.py#L672 --- modules/async_worker.py | 127 +++++++++++++++++++++++----------------- modules/config.py | 5 ++ modules/flags.py | 5 ++ webui.py | 7 ++- 4 files changed, 90 insertions(+), 54 deletions(-) diff --git a/modules/async_worker.py b/modules/async_worker.py index 3e0c1bac..98eaad4b 100644 --- a/modules/async_worker.py +++ b/modules/async_worker.py @@ -143,6 +143,7 @@ def worker(): inpaint_additional_prompt = args.pop() inpaint_mask_image_upload = args.pop() save_metadata_to_images = args.pop() if not args_manager.args.disable_metadata else False + metadata_schema = args.pop() if not args_manager.args.disable_metadata else 'fooocus' cn_tasks = {x: [] for x in flags.ip_list} for _ in range(4): @@ -780,69 +781,89 @@ def worker(): if inpaint_worker.current_task is not None: imgs = [inpaint_worker.current_task.post_process(x) for x in imgs] - metadata = { - 'prompt': raw_prompt, 'negative_prompt': raw_negative_prompt, 'styles': str(raw_style_selections), - 'real_prompt': task['log_positive_prompt'], 'real_negative_prompt': task['log_negative_prompt'], - 'seed': task['task_seed'], 'width': width, 'height': height, - 'sampler': sampler_name, 'scheduler': scheduler_name, 'performance': performance_selection, - 'steps': steps, 'refiner_switch': refiner_switch, 'sharpness': sharpness, 'cfg': cfg_scale, - 'base_model': base_model_name, 'refiner_model': refiner_model_name, - 'freeu': advanced_parameters.freeu_enabled, - 'img2img': input_image_checkbox, - 'prompt_expansion': task['expansion'] - } - - if advanced_parameters.freeu_enabled: - metadata |= { - 'freeu_b1': advanced_parameters.freeu_b1, 'freeu_b2': advanced_parameters.freeu_b2, 'freeu_s1': advanced_parameters.freeu_s1, 'freeu_s2': advanced_parameters.freeu_s2 + metadata_string = '' + if save_metadata_to_images and metadata_schema == 'fooocus': + metadata = { + 'prompt': raw_prompt, 'negative_prompt': raw_negative_prompt, 'styles': str(raw_style_selections), + 'real_prompt': task['log_positive_prompt'], 'real_negative_prompt': task['log_negative_prompt'], + 'seed': task['task_seed'], 'width': width, 'height': height, + 'sampler': sampler_name, 'scheduler': scheduler_name, 'performance': performance_selection, + 'steps': steps, 'refiner_switch': refiner_switch, 'sharpness': sharpness, 'cfg': cfg_scale, + 'base_model': base_model_name, 'refiner_model': refiner_model_name, + 'denoising_strength': denoising_strength, + 'freeu': advanced_parameters.freeu_enabled, + 'img2img': input_image_checkbox, + 'prompt_expansion': task['expansion'] } - if 'vary' in goals: - metadata |= { - 'uov_method': uov_method, 'denoising_strength': denoising_strength, - #'uov_input_image': raw_uov_input_image - } - if 'upscale' in goals: - metadata |= { - 'uov_method': uov_method, 'scale': f, - #'uov_input_image': uov_input_image - } - - if 'inpaint' in goals: - if len(outpaint_selections) > 0: + if advanced_parameters.freeu_enabled: metadata |= { - 'outpaint_selections': outpaint_selections - } - else: - metadata |= { - 'inpaint_additional_prompt': inpaint_additional_prompt, 'inpaint_mask_upload': advanced_parameters.inpaint_mask_upload_checkbox, 'invert_mask': advanced_parameters.invert_mask_checkbox, - 'inpaint_disable_initial_latent': advanced_parameters.inpaint_disable_initial_latent, 'inpaint_engine': advanced_parameters.inpaint_engine, - 'inpaint_strength': advanced_parameters.inpaint_strength, 'inpaint_respective_field': advanced_parameters.inpaint_respective_field, - #'inpaint_image': inpaint_image, 'inpaint_mask': inpaint_mask + 'freeu_b1': advanced_parameters.freeu_b1, 'freeu_b2': advanced_parameters.freeu_b2, 'freeu_s1': advanced_parameters.freeu_s1, 'freeu_s2': advanced_parameters.freeu_s2 } - if 'cn' in goals: - metadata |= { - 'canny_low_threshold': advanced_parameters.canny_low_threshold, 'canny_high_threshold': advanced_parameters.canny_high_threshold, - } + if 'vary' in goals: + metadata |= { + 'uov_method': uov_method + #'uov_input_image': raw_uov_input_image + } - ip_list = {x: [] for x in flags.ip_list} - cn_task_index = 1 - for cn_type in ip_list: - for cn_task in cn_tasks[cn_type]: - cn_img, cn_stop, cn_weight = cn_task + if 'upscale' in goals: + metadata |= { + 'uov_method': uov_method, 'scale': f, + #'uov_input_image': uov_input_image + } + + if 'inpaint' in goals: + if len(outpaint_selections) > 0: metadata |= { - # TODO check (A1111) compatibility - f'image_prompt_{cn_task_index}': { - 'cn_type': cn_type, 'cn_stop': cn_stop, 'cn_weight': cn_weight, - #'cn_image': cn_img - } + 'outpaint_selections': outpaint_selections + } + else: + metadata |= { + 'inpaint_additional_prompt': inpaint_additional_prompt, 'inpaint_mask_upload': advanced_parameters.inpaint_mask_upload_checkbox, 'invert_mask': advanced_parameters.invert_mask_checkbox, + 'inpaint_disable_initial_latent': advanced_parameters.inpaint_disable_initial_latent, 'inpaint_engine': advanced_parameters.inpaint_engine, + 'inpaint_strength': advanced_parameters.inpaint_strength, 'inpaint_respective_field': advanced_parameters.inpaint_respective_field, + #'inpaint_image': inpaint_image, 'inpaint_mask': inpaint_mask } - cn_task_index += 1 - metadata |= {'software': f'Fooocus v{fooocus_version.version}'} - metadata_string = json.dumps(metadata, ensure_ascii=False) + if 'cn' in goals: + metadata |= { + 'canny_low_threshold': advanced_parameters.canny_low_threshold, 'canny_high_threshold': advanced_parameters.canny_high_threshold, + } + + ip_list = {x: [] for x in flags.ip_list} + cn_task_index = 1 + for cn_type in ip_list: + for cn_task in cn_tasks[cn_type]: + cn_img, cn_stop, cn_weight = cn_task + metadata |= { + f'image_prompt_{cn_task_index}': { + 'cn_type': cn_type, 'cn_stop': cn_stop, 'cn_weight': cn_weight, + #'cn_image': cn_img + } + } + cn_task_index += 1 + + metadata |= {'software': f'Fooocus v{fooocus_version.version}'} + metadata_string = json.dumps(metadata, ensure_ascii=False) + elif save_metadata_to_images and metadata_schema == 'a1111': + generation_params = { + "Steps": steps, + "Sampler": sampler_name, + "CFG scale": cfg_scale, + "Seed": task['task_seed'], + "Size": f"{width}x{height}", + #"Model hash": p.sd_model_hash if opts.add_model_hash_to_info else None, + "Model": base_model_name, + "Denoising strength": denoising_strength, + "Version": f'Fooocus v{fooocus_version.version}', + "User": 'mashb1t', + } + + generation_params_text = ", ".join([k if k == v else f'{k}: {v}' for k, v in generation_params.items() if v is not None]) + negative_prompt_text = f"\nNegative prompt: {raw_negative_prompt}" if raw_negative_prompt else "" + metadata_string = f"{raw_prompt}{raw_negative_prompt}\n{generation_params_text}".strip() for x in imgs: d = [ diff --git a/modules/config.py b/modules/config.py index 7d760edf..28104859 100644 --- a/modules/config.py +++ b/modules/config.py @@ -320,6 +320,11 @@ default_save_metadata_to_images = get_config_item_or_set_default( default_value=False, validator=lambda x: isinstance(x, bool) ) +default_metadata_schema = get_config_item_or_set_default( + key='default_metadata_schema', + default_value='fooocus', + validator=lambda x: x in [y[1] for y in modules.flags.metadata_schema if y[1] == x] +) example_inpaint_prompts = [[x] for x in example_inpaint_prompts] diff --git a/modules/flags.py b/modules/flags.py index 27f2d716..873736df 100644 --- a/modules/flags.py +++ b/modules/flags.py @@ -32,6 +32,11 @@ default_parameters = { cn_ip: (0.5, 0.6), cn_ip_face: (0.9, 0.75), cn_canny: (0.5, 1.0), cn_cpds: (0.5, 1.0) } # stop, weight +metadata_schema =[ + ('Fooocus (json)', 'fooocus'), + ('A1111 (plain text)', 'a1111'), +] + inpaint_engine_versions = ['None', 'v1', 'v2.5', 'v2.6'] performance_selections = ['Speed', 'Quality', 'Extreme Speed'] diff --git a/webui.py b/webui.py index e3814ff9..1a88a87b 100644 --- a/webui.py +++ b/webui.py @@ -385,6 +385,11 @@ with shared.gradio_root: if not args_manager.args.disable_metadata: save_metadata_to_images = gr.Checkbox(label='Save Metadata to Images', value=modules.config.default_save_metadata_to_images, info='Adds parameters to generated images allowing manual regeneration.') + metadata_schema = gr.Radio(label='Metadata Schema', choices=flags.metadata_schema, value=modules.config.default_metadata_schema, + info='Use A1111 for compatibility with Civitai.') + + save_metadata_to_images.change(lambda x: gr.update(visible=not x), inputs=[save_metadata_to_images], outputs=[metadata_schema], + queue=False, show_progress=False) with gr.Tab(label='Control'): debugging_cn_preprocessor = gr.Checkbox(label='Debug Preprocessors', value=False, @@ -534,7 +539,7 @@ with shared.gradio_root: ctrls += [outpaint_selections, inpaint_input_image, inpaint_additional_prompt, inpaint_mask_image] if not args_manager.args.disable_metadata: - ctrls += [save_metadata_to_images] + ctrls += [save_metadata_to_images, metadata_schema] ctrls += ip_ctrls From f7489cc9ef6d9da102166ed4359b0123ade6830d Mon Sep 17 00:00:00 2001 From: Manuel Schmid Date: Mon, 15 Jan 2024 22:11:46 +0100 Subject: [PATCH 05/12] feat: add model hash support for a1111 --- modules/async_worker.py | 25 +++++++++++++++++++------ modules/util.py | 18 ++++++++++++++++++ 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/modules/async_worker.py b/modules/async_worker.py index 98eaad4b..eb5e98f6 100644 --- a/modules/async_worker.py +++ b/modules/async_worker.py @@ -14,6 +14,7 @@ async_tasks = [] def worker(): global async_tasks + import os import traceback import math import json @@ -42,7 +43,7 @@ def worker(): from modules.private_logger import log from extras.expansion import safe_str from modules.util import remove_empty_str, HWC3, resize_image, \ - get_image_shape_ceil, set_image_shape_ceil, get_shape_ceil, resample_image, erode_or_dilate + get_image_shape_ceil, set_image_shape_ceil, get_shape_ceil, resample_image, erode_or_dilate, calculate_sha256, quote from modules.upscaler import perform_upscale try: @@ -201,6 +202,17 @@ def worker(): modules.patch.adm_scaler_end = advanced_parameters.adm_scaler_end = 0.0 steps = 8 + base_model_path = os.path.join(modules.config.path_checkpoints, base_model_name) + base_model_hash = calculate_sha256(base_model_path)[0:10] + + lora_hashes = [] + for (n, w) in loras: + if n != 'None': + lora_path = os.path.join(modules.config.path_loras, n) + lora_hashes.append(f'{n.split('.')[0]}: {calculate_sha256(lora_path)[0:10]}') + lora_hashes_string = ", ".join(lora_hashes) + print(lora_hashes_string) + modules.patch.adaptive_cfg = advanced_parameters.adaptive_cfg print(f'[Parameters] Adaptive CFG = {modules.patch.adaptive_cfg}') @@ -854,16 +866,17 @@ def worker(): "CFG scale": cfg_scale, "Seed": task['task_seed'], "Size": f"{width}x{height}", - #"Model hash": p.sd_model_hash if opts.add_model_hash_to_info else None, - "Model": base_model_name, + "Model hash": base_model_hash, + "Model": base_model_name.split('.')[0], + "Lora hashes": lora_hashes_string, "Denoising strength": denoising_strength, "Version": f'Fooocus v{fooocus_version.version}', - "User": 'mashb1t', + "User": 'mashb1t' } - generation_params_text = ", ".join([k if k == v else f'{k}: {v}' for k, v in generation_params.items() if v is not None]) + generation_params_text = ", ".join([k if k == v else f'{k}: {quote(v)}' for k, v in generation_params.items() if v is not None]) negative_prompt_text = f"\nNegative prompt: {raw_negative_prompt}" if raw_negative_prompt else "" - metadata_string = f"{raw_prompt}{raw_negative_prompt}\n{generation_params_text}".strip() + metadata_string = f"{raw_prompt}{negative_prompt_text}\n{generation_params_text}".strip() for x in imgs: d = [ diff --git a/modules/util.py b/modules/util.py index 052b746b..89270138 100644 --- a/modules/util.py +++ b/modules/util.py @@ -4,8 +4,10 @@ import random import math import os import cv2 +import json from PIL import Image +from hashlib import sha256 LANCZOS = (Image.Resampling.LANCZOS if hasattr(Image, 'Resampling') else Image.LANCZOS) @@ -175,3 +177,19 @@ def get_files_from_folder(folder_path, exensions=None, name_filter=None): filenames.append(path) return sorted(filenames, key=lambda x: -1 if os.sep in x else 1) + +def calculate_sha256(filename): + hash_sha256 = sha256() + blksize = 1024 * 1024 + + with open(filename, "rb") as f: + for chunk in iter(lambda: f.read(blksize), b""): + hash_sha256.update(chunk) + + return hash_sha256.hexdigest() + +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) \ No newline at end of file From 1a52367f3f983d91fb1e7ffe8f9cb1d91205e088 Mon Sep 17 00:00:00 2001 From: Manuel Schmid Date: Mon, 15 Jan 2024 22:17:51 +0100 Subject: [PATCH 06/12] feat: use resolved prompts with included expansion and styles for a1111 metadata --- modules/async_worker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/async_worker.py b/modules/async_worker.py index eb5e98f6..c6213883 100644 --- a/modules/async_worker.py +++ b/modules/async_worker.py @@ -875,8 +875,8 @@ def worker(): } generation_params_text = ", ".join([k if k == v else f'{k}: {quote(v)}' for k, v in generation_params.items() if v is not None]) - negative_prompt_text = f"\nNegative prompt: {raw_negative_prompt}" if raw_negative_prompt else "" - metadata_string = f"{raw_prompt}{negative_prompt_text}\n{generation_params_text}".strip() + negative_prompt_text = f"\nNegative prompt: {task['negative']}" if raw_negative_prompt else "" + metadata_string = f"{task['positive']}{negative_prompt_text}\n{generation_params_text}".strip() for x in imgs: d = [ From 66623819314e9ce127271cb022f0a3d5d6b8d4a6 Mon Sep 17 00:00:00 2001 From: Manuel Schmid Date: Mon, 15 Jan 2024 23:00:59 +0100 Subject: [PATCH 07/12] fix: code cleanup and resolved prompt fixes --- modules/async_worker.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/modules/async_worker.py b/modules/async_worker.py index c6213883..762657f4 100644 --- a/modules/async_worker.py +++ b/modules/async_worker.py @@ -209,7 +209,7 @@ def worker(): for (n, w) in loras: if n != 'None': lora_path = os.path.join(modules.config.path_loras, n) - lora_hashes.append(f'{n.split('.')[0]}: {calculate_sha256(lora_path)[0:10]}') + lora_hashes.append(f'{n.split(".")[0]}: {calculate_sha256(lora_path)[0:10]}') lora_hashes_string = ", ".join(lora_hashes) print(lora_hashes_string) @@ -817,13 +817,11 @@ def worker(): if 'vary' in goals: metadata |= { 'uov_method': uov_method - #'uov_input_image': raw_uov_input_image } if 'upscale' in goals: metadata |= { - 'uov_method': uov_method, 'scale': f, - #'uov_input_image': uov_input_image + 'uov_method': uov_method, 'scale': f } if 'inpaint' in goals: @@ -836,7 +834,6 @@ def worker(): 'inpaint_additional_prompt': inpaint_additional_prompt, 'inpaint_mask_upload': advanced_parameters.inpaint_mask_upload_checkbox, 'invert_mask': advanced_parameters.invert_mask_checkbox, 'inpaint_disable_initial_latent': advanced_parameters.inpaint_disable_initial_latent, 'inpaint_engine': advanced_parameters.inpaint_engine, 'inpaint_strength': advanced_parameters.inpaint_strength, 'inpaint_respective_field': advanced_parameters.inpaint_respective_field, - #'inpaint_image': inpaint_image, 'inpaint_mask': inpaint_mask } if 'cn' in goals: @@ -852,12 +849,13 @@ def worker(): metadata |= { f'image_prompt_{cn_task_index}': { 'cn_type': cn_type, 'cn_stop': cn_stop, 'cn_weight': cn_weight, - #'cn_image': cn_img } } cn_task_index += 1 - metadata |= {'software': f'Fooocus v{fooocus_version.version}'} + metadata |= { + 'software': f'Fooocus v{fooocus_version.version}', + } metadata_string = json.dumps(metadata, ensure_ascii=False) elif save_metadata_to_images and metadata_schema == 'a1111': generation_params = { @@ -870,13 +868,14 @@ def worker(): "Model": base_model_name.split('.')[0], "Lora hashes": lora_hashes_string, "Denoising strength": denoising_strength, - "Version": f'Fooocus v{fooocus_version.version}', - "User": 'mashb1t' + "Version": f'Fooocus v{fooocus_version.version}' } generation_params_text = ", ".join([k if k == v else f'{k}: {quote(v)}' for k, v in generation_params.items() if v is not None]) - negative_prompt_text = f"\nNegative prompt: {task['negative']}" if raw_negative_prompt else "" - metadata_string = f"{task['positive']}{negative_prompt_text}\n{generation_params_text}".strip() + positive_prompt_resolved = ', '.join(task['positive']) + negative_prompt_resolved = ', '.join(task['negative']) + negative_prompt_text = f"\nNegative prompt: {negative_prompt_resolved}" if negative_prompt_resolved else "" + metadata_string = f"{positive_prompt_resolved}{negative_prompt_text}\n{generation_params_text}".strip() for x in imgs: d = [ From 7b9deb17eef55021b809fdc3da54bd98dd66c7ed Mon Sep 17 00:00:00 2001 From: Manuel Schmid Date: Mon, 15 Jan 2024 23:04:02 +0100 Subject: [PATCH 08/12] feat: add config metadata_created_by --- modules/async_worker.py | 9 +++++++++ modules/config.py | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/modules/async_worker.py b/modules/async_worker.py index 762657f4..5836176c 100644 --- a/modules/async_worker.py +++ b/modules/async_worker.py @@ -856,6 +856,10 @@ def worker(): metadata |= { 'software': f'Fooocus v{fooocus_version.version}', } + if modules.config.metadata_created_by != 'None': + metadata |= { + 'created_by': modules.config.metadata_created_by + } metadata_string = json.dumps(metadata, ensure_ascii=False) elif save_metadata_to_images and metadata_schema == 'a1111': generation_params = { @@ -871,6 +875,11 @@ def worker(): "Version": f'Fooocus v{fooocus_version.version}' } + if modules.config.metadata_created_by != 'None': + generation_params |= { + 'Created By': quote(modules.config.metadata_created_by) + } + generation_params_text = ", ".join([k if k == v else f'{k}: {quote(v)}' for k, v in generation_params.items() if v is not None]) positive_prompt_resolved = ', '.join(task['positive']) negative_prompt_resolved = ', '.join(task['negative']) diff --git a/modules/config.py b/modules/config.py index 28104859..ca1f8b4a 100644 --- a/modules/config.py +++ b/modules/config.py @@ -325,6 +325,11 @@ default_metadata_schema = get_config_item_or_set_default( default_value='fooocus', validator=lambda x: x in [y[1] for y in modules.flags.metadata_schema if y[1] == x] ) +metadata_created_by = get_config_item_or_set_default( + key='metadata_created_by', + default_value='', + validator=lambda x: isinstance(x, str) +) example_inpaint_prompts = [[x] for x in example_inpaint_prompts] From cd65f21d98566152940b0852feee4d21f6db4d52 Mon Sep 17 00:00:00 2001 From: Manuel Schmid Date: Mon, 15 Jan 2024 23:14:49 +0100 Subject: [PATCH 09/12] fix: use stting isntead of quote wrap for A1111 created_by --- modules/async_worker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/async_worker.py b/modules/async_worker.py index 5836176c..f4e8a2a6 100644 --- a/modules/async_worker.py +++ b/modules/async_worker.py @@ -877,7 +877,7 @@ def worker(): if modules.config.metadata_created_by != 'None': generation_params |= { - 'Created By': quote(modules.config.metadata_created_by) + 'Created By': f'{modules.config.metadata_created_by}' } generation_params_text = ", ".join([k if k == v else f'{k}: {quote(v)}' for k, v in generation_params.items() if v is not None]) From a2153dba7d8d55a2edb863704e534a356dfa46eb Mon Sep 17 00:00:00 2001 From: Manuel Schmid Date: Mon, 15 Jan 2024 23:20:43 +0100 Subject: [PATCH 10/12] fix: correctlyy hide/show metadata schema on app start --- webui.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/webui.py b/webui.py index 1a88a87b..f010fae7 100644 --- a/webui.py +++ b/webui.py @@ -386,9 +386,10 @@ with shared.gradio_root: save_metadata_to_images = gr.Checkbox(label='Save Metadata to Images', value=modules.config.default_save_metadata_to_images, info='Adds parameters to generated images allowing manual regeneration.') metadata_schema = gr.Radio(label='Metadata Schema', choices=flags.metadata_schema, value=modules.config.default_metadata_schema, - info='Use A1111 for compatibility with Civitai.') + info='Use A1111 for compatibility with Civitai.', + visible=modules.config.default_save_metadata_to_images) - save_metadata_to_images.change(lambda x: gr.update(visible=not x), inputs=[save_metadata_to_images], outputs=[metadata_schema], + save_metadata_to_images.change(lambda x: gr.update(visible=x), inputs=[save_metadata_to_images], outputs=[metadata_schema], queue=False, show_progress=False) with gr.Tab(label='Control'): From 80ad0d070e14d94cf99273a04577d6bc36a88fd3 Mon Sep 17 00:00:00 2001 From: Manuel Schmid Date: Mon, 15 Jan 2024 23:52:57 +0100 Subject: [PATCH 11/12] fix: do not generate hashes when arg --disable-metadata is used --- modules/async_worker.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/async_worker.py b/modules/async_worker.py index f4e8a2a6..5ed5f2f7 100644 --- a/modules/async_worker.py +++ b/modules/async_worker.py @@ -202,16 +202,16 @@ def worker(): modules.patch.adm_scaler_end = advanced_parameters.adm_scaler_end = 0.0 steps = 8 - base_model_path = os.path.join(modules.config.path_checkpoints, base_model_name) - base_model_hash = calculate_sha256(base_model_path)[0:10] + if not args_manager.args.disable_metadata: + base_model_path = os.path.join(modules.config.path_checkpoints, base_model_name) + base_model_hash = calculate_sha256(base_model_path)[0:10] - lora_hashes = [] - for (n, w) in loras: - if n != 'None': - lora_path = os.path.join(modules.config.path_loras, n) - lora_hashes.append(f'{n.split(".")[0]}: {calculate_sha256(lora_path)[0:10]}') - lora_hashes_string = ", ".join(lora_hashes) - print(lora_hashes_string) + lora_hashes = [] + for (n, w) in loras: + if n != 'None': + lora_path = os.path.join(modules.config.path_loras, n) + lora_hashes.append(f'{n.split(".")[0]}: {calculate_sha256(lora_path)[0:10]}') + lora_hashes_string = ", ".join(lora_hashes) modules.patch.adaptive_cfg = advanced_parameters.adaptive_cfg print(f'[Parameters] Adaptive CFG = {modules.patch.adaptive_cfg}') From ba5d0b6eafddf3272f9b6fa0c37d8cf91c4300f6 Mon Sep 17 00:00:00 2001 From: Manuel Schmid Date: Mon, 15 Jan 2024 23:59:17 +0100 Subject: [PATCH 12/12] refactor: rename metadata_schema to metadata_scheme --- modules/async_worker.py | 6 +++--- modules/config.py | 6 +++--- modules/flags.py | 2 +- webui.py | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/async_worker.py b/modules/async_worker.py index 5ed5f2f7..4ce29384 100644 --- a/modules/async_worker.py +++ b/modules/async_worker.py @@ -144,7 +144,7 @@ def worker(): inpaint_additional_prompt = args.pop() inpaint_mask_image_upload = args.pop() save_metadata_to_images = args.pop() if not args_manager.args.disable_metadata else False - metadata_schema = args.pop() if not args_manager.args.disable_metadata else 'fooocus' + metadata_scheme = args.pop() if not args_manager.args.disable_metadata else 'fooocus' cn_tasks = {x: [] for x in flags.ip_list} for _ in range(4): @@ -794,7 +794,7 @@ def worker(): imgs = [inpaint_worker.current_task.post_process(x) for x in imgs] metadata_string = '' - if save_metadata_to_images and metadata_schema == 'fooocus': + if save_metadata_to_images and metadata_scheme == 'fooocus': metadata = { 'prompt': raw_prompt, 'negative_prompt': raw_negative_prompt, 'styles': str(raw_style_selections), 'real_prompt': task['log_positive_prompt'], 'real_negative_prompt': task['log_negative_prompt'], @@ -861,7 +861,7 @@ def worker(): 'created_by': modules.config.metadata_created_by } metadata_string = json.dumps(metadata, ensure_ascii=False) - elif save_metadata_to_images and metadata_schema == 'a1111': + elif save_metadata_to_images and metadata_scheme == 'a1111': generation_params = { "Steps": steps, "Sampler": sampler_name, diff --git a/modules/config.py b/modules/config.py index ca1f8b4a..924284c5 100644 --- a/modules/config.py +++ b/modules/config.py @@ -320,10 +320,10 @@ default_save_metadata_to_images = get_config_item_or_set_default( default_value=False, validator=lambda x: isinstance(x, bool) ) -default_metadata_schema = get_config_item_or_set_default( - key='default_metadata_schema', +default_metadata_scheme = get_config_item_or_set_default( + key='default_metadata_scheme', default_value='fooocus', - validator=lambda x: x in [y[1] for y in modules.flags.metadata_schema if y[1] == x] + validator=lambda x: x in [y[1] for y in modules.flags.metadata_scheme if y[1] == x] ) metadata_created_by = get_config_item_or_set_default( key='metadata_created_by', diff --git a/modules/flags.py b/modules/flags.py index 873736df..fd346f2e 100644 --- a/modules/flags.py +++ b/modules/flags.py @@ -32,7 +32,7 @@ default_parameters = { cn_ip: (0.5, 0.6), cn_ip_face: (0.9, 0.75), cn_canny: (0.5, 1.0), cn_cpds: (0.5, 1.0) } # stop, weight -metadata_schema =[ +metadata_scheme =[ ('Fooocus (json)', 'fooocus'), ('A1111 (plain text)', 'a1111'), ] diff --git a/webui.py b/webui.py index f010fae7..ca0c837c 100644 --- a/webui.py +++ b/webui.py @@ -385,11 +385,11 @@ with shared.gradio_root: if not args_manager.args.disable_metadata: save_metadata_to_images = gr.Checkbox(label='Save Metadata to Images', value=modules.config.default_save_metadata_to_images, info='Adds parameters to generated images allowing manual regeneration.') - metadata_schema = gr.Radio(label='Metadata Schema', choices=flags.metadata_schema, value=modules.config.default_metadata_schema, + metadata_scheme = gr.Radio(label='Metadata Scheme', choices=flags.metadata_scheme, value=modules.config.default_metadata_scheme, info='Use A1111 for compatibility with Civitai.', visible=modules.config.default_save_metadata_to_images) - save_metadata_to_images.change(lambda x: gr.update(visible=x), inputs=[save_metadata_to_images], outputs=[metadata_schema], + save_metadata_to_images.change(lambda x: gr.update(visible=x), inputs=[save_metadata_to_images], outputs=[metadata_scheme], queue=False, show_progress=False) with gr.Tab(label='Control'): @@ -540,7 +540,7 @@ with shared.gradio_root: ctrls += [outpaint_selections, inpaint_input_image, inpaint_additional_prompt, inpaint_mask_image] if not args_manager.args.disable_metadata: - ctrls += [save_metadata_to_images, metadata_schema] + ctrls += [save_metadata_to_images, metadata_scheme] ctrls += ip_ctrls