2024-01-20 20:19:53 +00:00
import csv
2024-01-01 16:31:06 +00:00
import dataclasses
2023-01-23 06:24:43 +00:00
import json
import html
import os
import platform
import sys
import gradio as gr
2023-01-23 12:50:32 +00:00
import subprocess as sp
2023-01-23 06:24:43 +00:00
2024-02-17 15:38:05 +00:00
from modules import call_queue , shared , ui_tempdir
2024-01-01 14:25:30 +00:00
from modules . infotext_utils import image_from_url_text
2023-01-23 06:24:43 +00:00
import modules . images
2023-05-31 19:40:09 +00:00
from modules . ui_components import ToolButton
2024-01-01 14:25:30 +00:00
import modules . infotext_utils as parameters_copypaste
2023-01-23 06:24:43 +00:00
folder_symbol = ' \U0001f4c2 ' # 📂
2023-05-31 19:40:09 +00:00
refresh_symbol = ' \U0001f504 ' # 🔄
2023-01-23 06:24:43 +00:00
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 ( )
2023-07-15 05:07:25 +00:00
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> "
2023-01-23 06:24:43 +00:00
2024-01-16 18:03:48 +00:00
def update_logfile ( logfile_path , fields ) :
2024-01-20 21:04:53 +00:00
""" Update a logfile from old format to new format to maintain CSV integrity. """
2024-01-16 18:03:48 +00:00
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
2024-01-20 20:19:53 +00:00
# file is already synced, do nothing
if len ( rows [ 0 ] ) == len ( fields ) :
return
2024-01-16 18:03:48 +00:00
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 )
2023-01-23 06:24:43 +00:00
def save_files ( js_data , images , do_make_zip , index ) :
filenames = [ ]
fullfns = [ ]
2024-01-13 16:58:01 +00:00
parsed_infotexts = [ ]
2023-01-23 06:24:43 +00:00
2024-01-13 16:58:01 +00:00
# quick dictionary to class object conversion. Its necessary due apply_filename_pattern requiring it
2023-01-23 06:24:43 +00:00
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 )
2024-01-13 16:58:01 +00:00
2023-01-23 06:24:43 +00:00
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 )
2024-01-16 18:03:48 +00:00
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
2024-03-04 06:37:23 +00:00
# updating headers and padding with delimiters where needed
2024-01-16 18:03:48 +00:00
if os . path . exists ( logfile_path ) :
update_logfile ( logfile_path , fields )
2024-01-16 18:11:28 +00:00
with open ( logfile_path , " a " , encoding = " utf8 " , newline = ' ' ) as file :
2023-01-23 06:24:43 +00:00
at_start = file . tell ( ) == 0
writer = csv . writer ( file )
if at_start :
2024-01-16 18:03:48 +00:00
writer . writerow ( fields )
2023-01-23 06:24:43 +00:00
for image_index , filedata in enumerate ( images , start_index ) :
image = image_from_url_text ( filedata )
is_grid = image_index < p . index_of_first_image
2023-05-24 15:17:02 +00:00
p . batch_index = image_index - 1
2024-01-13 16:58:01 +00:00
2024-01-16 11:21:58 +00:00
parameters = parameters_copypaste . parse_generation_parameters ( data [ " infotexts " ] [ image_index ] , [ ] )
2024-01-13 16:58:01 +00:00
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 )
2023-01-23 06:24:43 +00:00
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 )
2024-01-20 20:27:38 +00:00
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 " ] ] )
2023-01-23 06:24:43 +00:00
# Make Zip
if do_make_zip :
2024-01-13 16:58:01 +00:00
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 )
2023-05-24 15:17:02 +00:00
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 " )
2023-01-23 06:24:43 +00:00
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 ] } " )
2024-01-01 16:31:06 +00:00
@dataclasses.dataclass
class OutputPanel :
gallery = None
2024-01-04 16:47:00 +00:00
generation_info = None
2024-01-01 16:31:06 +00:00
infotext = None
html_log = None
button_upscale = None
2023-11-05 16:19:55 +00:00
def create_output_panel ( tabname , outdir , toprow = None ) :
2024-01-01 16:31:06 +00:00
res = OutputPanel ( )
2023-01-23 06:24:43 +00:00
2024-02-17 15:38:05 +00:00
def open_folder ( f , images = None , index = None ) :
if shared . cmd_opts . hide_ui_dir_config :
return
try :
2024-02-17 17:47:44 +00:00
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 ) :
2024-02-17 15:38:05 +00:00
f = image_dir
except Exception :
pass
2023-01-23 06:24:43 +00:00
if not os . path . exists ( f ) :
2024-02-17 15:38:05 +00:00
msg = f ' Folder " { f } " does not exist. After you create an image, the folder will be created. '
print ( msg )
gr . Info ( msg )
2023-01-23 06:24:43 +00:00
return
elif not os . path . isdir ( f ) :
2024-02-17 15:38:05 +00:00
msg = f """
2023-01-23 06:24:43 +00:00
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 }
2024-02-17 15:38:05 +00:00
"""
print ( msg , file = sys . stderr )
gr . Warning ( msg )
2023-01-23 06:24:43 +00:00
return
2024-02-17 15:38:05 +00:00
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 ] )
2023-01-23 06:24:43 +00:00
2023-11-05 16:19:55 +00:00
with gr . Column ( elem_id = f " { tabname } _results " ) :
if toprow :
toprow . create_inline_toprow_image ( )
2023-01-23 06:24:43 +00:00
2023-11-05 16:19:55 +00:00
with gr . Column ( variant = ' panel ' , elem_id = f " { tabname } _results_panel " ) :
with gr . Group ( elem_id = f " { tabname } _gallery_container " ) :
2024-01-01 16:31:06 +00:00
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 )
2023-11-05 16:19:55 +00:00
2023-03-21 05:18:14 +00:00
with gr . Row ( elem_id = f " image_buttons_ { tabname } " , elem_classes = " image-buttons " ) :
2023-08-12 16:03:33 +00:00
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. " )
2023-01-23 06:24:43 +00:00
if tabname != " extras " :
2023-08-12 16:03:33 +00:00
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. " )
}
2023-01-23 06:24:43 +00:00
2024-01-01 16:31:06 +00:00
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. " )
2023-01-23 06:24:43 +00:00
open_folder_button . click (
2024-02-17 15:38:05 +00:00
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
] ,
2023-01-23 06:24:43 +00:00
outputs = [ ] ,
)
if tabname != " extras " :
2023-03-27 07:20:01 +00:00
download_files = gr . File ( None , file_count = " multiple " , interactive = False , show_label = False , visible = False , elem_id = f ' download_files_ { tabname } ' )
2023-01-23 06:24:43 +00:00
with gr . Group ( ) :
2024-01-04 16:47:00 +00:00
res . infotext = gr . HTML ( elem_id = f ' html_info_ { tabname } ' , elem_classes = " infotext " )
2024-01-01 16:31:06 +00:00
res . html_log = gr . HTML ( elem_id = f ' html_log_ { tabname } ' , elem_classes = " html-log " )
2023-01-23 06:24:43 +00:00
2024-01-04 16:47:00 +00:00
res . generation_info = gr . Textbox ( visible = False , elem_id = f ' generation_info_ { tabname } ' )
2023-01-23 06:24:43 +00:00
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()] } " ,
2024-01-04 16:47:00 +00:00
inputs = [ res . generation_info , res . infotext , res . infotext ] ,
outputs = [ res . infotext , res . infotext ] ,
2023-03-20 13:09:36 +00:00
show_progress = False ,
2023-01-23 06:24:43 +00:00
)
save . click (
fn = call_queue . wrap_gradio_call ( save_files ) ,
_js = " (x, y, z, w) => [x, y, false, selected_gallery_index()] " ,
inputs = [
2024-01-04 16:47:00 +00:00
res . generation_info ,
2024-01-01 16:31:06 +00:00
res . gallery ,
2024-01-04 16:47:00 +00:00
res . infotext ,
res . infotext ,
2023-01-23 06:24:43 +00:00
] ,
outputs = [
download_files ,
2024-01-01 16:31:06 +00:00
res . html_log ,
2023-01-23 06:24:43 +00:00
] ,
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 = [
2024-01-04 16:47:00 +00:00
res . generation_info ,
2024-01-01 16:31:06 +00:00
res . gallery ,
2024-01-04 16:47:00 +00:00
res . infotext ,
res . infotext ,
2023-01-23 06:24:43 +00:00
] ,
outputs = [
download_files ,
2024-01-01 16:31:06 +00:00
res . html_log ,
2023-01-23 06:24:43 +00:00
]
)
else :
2024-01-04 16:47:00 +00:00
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 " )
2024-01-01 16:31:06 +00:00
res . html_log = gr . HTML ( elem_id = f ' html_log_ { tabname } ' )
2023-01-23 06:24:43 +00:00
2023-02-14 11:55:42 +00:00
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
2023-01-29 21:25:30 +00:00
for paste_tabname , paste_button in buttons . items ( ) :
parameters_copypaste . register_paste_params_button ( parameters_copypaste . ParamBinding (
2024-01-01 16:31:06 +00:00
paste_button = paste_button , tabname = paste_tabname , source_tabname = " txt2img " if tabname == " txt2img " else None , source_image_component = res . gallery ,
2023-02-14 11:55:42 +00:00
paste_field_names = paste_field_names
2023-01-29 21:25:30 +00:00
) )
2024-01-01 16:31:06 +00:00
return res
2023-05-31 19:40:09 +00:00
def create_refresh_button ( refresh_component , refresh_method , refreshed_args , elem_id ) :
2023-08-03 19:46:57 +00:00
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
2023-05-31 19:40:09 +00:00
def refresh ( ) :
refresh_method ( )
args = refreshed_args ( ) if callable ( refreshed_args ) else refreshed_args
for k , v in args . items ( ) :
2023-08-03 19:46:57 +00:00
for comp in refresh_components :
setattr ( comp , k , v )
2023-05-31 19:40:09 +00:00
2023-08-05 06:17:36 +00:00
return [ gr . update ( * * ( args or { } ) ) for _ in refresh_components ] if len ( refresh_components ) > 1 else gr . update ( * * ( args or { } ) )
2023-05-31 19:40:09 +00:00
2023-08-03 19:46:57 +00:00
refresh_button = ToolButton ( value = refresh_symbol , elem_id = elem_id , tooltip = f " { label } : refresh " if label else " Refresh " )
2023-05-31 19:40:09 +00:00
refresh_button . click (
fn = refresh ,
inputs = [ ] ,
2023-08-04 11:56:27 +00:00
outputs = refresh_components
2023-05-31 19:40:09 +00:00
)
return refresh_button
2023-08-03 19:46:57 +00:00
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 ] ,
2023-08-27 06:39:37 +00:00
) . then ( fn = None , _js = " function() { popupId( ' " + dialog . elem_id + " ' ); } " )
2023-08-03 19:46:57 +00:00
if button_close :
button_close . click ( fn = None , _js = " closePopup " )