mirror of
				https://github.com/AUTOMATIC1111/stable-diffusion-webui.git
				synced 2025-11-04 03:55:05 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			350 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			350 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
import csv
 | 
						|
import dataclasses
 | 
						|
import json
 | 
						|
import html
 | 
						|
import os
 | 
						|
import platform
 | 
						|
import sys
 | 
						|
 | 
						|
import gradio as gr
 | 
						|
import subprocess as sp
 | 
						|
 | 
						|
from modules import call_queue, shared, ui_tempdir
 | 
						|
from modules.infotext_utils import image_from_url_text
 | 
						|
import modules.images
 | 
						|
from modules.ui_components import ToolButton
 | 
						|
import modules.infotext_utils as parameters_copypaste
 | 
						|
 | 
						|
folder_symbol = '\U0001f4c2'  # 📂
 | 
						|
refresh_symbol = '\U0001f504'  # 🔄
 | 
						|
 | 
						|
 | 
						|
def update_generation_info(generation_info, html_info, img_index):
 | 
						|
    try:
 | 
						|
        generation_info = json.loads(generation_info)
 | 
						|
        if img_index < 0 or img_index >= len(generation_info["infotexts"]):
 | 
						|
            return html_info, gr.update()
 | 
						|
        return plaintext_to_html(generation_info["infotexts"][img_index]), gr.update()
 | 
						|
    except Exception:
 | 
						|
        pass
 | 
						|
    # if the json parse or anything else fails, just return the old html_info
 | 
						|
    return html_info, gr.update()
 | 
						|
 | 
						|
 | 
						|
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 update_logfile(logfile_path, fields):
 | 
						|
    """Update a logfile from old format to new format to maintain CSV integrity."""
 | 
						|
    with open(logfile_path, "r", encoding="utf8", newline="") as file:
 | 
						|
        reader = csv.reader(file)
 | 
						|
        rows = list(reader)
 | 
						|
 | 
						|
    # blank file: leave it as is
 | 
						|
    if not rows:
 | 
						|
        return
 | 
						|
 | 
						|
    # file is already synced, do nothing
 | 
						|
    if len(rows[0]) == len(fields):
 | 
						|
        return
 | 
						|
 | 
						|
    rows[0] = fields
 | 
						|
 | 
						|
    # append new fields to each row as empty values
 | 
						|
    for row in rows[1:]:
 | 
						|
        while len(row) < len(fields):
 | 
						|
            row.append("")
 | 
						|
 | 
						|
    with open(logfile_path, "w", encoding="utf8", newline="") as file:
 | 
						|
        writer = csv.writer(file)
 | 
						|
        writer.writerows(rows)
 | 
						|
 | 
						|
 | 
						|
