mirror of
https://github.com/vladmandic/automatic
synced 2026-09-19 17:24:32 +02:00
add image gallery
This commit is contained in:
+10
-4
@@ -32,10 +32,16 @@ def parse_generation_parameters(infotext):
|
||||
|
||||
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
|
||||
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))
|
||||
if first_param_idx > 0:
|
||||
for _i in range(first_param_idx):
|
||||
params.pop(next(iter(params)))
|
||||
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:")
|
||||
|
||||
|
||||
+7
-69
@@ -2,12 +2,13 @@
|
||||
|
||||
let ws;
|
||||
let url;
|
||||
let currentImage;
|
||||
const el = {
|
||||
folders: undefined,
|
||||
files: undefined,
|
||||
image: undefined,
|
||||
search: undefined,
|
||||
status: undefined,
|
||||
btnSend: undefined,
|
||||
};
|
||||
|
||||
// HTML Elements
|
||||
@@ -39,69 +40,6 @@ class GalleryFolder extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
class GalleryImage extends HTMLElement {
|
||||
constructor(folder, name, size, mtime) {
|
||||
super();
|
||||
this.folder = folder;
|
||||
this.name = name;
|
||||
this.size = size;
|
||||
this.mtime = mtime;
|
||||
this.shadow = this.attachShadow({ mode: 'open' });
|
||||
}
|
||||
|
||||
async connectedCallback() {
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
.gallery-image {
|
||||
text-align: center;
|
||||
}
|
||||
.gallery-image > img {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
max-width: 100%;
|
||||
max-height: 60vh;
|
||||
}
|
||||
.gallery-image-text {
|
||||
text-align: left;
|
||||
padding: 8px;
|
||||
line-height: 1.3em;
|
||||
}
|
||||
`;
|
||||
this.shadow.appendChild(style);
|
||||
const div = document.createElement('div');
|
||||
div.className = 'gallery-image';
|
||||
|
||||
const text = document.createElement('div');
|
||||
text.className = 'gallery-image-text';
|
||||
text.innerHTML = `
|
||||
<b>Folder:</b> ${this.folder}<br>
|
||||
<b>File:</b> ${this.name}<br>
|
||||
<b>Resolution:</b> <span id="gallery-resolution"></span><br>
|
||||
<b>Size:</b> ${this.size.toLocaleString()} bytes<br>
|
||||
<b>Modified:</b> ${this.mtime.toLocaleString()}<br>
|
||||
<br>
|
||||
<span id="gallery-exif"></span>
|
||||
`;
|
||||
|
||||
const img = document.createElement('img');
|
||||
img.id = 'gallery-image';
|
||||
img.onload = async () => {
|
||||
const resolutionEl = this.shadow.getElementById('gallery-resolution');
|
||||
if (resolutionEl) resolutionEl.innerText = `${img.naturalWidth} x ${img.naturalHeight}`;
|
||||
const exifData = await getExif(img);
|
||||
const exifEl = this.shadow.getElementById('gallery-exif');
|
||||
if (exifEl) exifEl.innerHTML = exifData;
|
||||
};
|
||||
img.loading = 'lazy';
|
||||
img.src = `file=${this.folder}/${this.name}`;
|
||||
img.title = `Folder: ${this.folder}\nFile: ${this.name}\nResolution: ${img.naturalWidth} x ${img.naturalHeight}\nSize: ${this.size.toLocaleString()} bytes\nModified: ${this.mtime.toLocaleString()}`;
|
||||
img.addEventListener('click', galleryClickEventHandler, true);
|
||||
div.appendChild(img);
|
||||
div.appendChild(text);
|
||||
this.shadow.appendChild(div);
|
||||
}
|
||||
}
|
||||
|
||||
class GalleryFile extends HTMLElement {
|
||||
constructor({ folder, file, size, mtime }) {
|
||||
super();
|
||||
@@ -149,9 +87,8 @@ class GalleryFile extends HTMLElement {
|
||||
};
|
||||
img.src = `file=${this.folder}/${this.name}`;
|
||||
img.onclick = () => {
|
||||
el.image.innerHTML = '';
|
||||
const image = new GalleryImage(this.folder, this.name, this.size, this.mtime);
|
||||
el.image.appendChild(image);
|
||||
currentImage = `${this.folder}/${this.name}`;
|
||||
el.btnSend.click();
|
||||
};
|
||||
this.title = img.title;
|
||||
this.style.display = this.title.toLowerCase().includes(el.search.value.toLowerCase()) ? 'unset' : 'none';
|
||||
@@ -161,6 +98,8 @@ class GalleryFile extends HTMLElement {
|
||||
|
||||
// methods
|
||||
|
||||
const gallerySendImage = (_images) => [currentImage]; // invoked by gadio button
|
||||
|
||||
async function getHash(str, algo = 'SHA-256') {
|
||||
const strBuf = new TextEncoder().encode(str);
|
||||
const hash = await crypto.subtle.digest(algo, strBuf);
|
||||
@@ -330,10 +269,10 @@ async function galleryObserve() { // triggered on gradio change to monitor when
|
||||
log('initBrowser');
|
||||
el.folders = gradioApp().getElementById('tab-gallery-folders');
|
||||
el.files = gradioApp().getElementById('tab-gallery-files');
|
||||
el.image = gradioApp().getElementById('tab-gallery-image');
|
||||
el.status = gradioApp().getElementById('tab-gallery-status');
|
||||
el.search = gradioApp().querySelector('#tab-gallery-search textarea');
|
||||
el.search.addEventListener('input', gallerySearch);
|
||||
el.btnSend = gradioApp().getElementById('tab-gallery-send-image');
|
||||
|
||||
const intersectionObserver = new IntersectionObserver((entries) => {
|
||||
if (entries[0].intersectionRatio <= 0) galleryHidden();
|
||||
@@ -346,5 +285,4 @@ async function galleryObserve() { // triggered on gradio change to monitor when
|
||||
|
||||
customElements.define('gallery-folder', GalleryFolder);
|
||||
customElements.define('gallery-file', GalleryFile);
|
||||
customElements.define('gallery-image', GalleryImage);
|
||||
onUiLoaded(galleryObserve);
|
||||
|
||||
@@ -37,7 +37,7 @@ async function logMonitor() {
|
||||
if (logMonitorEl && lines?.length > 0) logMonitorEl.parentElement.parentElement.style.display = opts.logmonitor_show ? 'block' : 'none';
|
||||
for (const line of lines) {
|
||||
try {
|
||||
const l = JSON.parse(line);
|
||||
const l = JSON.parse(line.replaceAll('\n', ' '));
|
||||
const row = document.createElement('tr');
|
||||
// row.style = 'padding: 10px; margin: 0;';
|
||||
const level = `<td style="color: var(--color-${l.level.toLowerCase()})">${l.level}</td>`;
|
||||
|
||||
@@ -298,7 +298,12 @@ table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-widt
|
||||
#tab-gallery-sortby { padding: 0; }
|
||||
#tab-gallery-status { align-content: center; text-align: right; background: var(--input-background-fill); padding-right: 1em; margin-left: -0.6em; color: var(--block-title-text-color); }
|
||||
div:has(>#tab-gallery-folders) { flex-grow: 0 !important; background-color: var(--input-background-fill); min-width: max-content !important; }
|
||||
.gallery-separator { background-color: var(--input-background-fill); font-size: larger; padding: 0.5em; display: block !important; }
|
||||
.gallery-separator { background-color: var(--input-background-fill); font-size: larger; padding: 0.5em; display: block !important; }
|
||||
#html_log_gallery { font-size: 0.95em; }
|
||||
#gallery_gallery { height: 60vh; }
|
||||
#gallery_gallery .thumbnails { display: none; }
|
||||
#gallery_gallery .preview { display: grid; background: none; }
|
||||
#gallery_gallery img { object-fit: contain; }
|
||||
|
||||
/* loader */
|
||||
.splash { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 1000; display: block; text-align: center; }
|
||||
|
||||
+2
-1
@@ -437,7 +437,8 @@ function currentImageResolutioncontrol(_a, _b, scaleBy) {
|
||||
}
|
||||
|
||||
function updateImg2imgResizeToTextAfterChangingImage() {
|
||||
setTimeout(() => gradioApp().getElementById('img2img_update_resize_to').click(), 500);
|
||||
const el = gradioApp().getElementById('img2img_update_resize_to');
|
||||
if (el) setTimeout(() => gradioApp().getElementById('img2img_update_resize_to').click(), 500);
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
@@ -159,13 +159,14 @@ def connect_paste_params_buttons():
|
||||
)
|
||||
if binding.source_text_component is not None and fields is not None:
|
||||
connect_paste(binding.paste_button, fields, binding.source_text_component, override_settings_component, binding.tabname)
|
||||
if binding.source_tabname is not None and fields is not None:
|
||||
if binding.source_tabname is not None and fields is not None and binding.source_tabname in paste_fields:
|
||||
paste_field_names = ['Prompt', 'Negative prompt', 'Steps', 'Face restoration'] + (["Seed"] if shared.opts.send_seed else []) + binding.paste_field_names
|
||||
binding.paste_button.click(
|
||||
fn=lambda *x: x,
|
||||
inputs=[field for field, name in paste_fields[binding.source_tabname]["fields"] if name in paste_field_names],
|
||||
outputs=[field for field, name in fields if name in paste_field_names],
|
||||
)
|
||||
if "fields" in paste_fields[binding.source_tabname] and paste_fields[binding.source_tabname]["fields"] is not None:
|
||||
binding.paste_button.click(
|
||||
fn=lambda *x: x,
|
||||
inputs=[field for field, name in paste_fields[binding.source_tabname]["fields"] if name in paste_field_names],
|
||||
outputs=[field for field, name in fields if name in paste_field_names],
|
||||
)
|
||||
binding.paste_button.click(
|
||||
fn=None,
|
||||
_js=f"switch_to_{binding.tabname}",
|
||||
@@ -192,7 +193,7 @@ def parse_generation_parameters(infotext):
|
||||
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
|
||||
basic_params = ['steps:', 'seed:', 'width:', 'height:', 'sampler:', 'size:', 'cfg scale:'] # 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)
|
||||
@@ -202,10 +203,16 @@ def parse_generation_parameters(infotext):
|
||||
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
|
||||
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))
|
||||
if first_param_idx > 0:
|
||||
for _i in range(first_param_idx):
|
||||
params.pop(next(iter(params)))
|
||||
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:")
|
||||
|
||||
|
||||
+13
-6
@@ -246,7 +246,7 @@ def create_output_panel(tabname, preview=True, prompt=None, height=None):
|
||||
if shared.backend == shared.Backend.ORIGINAL:
|
||||
buttons = generation_parameters_copypaste.create_buttons(["img2img", "inpaint", "extras"])
|
||||
else:
|
||||
buttons = generation_parameters_copypaste.create_buttons(["img2img", "inpaint", "control", "extras"])
|
||||
buttons = generation_parameters_copypaste.create_buttons(["txt2img", "img2img", "control", "extras"])
|
||||
|
||||
download_files = gr.File(None, file_count="multiple", interactive=False, show_label=False, visible=False, elem_id=f'download_files_{tabname}')
|
||||
with gr.Group():
|
||||
@@ -257,15 +257,18 @@ def create_output_panel(tabname, preview=True, prompt=None, height=None):
|
||||
generation_info = gr.Textbox(visible=False, elem_id=f'generation_info_{tabname}')
|
||||
generation_info_button = gr.Button(visible=False, elem_id=f"{tabname}_generation_info_button")
|
||||
|
||||
generation_info_button.click(fn=update_generation_info, _js="(x, y, z) => [x, y, selected_gallery_index()]", show_progress=False, # triggered on gallery change from js
|
||||
generation_info_button.click(fn=update_generation_info, show_progress=False,
|
||||
_js="(x, y, z) => [x, y, selected_gallery_index()]", # triggered on gallery change from js
|
||||
inputs=[generation_info, html_info, html_info],
|
||||
outputs=[html_info, html_info_formatted],
|
||||
)
|
||||
save.click(fn=call_queue.wrap_gradio_call(save_files), _js="(x, y, z, i) => [x, y, z, selected_gallery_index()]", show_progress=False,
|
||||
save.click(fn=call_queue.wrap_gradio_call(save_files), show_progress=False,
|
||||
_js="(x, y, z, i) => [x, y, z, selected_gallery_index()]",
|
||||
inputs=[generation_info, result_gallery, html_info, html_info],
|
||||
outputs=[download_files, html_log],
|
||||
)
|
||||
delete.click(fn=call_queue.wrap_gradio_call(delete_files), _js="(x, y, z, i) => [x, y, z, selected_gallery_index()]",
|
||||
delete.click(fn=call_queue.wrap_gradio_call(delete_files),show_progress=False,
|
||||
_js="(x, y, z, i) => [x, y, z, selected_gallery_index()]",
|
||||
inputs=[generation_info, result_gallery, html_info, html_info],
|
||||
outputs=[result_gallery, html_log],
|
||||
)
|
||||
@@ -278,9 +281,13 @@ def create_output_panel(tabname, preview=True, prompt=None, height=None):
|
||||
paste_field_names = scripts.scripts_control.paste_field_names
|
||||
else:
|
||||
paste_field_names = []
|
||||
debug(f'Paste field: tab={tabname} fields={paste_field_names}')
|
||||
for paste_tabname, paste_button in buttons.items():
|
||||
debug(f'Create output panel: button={paste_button} tabname={paste_tabname}')
|
||||
bindings = generation_parameters_copypaste.ParamBinding(paste_button=paste_button, tabname=paste_tabname, source_tabname=("txt2img" if tabname == "txt2img" else None), source_image_component=result_gallery, paste_field_names=paste_field_names)
|
||||
debug(f'Create output panel: source={tabname} target={paste_tabname} button={paste_button}')
|
||||
bindings = generation_parameters_copypaste.ParamBinding(paste_button=paste_button, tabname=paste_tabname, source_tabname=tabname, source_image_component=result_gallery, paste_field_names=paste_field_names, source_text_component=generation_info)
|
||||
|
||||
# txt2img_bindings = generation_parameters_copypaste.ParamBinding(paste_button=txt2img_paste, tabname="txt2img", source_text_component=txt2img_prompt, source_image_component=None)
|
||||
|
||||
generation_parameters_copypaste.register_paste_params_button(bindings)
|
||||
return result_gallery, generation_info, html_info, html_info_formatted, html_log
|
||||
|
||||
|
||||
+21
-3
@@ -1,10 +1,26 @@
|
||||
import os
|
||||
from datetime import datetime
|
||||
import gradio as gr
|
||||
from modules import shared, ui_symbols
|
||||
from PIL import Image
|
||||
from modules import ui_symbols, ui_common, images
|
||||
from modules.ui_components import ToolButton
|
||||
|
||||
|
||||
debug = shared.log.debug if os.environ.get('SD_GALLERY_DEBUG', None) is not None else lambda *args, **kwargs: None
|
||||
def read_image(fn):
|
||||
if not os.path.isfile(fn):
|
||||
return [[], '', f'Image not found: {fn}']
|
||||
stat = os.stat(fn)
|
||||
image = Image.open(fn)
|
||||
image.already_saved_as = fn
|
||||
geninfo, _items = images.read_info_from_image(image)
|
||||
log = f'''
|
||||
<p>Image <b>{image.width} x {image.height}</b>
|
||||
| Format <b>{image.format}</b>
|
||||
| Mode <b>{image.mode}</b>
|
||||
| Size <b>{stat.st_size:,}</b>
|
||||
| Modified <b>{datetime.fromtimestamp(stat.st_mtime)}</b></p><br>
|
||||
'''
|
||||
return [[image], geninfo, geninfo, log]
|
||||
|
||||
|
||||
def create_ui():
|
||||
@@ -29,5 +45,7 @@ def create_ui():
|
||||
with gr.Column():
|
||||
gr.HTML('', elem_id='tab-gallery-files')
|
||||
with gr.Column():
|
||||
gr.HTML('', elem_id='tab-gallery-image')
|
||||
btn_gallery_image = gr.Button('', elem_id='tab-gallery-send-image', visible=False, interactive=True)
|
||||
gallery_images, gen_info, html_info, _html_info_formatted, html_log = ui_common.create_output_panel("gallery")
|
||||
btn_gallery_image.click(fn=read_image, _js='gallerySendImage', inputs=[html_info], outputs=[gallery_images, html_info, gen_info, html_log])
|
||||
return [(tab, 'Gallery', 'tab-gallery')]
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import json
|
||||
import gradio as gr
|
||||
from modules import scripts, shared, ui_common, postprocessing, call_queue, interrogate
|
||||
import modules.generation_parameters_copypaste as parameters_copypaste
|
||||
from modules import scripts, shared, ui_common, postprocessing, call_queue, interrogate, generation_parameters_copypaste
|
||||
from modules.call_queue import wrap_gradio_gpu_call, wrap_queued_call, wrap_gradio_call # pylint: disable=unused-import
|
||||
from modules.extras import run_pnginfo
|
||||
from modules.ui_common import infotext_to_html
|
||||
|
||||
|
||||
def submit_info(image):
|
||||
from modules.extras import run_pnginfo
|
||||
from modules.ui_common import infotext_to_html
|
||||
_, geninfo, info = run_pnginfo(image)
|
||||
return infotext_to_html(geninfo), info, geninfo
|
||||
|
||||
@@ -26,7 +25,7 @@ def create_ui():
|
||||
with gr.Row():
|
||||
extras_image = gr.Image(label="Source", source="upload", interactive=True, type="pil", elem_id="extras_image")
|
||||
with gr.Row(elem_id='copy_buttons_process'):
|
||||
copy_process_buttons = parameters_copypaste.create_buttons(["txt2img", "img2img", "inpaint", "control"])
|
||||
copy_process_buttons = generation_parameters_copypaste.create_buttons(["txt2img", "img2img", "inpaint", "control"])
|
||||
with gr.Tab('Process Batch', id="batch_process", elem_id="extras_batch_process_tab") as tab_batch:
|
||||
image_batch = gr.Files(label="Batch process", interactive=True, elem_id="extras_image_batch")
|
||||
with gr.Tab('Process Folder', id="batch_from_directory", elem_id="extras_batch_directory_tab") as tab_batch_dir:
|
||||
@@ -54,7 +53,7 @@ def create_ui():
|
||||
btn_analyze_img = gr.Button("Analyze", elem_id="interrogate_btn_analyze", variant='primary')
|
||||
btn_unload = gr.Button("Unload", elem_id="interrogate_btn_unload")
|
||||
with gr.Row(elem_id='copy_buttons_interrogate'):
|
||||
copy_interrogate_buttons = parameters_copypaste.create_buttons(["txt2img", "img2img", "extras", "control"])
|
||||
copy_interrogate_buttons = generation_parameters_copypaste.create_buttons(["txt2img", "img2img", "extras", "control"])
|
||||
btn_interrogate_img.click(interrogate.interrogate_image, inputs=[image, clip_model, mode], outputs=prompt)
|
||||
btn_analyze_img.click(interrogate.analyze_image, inputs=[image, clip_model], outputs=[medium, artist, movement, trending, flavor])
|
||||
btn_unload.click(interrogate.unload_clip_model)
|
||||
@@ -102,9 +101,9 @@ def create_ui():
|
||||
exif_info = gr.HTML(elem_id="pnginfo_html_info")
|
||||
gen_info = gr.Text(elem_id="pnginfo_gen_info", visible=False)
|
||||
for tabname, button in copy_process_buttons.items():
|
||||
parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding(paste_button=button, tabname=tabname, source_text_component=gen_info, source_image_component=extras_image))
|
||||
generation_parameters_copypaste.register_paste_params_button(generation_parameters_copypaste.ParamBinding(paste_button=button, tabname=tabname, source_text_component=gen_info, source_image_component=extras_image))
|
||||
for tabname, button in copy_interrogate_buttons.items():
|
||||
parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding(paste_button=button, tabname=tabname, source_text_component=prompt, source_image_component=image,))
|
||||
generation_parameters_copypaste.register_paste_params_button(generation_parameters_copypaste.ParamBinding(paste_button=button, tabname=tabname, source_text_component=prompt, source_image_component=image,))
|
||||
|
||||
|
||||
tab_single.select(fn=lambda: 0, inputs=[], outputs=[tab_index])
|
||||
@@ -141,7 +140,7 @@ def create_ui():
|
||||
outputs=[batch],
|
||||
)
|
||||
|
||||
parameters_copypaste.add_paste_fields("extras", extras_image, None)
|
||||
generation_parameters_copypaste.add_paste_fields("extras", extras_image, None)
|
||||
|
||||
extras_image.change(
|
||||
fn=scripts.scripts_postproc.image_changed,
|
||||
|
||||
Reference in New Issue
Block a user