diff --git a/html/locale_en.json b/html/locale_en.json
index de35d33ec..5a046245f 100644
--- a/html/locale_en.json
+++ b/html/locale_en.json
@@ -237,7 +237,7 @@
{"id":"","label":"latent nothing","localized":"","hint":"fill it with latent space zeroes"},
{"id":"","label":"Whole picture","localized":"","hint":""},
{"id":"","label":"Only masked","localized":"","hint":""},
- {"id":"","label":"Only masked padding, pixels","localized":"","hint":""},
+ {"id":"","label":"Masked padding","localized":"","hint":""},
{"id":"","label":"Scale","localized":"","hint":""},
{"id":"","label":"Unused","localized":"","hint":""},
{"id":"","label":"Image CFG scale","localized":"","hint":""}
diff --git a/javascript/amethyst-nightfall.css b/javascript/amethyst-nightfall.css
index a8119ed02..6cf91ab63 100644
--- a/javascript/amethyst-nightfall.css
+++ b/javascript/amethyst-nightfall.css
@@ -73,7 +73,6 @@ svg.feather.feather-image, .feather .feather-image { display: none }
.block.token-counter span { background-color: #222 !important; box-shadow: 2px 2px 2px #111; border: none !important; font-size: 0.8rem; }
.tab-nav { zoom: 120%; margin-bottom: 10px; border-bottom: 2px solid var(--highlight-color) !important; padding-bottom: 2px; }
.label-wrap { margin: 16px 0px 8px 0px; }
-.gradio-slider input[type="number"] { width: 4.5em; font-size: 0.8rem; height: 20px; }
.gradio-button.tool { border: none; background: none; box-shadow: none; }
#tab_extensions table td, #tab_extensions table th { border: none; padding: 0.5em; }
#tab_extensions table { width: 96vw }
diff --git a/javascript/black-orange.css b/javascript/black-orange.css
index 91eda0540..30e069777 100644
--- a/javascript/black-orange.css
+++ b/javascript/black-orange.css
@@ -78,7 +78,6 @@ svg.feather.feather-image, .feather .feather-image { display: none }
.block.token-counter span { background-color: #222 !important; box-shadow: 2px 2px 2px #111; border: none !important; font-size: 0.8rem; }
.tab-nav { zoom: 120%; margin-bottom: 10px; border-bottom: 2px solid var(--highlight-color) !important; padding-bottom: 2px; }
.label-wrap { margin: 16px 0px 8px 0px; }
-.gradio-slider input[type="number"] { width: 4.5em; font-size: 0.8rem; height: 20px; }
.gradio-button.tool { border: none; background: none; box-shadow: none; }
#tab_extensions table td, #tab_extensions table th { border: none; padding: 0.5em; }
#tab_extensions table { width: 96vw }
diff --git a/javascript/black-teal.css b/javascript/black-teal.css
index 87b9d6e4e..bef9280ac 100644
--- a/javascript/black-teal.css
+++ b/javascript/black-teal.css
@@ -83,7 +83,6 @@ svg.feather.feather-image, .feather .feather-image { display: none }
.block.token-counter span { background-color: var(--input-background-fill) !important; box-shadow: 2px 2px 2px #111; border: none !important; font-size: 0.8rem; }
.tab-nav { zoom: 120%; margin-top: 10px; margin-bottom: 10px; border-bottom: 2px solid var(--highlight-color) !important; padding-bottom: 2px; }
.label-wrap { margin: 8px 0px 4px 0px; }
-.gradio-slider input[type="number"] { width: 4.5em; font-size: 0.8rem; height: 20px; }
.gradio-button.tool { border: none; background: none; box-shadow: none; filter: hue-rotate(340deg) saturate(0.5); }
#tab_extensions table td, #tab_extensions table th, #tab_config table td, #tab_config table th { border: none; padding: 0.5em; }
#tab_extensions table, #tab_config table { width: 96vw }
diff --git a/javascript/invoked.css b/javascript/invoked.css
index eb8960264..6d4150d02 100644
--- a/javascript/invoked.css
+++ b/javascript/invoked.css
@@ -77,7 +77,6 @@ div.tab-nav button.selected {background-color: var(--button-primary-background-f
.label-wrap { background-color: #363c4a; padding: 16px 8px 8px 8px; border-radius: var(--radius-lg); padding-left: 8px !important; }
.small-accordion .label-wrap { padding: 8px 0px 8px 0px; }
.small-accordion .label-wrap .icon { margin-right: 1em; }
-.gradio-slider input[type="number"] { width: 4.5em; font-size: 0.8rem; height: 20px; }
.gradio-button.tool { border: none; box-shadow: none; border-radius: var(--radius-lg);}
button.selected {background: var(--button-primary-background-fill);}
.center.boundedheight.flex {background-color: var(--input-background-fill);}
diff --git a/javascript/light-teal.css b/javascript/light-teal.css
index 0b908c059..7778caded 100644
--- a/javascript/light-teal.css
+++ b/javascript/light-teal.css
@@ -84,7 +84,6 @@ svg.feather.feather-image, .feather .feather-image { display: none }
.block.token-counter span { background-color: var(--input-background-fill) !important; box-shadow: 2px 2px 2px #111; border: none !important; font-size: 0.8rem; }
.tab-nav { zoom: 120%; margin-top: 10px; margin-bottom: 10px; border-bottom: 2px solid var(--highlight-color) !important; padding-bottom: 2px; }
.label-wrap { margin: 16px 0px 8px 0px; }
-.gradio-slider input[type="number"] { width: 4.5em; font-size: 0.8rem; height: 20px; }
.gradio-button.tool { border: none; background: none; box-shadow: none; filter: hue-rotate(340deg) saturate(0.5); }
#tab_extensions table td, #tab_extensions table th { border: none; padding: 0.5em; }
#tab_extensions table { width: 96vw }
diff --git a/javascript/midnight-barbie.css b/javascript/midnight-barbie.css
index 67f616f1e..238dac9ff 100644
--- a/javascript/midnight-barbie.css
+++ b/javascript/midnight-barbie.css
@@ -78,7 +78,6 @@ svg.feather.feather-image, .feather .feather-image { display: none }
.block.token-counter span { background-color: #222 !important; box-shadow: 2px 2px 2px #111; border: none !important; font-size: 0.8rem; }
.tab-nav { zoom: 120%; margin-bottom: 10px; border-bottom: 2px solid var(--highlight-color) !important; padding-bottom: 2px; }
.label-wrap { margin: 16px 0px 8px 0px; }
-.gradio-slider input[type="number"] { width: 4.5em; font-size: 0.8rem; height: 20px; }
.gradio-button.tool { border: none; background: none; box-shadow: none; }
#tab_extensions table td, #tab_extensions table th { border: none; padding: 0.5em; }
#tab_extensions table { width: 96vw }
diff --git a/javascript/sdnext.css b/javascript/sdnext.css
index fb6a198f3..4b0bdbeb1 100644
--- a/javascript/sdnext.css
+++ b/javascript/sdnext.css
@@ -38,10 +38,10 @@ tr { border-bottom: none !important; padding: 0.1em 0.5em !important; }
.gradio-html .min { min-height: 0; }
.gradio-html div.wrap { height: 100%; }
.gradio-number { min-width: unset !important; max-width: 5em !important; }
-.gradio-slider { max-width: 50%; margin-right: var(--spacing-sm) !important; }
-.gradio-slider input[type="number"] { width: 6em; margin-left: 0.5em; }
.gradio-textbox { overflow: visible !important; }
-.gradio-radio { padding: 0 !important; }
+.gradio-radio { padding: 0 !important; width: max-content !important; }
+.gradio-slider { margin-right: var(--spacing-sm) !important; width: max-content !important }
+.gradio-slider input[type="number"] { width: 6em; font-size: 0.8rem; height: 20px; margin-left: 0.5em; }
/* custom gradio elements */
.accordion-compact { padding: 8px 0px 4px 0px !important; }
diff --git a/modules/images.py b/modules/images.py
index effac2ac9..ba653f36f 100644
--- a/modules/images.py
+++ b/modules/images.py
@@ -19,6 +19,7 @@ from PIL import Image, ImageFont, ImageDraw, PngImagePlugin, ExifTags
from modules import sd_samplers, shared, script_callbacks, errors, paths
LANCZOS = (Image.Resampling.LANCZOS if hasattr(Image, 'Resampling') else Image.LANCZOS)
+debug = errors.log.info if os.environ.get('SD_PATH_DEBUG', None) is not None else lambda *args, **kwargs: None
try:
@@ -351,7 +352,7 @@ class FilenameGenerator:
return shorthash
def prompt_full(self):
- return self.sanitize(self.prompt)
+ return self.prompt_sanitize(self.prompt)
def prompt_words(self):
if self.prompt is None:
@@ -361,7 +362,7 @@ class FilenameGenerator:
no_brackets = re_brackets.sub('', no_network)
words = [x for x in re_nonletters.split(no_brackets or "") if len(x) > 0]
prompt = " ".join(words[0:shared.opts.directories_max_prompt_words])
- return self.sanitize(prompt)
+ return self.prompt_sanitize(prompt)
def prompt_no_style(self):
if self.p is None or self.prompt is None:
@@ -372,7 +373,7 @@ class FilenameGenerator:
for part in style.split("{prompt}"):
prompt_no_style = prompt_no_style.replace(part, "").replace(", ,", ",")
prompt_no_style = prompt_no_style.replace(style, "")
- return self.sanitize(prompt_no_style)
+ return self.prompt_sanitize(prompt_no_style)
def datetime(self, *args):
time_datetime = datetime.datetime.now()
@@ -388,22 +389,33 @@ class FilenameGenerator:
formatted_time = time_zone_time.strftime(self.default_time_format)
return formatted_time
+ def prompt_sanitize(self, prompt):
+ invalid_chars = '#<>:\'"\\|?*\n\t\r'
+ sanitized = prompt.translate({ ord(x): '_' for x in invalid_chars }).strip()
+ debug(f'Prompt sanitize: input="{prompt}" output={sanitized}')
+ return sanitized
+
def sanitize(self, filename):
- invalid_chars = '#<>.;"/\\|?*\n\t\r'
- invalid_prefix = ''
- invalid_suffix = '.'
+ invalid_chars = '\'"\\|?*\n\t\r' #
+ invalid_folder = ':'
+ invalid_files = ['CON', 'PRN', 'AUX', 'NUL', 'NULL', 'COM0', 'COM1', 'LPT0', 'LPT1']
+ invalid_prefix = ', '
+ invalid_suffix = '.,_ '
fn, ext = os.path.splitext(filename)
parts = Path(fn).parts
newparts = []
- for part in parts:
+ for i, part in enumerate(parts):
part = part.translate({ ord(x): '_' for x in invalid_chars })
- part = part.lstrip(invalid_prefix)
- part = part.rstrip(invalid_suffix)
+ if i > 0 or (len(part) >= 2 and part[1] != invalid_folder): # skip drive, otherwise remove
+ part = part.translate({ ord(x): '_' for x in invalid_folder })
+ part = part.lstrip(invalid_prefix).rstrip(invalid_suffix)
+ if part in invalid_files: # reserved names
+ [part := part.replace(word, '_') for word in invalid_files] # pylint: disable=expression-not-assigned
newparts.append(part)
fn = Path(*newparts)
- max_length = max(os.statvfs(__file__).f_namemax if hasattr(os, 'statvfs') else 128, 250)
- fn = str(fn)[:max_length-max(4, len(ext))] + ext
- shared.log.debug(f'Filename sanitize: input="{filename}" parts={parts} output={fn}')
+ max_length = os.statvfs(__file__).f_namemax - 32 if hasattr(os, 'statvfs') else 230
+ fn = str(fn)[:max_length-max(4, len(ext))].rstrip(invalid_suffix) + ext
+ debug(f'Filename sanitize: input="{filename}" parts={parts} output="{fn}" ext={ext} max={max_length} len={len(fn)}')
return fn
def apply(self, x):
@@ -479,7 +491,10 @@ def atomically_save_image():
pnginfo_data = PngImagePlugin.PngInfo()
for k, v in params.pnginfo.items():
pnginfo_data.add_text(k, str(v))
- image.save(fn, format=image_format, compress_level=6, pnginfo=pnginfo_data if shared.opts.image_metadata else None)
+ try:
+ image.save(fn, format=image_format, compress_level=6, pnginfo=pnginfo_data if shared.opts.image_metadata else None)
+ except Exception as e:
+ shared.log.warning(f'Image save failed: {fn} {e}')
elif image_format == 'JPEG':
if image.mode == 'RGBA':
shared.log.warning('Saving RGBA image as JPEG: Alpha channel will be lost')
@@ -487,7 +502,10 @@ def atomically_save_image():
elif image.mode == 'I;16':
image = image.point(lambda p: p * 0.0038910505836576).convert("L")
exif_bytes = piexif.dump({ "Exif": { piexif.ExifIFD.UserComment: piexif.helper.UserComment.dump(exifinfo, encoding="unicode") } })
- image.save(fn, format=image_format, optimize=True, quality=shared.opts.jpeg_quality, exif=exif_bytes)
+ try:
+ image.save(fn, format=image_format, optimize=True, quality=shared.opts.jpeg_quality, exif=exif_bytes)
+ except Exception as e:
+ shared.log.warning(f'Image save failed: {fn} {e}')
elif image_format == 'WEBP':
if image.mode == 'I;16':
image = image.point(lambda p: p * 0.0038910505836576).convert("RGB")
@@ -578,10 +596,11 @@ def save_image(image, path, basename = '', seed=None, prompt=None, extension=sha
filename_txt = f"{filename}.txt" if shared.opts.save_txt and len(exifinfo) > 0 else None
save_queue.put((params.image, filename, extension, params, exifinfo, filename_txt)) # actual save is executed in a thread that polls data from queue
save_queue.join()
-
- params.image.already_saved_as = params.filename
+ if not hasattr(params.image, 'already_saved_as'):
+ debug(f'Image marked: "{params.filename}"')
+ params.image.already_saved_as = params.filename
script_callbacks.image_saved_callback(params)
- return filename, filename_txt
+ return params.filename, filename_txt
def safe_decode_string(s: bytes):
diff --git a/modules/ui.py b/modules/ui.py
index 3477a2802..21f816c58 100644
--- a/modules/ui.py
+++ b/modules/ui.py
@@ -725,7 +725,7 @@ def create_ui(startup_timer = None):
with gr.Column():
inpaint_full_res = gr.Radio(label="Inpaint area", choices=["Whole picture", "Only masked"], type="index", value="Whole picture", elem_id="img2img_inpaint_full_res")
with gr.Column():
- inpaint_full_res_padding = gr.Slider(label='Only masked padding, pixels', minimum=0, maximum=256, step=4, value=32, elem_id="img2img_inpaint_full_res_padding")
+ inpaint_full_res_padding = gr.Slider(label='Masked padding', minimum=0, maximum=256, step=4, value=32, elem_id="img2img_inpaint_full_res_padding")
def select_img2img_tab(tab):
return gr.update(visible=tab in [2, 3, 4]), gr.update(visible=tab == 3)
diff --git a/modules/ui_tempdir.py b/modules/ui_tempdir.py
index 1293ad6b6..f245edb4e 100644
--- a/modules/ui_tempdir.py
+++ b/modules/ui_tempdir.py
@@ -4,10 +4,11 @@ from collections import namedtuple
from pathlib import Path
import gradio as gr
from PIL import PngImagePlugin
-from modules import shared
+from modules import shared, errors
Savedfile = namedtuple("Savedfile", ["name"])
+debug = errors.log.info if os.environ.get('SD_PATH_DEBUG', None) is not None else lambda *args, **kwargs: None
def register_tmp_file(gradio, filename):
@@ -45,10 +46,13 @@ def pil_to_temp_file(self, img, dir: str, format="png") -> str: # pylint: disabl
img.save(filename, pnginfo=gr.processing_utils.get_pil_metadata(img))
"""
already_saved_as = getattr(img, 'already_saved_as', None)
- if already_saved_as and os.path.isfile(already_saved_as):
+ exists = os.path.isfile(already_saved_as)
+ debug(f'Image lookup: {already_saved_as} exists={exists}')
+ if already_saved_as and exists:
register_tmp_file(shared.demo, already_saved_as)
file_obj = Savedfile(already_saved_as)
name = file_obj.name
+ debug(f'Image registered: {name}')
return name
if shared.opts.temp_dir != "":
dir = shared.opts.temp_dir
@@ -66,7 +70,6 @@ def pil_to_temp_file(self, img, dir: str, format="png") -> str: # pylint: disabl
# override save to file function so that it also writes PNG info
-# gr.processing_utils.save_pil_to_file = save_pil_to_file # gradio <=3.31.0
gr.components.IOComponent.pil_to_temp_file = pil_to_temp_file # gradio >=3.32.0
def on_tmpdir_changed():
diff --git a/wiki b/wiki
index b9712d07c..21fecf2ec 160000
--- a/wiki
+++ b/wiki
@@ -1 +1 @@
-Subproject commit b9712d07cf931b8bca512ae4f083b88877062c77
+Subproject commit 21fecf2ec5a2efef8bf7b5a2ca7c415fe3a41042