def save_files(js_data, images, do_make_zip, index):
 | 
						|
    filenames = []
 | 
						|
    fullfns = []
 | 
						|
    parsed_infotexts = []
 | 
						|
 | 
						|
    # quick dictionary to class object conversion. Its necessary due apply_filename_pattern requiring it
 | 
						|
    class MyObject:
 | 
						|
        def __init__(self, d=None):
 | 
						|
            if d is not None:
 | 
						|
                for key, value in d.items():
 | 
						|
                    setattr(self, key, value)
 | 
						|
 | 
						|
    data = json.loads(js_data)
 | 
						|
    p = MyObject(data)
 | 
						|
 | 
						|
    path = shared.opts.outdir_save
 | 
						|
    save_to_dirs = shared.opts.use_save_to_dirs_for_ui
 | 
						|
    extension: str = shared.opts.samples_format
 | 
						|
    start_index = 0
 | 
						|
 | 
						|
    if index > -1 and shared.opts.save_selected_only and (index >= data["index_of_first_image"]):  # ensures we are looking at a specific non-grid picture, and we have save_selected_only
 | 
						|
        images = [images[index]]
 | 
						|
        start_index = index
 | 
						|
 | 
						|
    os.makedirs(shared.opts.outdir_save, exist_ok=True)
 | 
						|
 | 
						|
    fields = [
 | 
						|
        "prompt",
 | 
						|
        "seed",
 | 
						|
        "width",
 | 
						|
        "height",
 | 
						|
        "sampler",
 | 
						|
        "cfgs",
 | 
						|
        "steps",
 | 
						|
        "filename",
 | 
						|
        "negative_prompt",
 | 
						|
        "sd_model_name",
 | 
						|
        "sd_model_hash",
 | 
						|
    ]
 | 
						|
    logfile_path = os.path.join(shared.opts.outdir_save, "log.csv")
 | 
						|
 | 
						|
    # NOTE: ensure csv integrity when fields are added by
 | 
						|
    # updating headers and padding with delimiters where needed
 | 
						|
    if os.path.exists(logfile_path):
 | 
						|
        update_logfile(logfile_path, fields)
 | 
						|
 | 
						|
    with open(logfile_path, "a", encoding="utf8", newline='') as file:
 | 
						|
        at_start = file.tell() == 0
 | 
						|
        writer = csv.writer(file)
 | 
						|
        if at_start:
 | 
						|
            writer.writerow(fields)
 | 
						|
 | 
						|
        for image_index, filedata in enumerate(images, start_index):
 | 
						|
            image = image_from_url_text(filedata)
 | 
						|
 | 
						|
            is_grid = image_index < p.index_of_first_image
 | 
						|
 | 
						|
            p.batch_index = image_index-1
 | 
						|
 | 
						|
            parameters = parameters_copypaste.parse_generation_parameters(data["infotexts"][image_index], [])
 | 
						|
            parsed_infotexts.append(parameters)
 | 
						|
            fullfn, txt_fullfn = modules.images.save_image(image, path, "", seed=parameters['Seed'], prompt=parameters['Prompt'], extension=extension, info=p.infotexts[image_index], grid=is_grid, p=p, save_to_dirs=save_to_dirs)
 | 
						|
 | 
						|
            filename = os.path.relpath(fullfn, path)
 | 
						|
            filenames.append(filename)
 | 
						|
            fullfns.append(fullfn)
 | 
						|
            if txt_fullfn:
 | 
						|
                filenames.append(os.path.basename(txt_fullfn))
 | 
						|
                fullfns.append(txt_fullfn)
 | 
						|
 | 
						|
        writer.writerow([parsed_infotexts[0]['Prompt'], parsed_infotexts[0]['Seed'], data["width"], data["height"], data["sampler_name"], data["cfg_scale"], data["steps"], filenames[0], parsed_infotexts[0]['Negative prompt'], data["sd_model_name"], data["sd_model_hash"]])
 | 
						|
 | 
						|
    # Make Zip
 | 
						|
    if do_make_zip:
 | 
						|
        p.all_seeds = [parameters['Seed'] for parameters in parsed_infotexts]
 | 
						|
        namegen = modules.images.FilenameGenerator(p, parsed_infotexts[0]['Seed'], parsed_infotexts[0]['Prompt'], image, True)
 | 
						|
        zip_filename = namegen.apply(shared.opts.grid_zip_filename_pattern or "[datetime]_[[model_name]]_[seed]-[seed_last]")
 | 
						|
        zip_filepath = os.path.join(path, f"{zip_filename}.zip")
 | 
						|
 | 
						|
        from zipfile import ZipFile
 | 
						|
        with ZipFile(zip_filepath, "w") as zip_file:
 | 
						|
            for i in range(len(fullfns)):
 | 
						|
                with open(fullfns[i], mode="rb") as f:
 | 
						|
                    zip_file.writestr(filenames[i], f.read())
 | 
						|
        fullfns.insert(0, zip_filepath)
 | 
						|
 | 
						|
    return gr.File.update(value=fullfns, visible=True), plaintext_to_html(f"Saved: {filenames[0]}")
 | 
						|
 | 
						|
 | 
						|
@dataclasses.dataclass
 | 
						|
class OutputPanel:
 | 
						|
    gallery = None
 | 
						|
    generation_info = None
 | 
						|
    infotext = None
 | 
						|
    html_log = None
 | 
						|
    button_upscale = None
 | 
						|
 | 
						|
 | 
						|
