ddoc commited on
Commit
2246586
·
1 Parent(s): 1cfd2e0

Upload process_png_metadata.py

Browse files
Files changed (1) hide show
  1. process_png_metadata.py +237 -0
process_png_metadata.py ADDED
@@ -0,0 +1,237 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import re
3
+ from PIL import Image
4
+ import pathlib
5
+
6
+ import modules.scripts as scripts
7
+ from modules import processing
8
+ from modules import images
9
+ from modules.processing import process_images, Processed
10
+ from modules.shared import state
11
+ import modules.shared as shared
12
+ from modules.shared import opts
13
+ from modules.generation_parameters_copypaste import parse_generation_parameters
14
+ from modules.extras import run_pnginfo
15
+
16
+ # github repository -> https://github.com/thundaga/SD-webui-txt2img-script
17
+
18
+ def int_convert(text: str) -> int:
19
+ return int(text)
20
+
21
+ def float_convert(text: str) -> float:
22
+ return float(text)
23
+
24
+ def boolean_convert(text: str) -> bool:
25
+ return True if (text == "true") else False
26
+
27
+ def hires_resize(p, parsed_text: dict):
28
+ # Reset hr_settings to avoid wrong settings
29
+ p.hr_scale = None
30
+ p.hr_resize_x = int(0)
31
+ p.hr_resize_y= int(0)
32
+ if 'Hires upscale' in parsed_text:
33
+ p.hr_scale = float(parsed_text['Hires upscale'])
34
+ if 'Hires resize-1' in parsed_text:
35
+ p.hr_resize_x = int(parsed_text['Hires resize-1'])
36
+ if 'Hires resize-2' in parsed_text:
37
+ p.hr_resize_y = int(parsed_text['Hires resize-2'])
38
+ return p
39
+
40
+ def override_settings(p, options: list, parsed_text: dict):
41
+ if "Checkpoint" in options and 'Model hash' in parsed_text:
42
+ p.override_settings['sd_model_checkpoint'] = parsed_text['Model hash']
43
+ if "Clip Skip" in options and 'Clip skip' in parsed_text:
44
+ p.override_settings['CLIP_stop_at_last_layers'] = int(parsed_text['Clip skip'])
45
+ return p
46
+
47
+ def width_height(p, parsed_text: dict):
48
+ if 'Size-1' in parsed_text:
49
+ p.width = int(parsed_text['Size-1'])
50
+ if 'Size-2' in parsed_text:
51
+ p.height = int(parsed_text['Size-2'])
52
+ return p
53
+
54
+ def prompt_modifications(parsed_text: dict, front_tags: str, back_tags: str, remove_tags: str) -> str:
55
+ prompt = parsed_text['Prompt']
56
+
57
+ if remove_tags:
58
+ remove_tags = remove_tags.strip("\n")
59
+ tags = [x.strip() for x in remove_tags.split(',')]
60
+ while("" in tags):
61
+ tags.remove("")
62
+ text = prompt
63
+
64
+ for tag in tags:
65
+ text = re.sub("\(\(" + tag + "\)\)|\(" + tag + ":.*?\)|<" + tag + ":.*?>|<" + tag + ">", "", text)
66
+ text = re.sub(r'\([^\(]*(%s)\S*\)' % tag, '', text)
67
+ text = re.sub(r'\[[^\[]*(%s)\S*\]' % tag, '', text)
68
+ text = re.sub(r'<[^<]*(%s)\S*>' % tag, '', text)
69
+ text = re.sub(r'\b' + tag + r'\b', '', text)
70
+
71
+ # remove consecutive comma patterns with a coma and space
72
+ pattern = re.compile(r'(,\s){2,}')
73
+ text = re.sub(pattern, ', ', text)
74
+
75
+ # remove final comma at start of prompt
76
+ text = text.replace(", ", "", 1)
77
+ prompt = text
78
+
79
+ if front_tags:
80
+ if front_tags.endswith(' ') == False and front_tags.endswith(',') == False:
81
+ front_tags = front_tags + ','
82
+ prompt = ''.join([front_tags, prompt])
83
+
84
+ if back_tags:
85
+ if back_tags.startswith(' ') == False and back_tags.startswith(',') == False:
86
+ back_tags = ',' + back_tags
87
+ prompt = ''.join([prompt, back_tags])
88
+ return prompt
89
+
90
+ # build valid txt and image files e.g (txt(utf-8),img(png)) into valid parsed dictionaries with metadata info
91
+ def build_file_list(file, tab_index: int, file_list: list[dict]) -> list[dict]:
92
+
93
+ file = file.name if tab_index == 0 else file
94
+ file_ext = pathlib.Path(file).suffix
95
+
96
+ if file_ext == ".txt":
97
+ text = open(file, "r", encoding="utf-8").read()
98
+ if text != None and text != "":
99
+ parsed_text = parse_generation_parameters(text)
100
+ file_list.append(parsed_text)
101
+ elif run_pnginfo(Image.open(file))[1] != None:
102
+ text = run_pnginfo(Image.open(file))[1]
103
+ parsed_text = parse_generation_parameters(text)
104
+ file_list.append(parsed_text)
105
+
106
+ return file_list
107
+
108
+ # key->(option name) : Values->tuple(metadata name, object property, property specific functions)
109
+ prompt_options = {
110
+ "Checkpoint": ("Model hash", None, override_settings),
111
+ "Prompt": ("Prompt", "prompt", prompt_modifications),
112
+ "Negative Prompt": ("Negative prompt", "negative_prompt", None),
113
+ "Seed": ("Seed", "seed", float_convert),
114
+ "Variation Seed": ("Variation seed", "subseed", float_convert),
115
+ "Variation Seed Strength": ("Variation seed strength", "subseed_strength", float_convert),
116
+ "Sampler": ("Sampler", "sampler_name", None),
117
+ "Steps": ("Steps", "steps", int_convert),
118
+ "CFG scale": ("CFG scale", "cfg_scale", float_convert),
119
+ "Width and Height": (None, None, width_height),
120
+ "Upscaler": ("Hires upscaler", "hr_upscaler", None),
121
+ "Denoising Strength": ("Denoising strength", "denoising_strength", float_convert),
122
+ "Hires Scale or Width and Height": (None, None, hires_resize),
123
+ "Clip Skip": ("Clip skip", None, override_settings),
124
+ "Face restoration": ("Face restoration", "restore_faces", boolean_convert),
125
+ }
126
+
127
+ class Script(scripts.Script):
128
+
129
+ def title(self):
130
+
131
+ return "Process PNG Metadata Info"
132
+
133
+ def show(self, is_img2img):
134
+
135
+ return not is_img2img
136
+
137
+ # set up ui to drag and drop the processed images and hold their file info
138
+ def ui(self, is_img2img):
139
+
140
+ tab_index = gr.State(value=0)
141
+
142
+ with gr.Row().style(equal_height=False, variant='compact'):
143
+ with gr.Column(variant='compact'):
144
+ with gr.Tabs(elem_id="mode_extras"):
145
+ with gr.TabItem('Batch Process', elem_id="extras_batch_process_tab") as tab_batch:
146
+ upload_files = gr.File(label="Batch Process", file_count="multiple", interactive=True, type="file", elem_id=self.elem_id("files"))
147
+
148
+ with gr.TabItem('Batch from Directory', elem_id="extras_batch_directory_tab") as tab_batch_dir:
149
+ input_dir = gr.Textbox(label="Input directory", **shared.hide_dirs, placeholder="Add input folder path", elem_id="files_batch_input_dir")
150
+ output_dir = gr.Textbox(label="Output directory", **shared.hide_dirs, placeholder="Add output folder path or Leave blank to use default path.", elem_id="files_batch_output_dir")
151
+
152
+ # CheckboxGroup with all parameters assignable from the input image (output is a list with the Name of the Checkbox checked ex: ["Checkpoint", "Prompt"])
153
+ options = gr.CheckboxGroup(list(prompt_options.keys()), label="Assign from input image", info="Checked : Assigned from the input images\nUnchecked : Assigned from the UI")
154
+
155
+ gr.HTML("<p style=\"margin-bottom:0.75em\">Optional tags to remove or add in front/end of a positive prompt on all images</p>")
156
+ front_tags = gr.Textbox(label="Tags to add at the front")
157
+ back_tags = gr.Textbox(label="Tags to add at the end")
158
+ remove_tags = gr.Textbox(label="Tags to remove")
159
+
160
+ tab_batch.select(fn=lambda: 0, inputs=[], outputs=[tab_index])
161
+ tab_batch_dir.select(fn=lambda: 1, inputs=[], outputs=[tab_index])
162
+
163
+ return [tab_index,upload_files,front_tags,back_tags,remove_tags,input_dir,output_dir,options]
164
+
165
+ # Files are open as images and the png info is set to the processed class for each iterated process
166
+ def run(self,p,tab_index,upload_files,front_tags,back_tags,remove_tags,input_dir,output_dir,options):
167
+
168
+ image_batch = []
169
+
170
+ # Operation based on current batch process tab
171
+ if tab_index == 0:
172
+ for file in upload_files:
173
+ image_batch = build_file_list(file, tab_index, image_batch)
174
+ elif tab_index == 1:
175
+ assert not shared.cmd_opts.hide_ui_dir_config, '--hide-ui-dir-config option must be disabled'
176
+ assert input_dir, 'input directory not selected'
177
+
178
+ files_dir = shared.listfiles(input_dir)
179
+ for file in files_dir:
180
+ image_batch = build_file_list(file, tab_index, image_batch)
181
+
182
+ if tab_index == 1 and output_dir != '':
183
+ p.do_not_save_samples = True
184
+
185
+ image_count = len(image_batch)
186
+ state.job_count = image_count
187
+
188
+ images_list = []
189
+ all_prompts = []
190
+ infotexts = []
191
+
192
+ for parsed_text in image_batch:
193
+ state.job = f"{state.job_no + 1} out of {state.job_count}"
194
+
195
+ metadata, p_property, func = 0, 1, 2
196
+ # go through dictionary and commit uniform actions on similar object properties
197
+ for option, tuple in prompt_options.items():
198
+ match option:
199
+ case "Prompt":
200
+ if option in options and tuple[metadata] in parsed_text:
201
+ setattr(p, tuple[p_property], tuple[func](parsed_text,front_tags,back_tags,remove_tags))
202
+ case "Width and Height":
203
+ if option in options:
204
+ p = tuple[func](p, parsed_text)
205
+ case "Hires Scale or Width and Height":
206
+ if option in options:
207
+ p = tuple[func](p, parsed_text)
208
+ case "Checkpoint" | "Clip Skip":
209
+ p = tuple[func](p, options, parsed_text)
210
+ case _:
211
+ if option in options and tuple[metadata] in parsed_text:
212
+ if tuple[func] == None:
213
+ setattr(p, tuple[p_property], parsed_text[tuple[metadata]])
214
+ else:
215
+ setattr(p, tuple[p_property], tuple[func](parsed_text[tuple[metadata]]))
216
+
217
+ proc = process_images(p)
218
+
219
+ # Reset Hires prompts (else the prompts of the first image will be used as Hires prompt for all the others)
220
+ p.hr_prompt = ""
221
+ p.hr_negative_prompt = ""
222
+
223
+ # Reset extra_generation_params as it stores the Hires resize and scale (Avoid having wrong info in the infotext)
224
+ p.extra_generation_params = {}
225
+
226
+ # Modified directory to save generated images in cache
227
+ if tab_index == 1 and output_dir != '':
228
+ for n, processed_image in enumerate(proc.images):
229
+ images.save_image(image=processed_image, path=output_dir, basename='', existing_info=processed_image.info)
230
+
231
+ images_list += proc.images
232
+ all_prompts += proc.all_prompts
233
+ infotexts += proc.infotexts
234
+
235
+ processing.fix_seed(p)
236
+
237
+ return Processed(p, images_list, p.seed, "", all_prompts=all_prompts, infotexts=infotexts)