def create_output_panel(tabname, outdir, toprow=None):
 | 
						|
    res = OutputPanel()
 | 
						|
 | 
						|
    def open_folder(f, images=None, index=None):
 | 
						|
        if shared.cmd_opts.hide_ui_dir_config:
 | 
						|
            return
 | 
						|
 | 
						|
        try:
 | 
						|
            if 'Sub' in shared.opts.open_dir_button_choice:
 | 
						|
                image_dir = os.path.split(images[index]["name"].rsplit('?', 1)[0])[0]
 | 
						|
                if 'temp' in shared.opts.open_dir_button_choice or not ui_tempdir.is_gradio_temp_path(image_dir):
 | 
						|
                    f = image_dir
 | 
						|
        except Exception:
 | 
						|
            pass
 | 
						|
 | 
						|
        if not os.path.exists(f):
 | 
						|
            msg = f'Folder "{f}" does not exist. After you create an image, the folder will be created.'
 | 
						|
            print(msg)
 | 
						|
            gr.Info(msg)
 | 
						|
            return
 | 
						|
        elif not os.path.isdir(f):
 | 
						|
            msg = f"""
 | 
						|
WARNING
 | 
						|
An open_folder request was made with an argument that is not a folder.
 | 
						|
This could be an error or a malicious attempt to run code on your computer.
 | 
						|
Requested path was: {f}
 | 
						|
"""
 | 
						|
            print(msg, file=sys.stderr)
 | 
						|
            gr.Warning(msg)
 | 
						|
            return
 | 
						|
 | 
						|
        path = os.path.normpath(f)
 | 
						|
        if platform.system() == "Windows":
 | 
						|
            os.startfile(path)
 | 
						|
        elif platform.system() == "Darwin":
 | 
						|
            sp.Popen(["open", path])
 | 
						|
        elif "microsoft-standard-WSL2" in platform.uname().release:
 | 
						|
            sp.Popen(["wsl-open", path])
 | 
						|
        else:
 | 
						|
            sp.Popen(["xdg-open", path])
 | 
						|
 | 
						|
    with gr.Column(elem_id=f"{tabname}_results"):
 | 
						|
        if toprow:
 | 
						|
            toprow.create_inline_toprow_image()
 | 
						|
 | 
						|
        with gr.Column(variant='panel', elem_id=f"{tabname}_results_panel"):
 | 
						|
            with gr.Group(elem_id=f"{tabname}_gallery_container"):
 | 
						|
                res.gallery = gr.Gallery(label='Output', show_label=False, elem_id=f"{tabname}_gallery", columns=4, preview=True, height=shared.opts.gallery_height or None)
 | 
						|
 | 
						|
            with gr.Row(elem_id=f"image_buttons_{tabname}", elem_classes="image-buttons"):
 | 
						|
                open_folder_button = ToolButton(folder_symbol, elem_id=f'{tabname}_open_folder', visible=not shared.cmd_opts.hide_ui_dir_config, tooltip="Open images output directory.")
 | 
						|
 | 
						|
                if tabname != "extras":
 | 
						|
                    save = ToolButton('💾', elem_id=f'save_{tabname}', tooltip=f"Save the image to a dedicated directory ({shared.opts.outdir_save}).")
 | 
						|
                    save_zip = ToolButton('🗃️', elem_id=f'save_zip_{tabname}', tooltip=f"Save zip archive with images to a dedicated directory ({shared.opts.outdir_save})")
 | 
						|
 | 
						|
                buttons = {
 | 
						|
                    'img2img': ToolButton('🖼️', elem_id=f'{tabname}_send_to_img2img', tooltip="Send image and generation parameters to img2img tab."),
 | 
						|
                    'inpaint': ToolButton('🎨️', elem_id=f'{tabname}_send_to_inpaint', tooltip="Send image and generation parameters to img2img inpaint tab."),
 | 
						|
                    'extras': ToolButton('📐', elem_id=f'{tabname}_send_to_extras', tooltip="Send image and generation parameters to extras tab.")
 | 
						|
                }
 | 
						|
 | 
						|
                if tabname == 'txt2img':
 | 
						|
                    res.button_upscale = ToolButton('✨', elem_id=f'{tabname}_upscale', tooltip="Create an upscaled version of the current image using hires fix settings.")
 | 
						|
 | 
						|
            open_folder_button.click(
 | 
						|
                fn=lambda images, index: open_folder(shared.opts.outdir_samples or outdir, images, index),
 | 
						|
                _js="(y, w) => [y, selected_gallery_index()]",
 | 
						|
                inputs=[
 | 
						|
                    res.gallery,
 | 
						|
                    open_folder_button,  # placeholder for index
 | 
						|
                ],
 | 
						|
                outputs=[],
 | 
						|
            )
 | 
						|
 | 
						|
            if tabname != "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():
 | 
						|
                    res.infotext = gr.HTML(elem_id=f'html_info_{tabname}', elem_classes="infotext")
 | 
						|
                    res.html_log = gr.HTML(elem_id=f'html_log_{tabname}', elem_classes="html-log")
 | 
						|
 | 
						|
                    res.generation_info = gr.Textbox(visible=False, elem_id=f'generation_info_{tabname}')
 | 
						|
                    if tabname == 'txt2img' or tabname == 'img2img':
 | 
						|
                        generation_info_button = gr.Button(visible=False, elem_id=f"{tabname}_generation_info_button")
 | 
						|
                        generation_info_button.click(
 | 
						|
                            fn=update_generation_info,
 | 
						|
                            _js="function(x, y, z){ return [x, y, selected_gallery_index()] }",
 | 
						|
                            inputs=[res.generation_info, res.infotext, res.infotext],
 | 
						|
                            outputs=[res.infotext, res.infotext],
 | 
						|
                            show_progress=False,
 | 
						|
                        )
 | 
						|
 | 
						|
                    save.click(
 | 
						|
                        fn=call_queue.wrap_gradio_call(save_files),
 | 
						|
                        _js="(x, y, z, w) => [x, y, false, selected_gallery_index()]",
 | 
						|
                        inputs=[
 | 
						|
                            res.generation_info,
 | 
						|
                            res.gallery,
 | 
						|
                            res.infotext,
 | 
						|
                            res.infotext,
 | 
						|
                        ],
 | 
						|
                        outputs=[
 | 
						|
                            download_files,
 | 
						|
                            res.html_log,
 | 
						|
                        ],
 | 
						|
                        show_progress=False,
 | 
						|
                    )
 | 
						|
 | 
						|
                    save_zip.click(
 | 
						|
                        fn=call_queue.wrap_gradio_call(save_files),
 | 
						|
                        _js="(x, y, z, w) => [x, y, true, selected_gallery_index()]",
 | 
						|
                        inputs=[
 | 
						|
                            res.generation_info,
 | 
						|
                            res.gallery,
 | 
						|
                            res.infotext,
 | 
						|
                            res.infotext,
 | 
						|
                        ],
 | 
						|
                        outputs=[
 | 
						|
                            download_files,
 | 
						|
                            res.html_log,
 | 
						|
                        ]
 | 
						|
                    )
 | 
						|
 | 
						|
            else:
 | 
						|
                res.generation_info = gr.HTML(elem_id=f'html_info_x_{tabname}')
 | 
						|
                res.infotext = gr.HTML(elem_id=f'html_info_{tabname}', elem_classes="infotext")
 | 
						|
                res.html_log = gr.HTML(elem_id=f'html_log_{tabname}')
 | 
						|
 | 
						|
            paste_field_names = []
 | 
						|
            if tabname == "txt2img":
 | 
						|
                paste_field_names = modules.scripts.scripts_txt2img.paste_field_names
 | 
						|
            elif tabname == "img2img":
 | 
						|
                paste_field_names = modules.scripts.scripts_img2img.paste_field_names
 | 
						|
 | 
						|
            for paste_tabname, paste_button in buttons.items():
 | 
						|
                parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding(
 | 
						|
                    paste_button=paste_button, tabname=paste_tabname, source_tabname="txt2img" if tabname == "txt2img" else None, source_image_component=res.gallery,
 | 
						|
                    paste_field_names=paste_field_names
 | 
						|
                ))
 | 
						|
 | 
						|
    return res
 | 
						|
 | 
						|
 | 
						|
def create_refresh_button(refresh_component, refresh_method, refreshed_args, elem_id):
 | 
						|
    refresh_components = refresh_component if isinstance(refresh_component, list) else [refresh_component]
 | 
						|
 | 
						|
    label = None
 | 
						|
    for comp in refresh_components:
 | 
						|
        label = getattr(comp, 'label', None)
 | 
						|
        if label is not None:
 | 
						|
            break
 | 
						|
 | 
						|
    def refresh():
 | 
						|
        refresh_method()
 | 
						|
        args = refreshed_args() if callable(refreshed_args) else refreshed_args
 | 
						|
 | 
						|
        for k, v in args.items():
 | 
						|
            for comp in refresh_components:
 | 
						|
                setattr(comp, k, v)
 | 
						|
 | 
						|
        return [gr.update(**(args or {})) for _ in refresh_components] if len(refresh_components) > 1 else gr.update(**(args or {}))
 | 
						|
 | 
						|
    refresh_button = ToolButton(value=refresh_symbol, elem_id=elem_id, tooltip=f"{label}: refresh" if label else "Refresh")
 | 
						|
    refresh_button.click(
 | 
						|
        fn=refresh,
 | 
						|
        inputs=[],
 | 
						|
        outputs=refresh_components
 | 
						|
    )
 | 
						|
    return refresh_button
 | 
						|
 | 
						|
 | 
						|
def setup_dialog(button_show, dialog, *, button_close=None):
 | 
						|
    """Sets up the UI so that the dialog (gr.Box) is invisible, and is only shown when buttons_show is clicked, in a fullscreen modal window."""
 | 
						|
 | 
						|
    dialog.visible = False
 | 
						|
 | 
						|
    button_show.click(
 | 
						|
        fn=lambda: gr.update(visible=True),
 | 
						|
        inputs=[],
 | 
						|
        outputs=[dialog],
 | 
						|
    ).then(fn=None, _js="function(){ popupId('" + dialog.elem_id + "'); }")
 | 
						|
 | 
						|
    if button_close:
 | 
						|
        button_close.click(fn=None, _js="closePopup")
 | 
						|
 |