From 58351b1f53e966628d8c3620eceab78ef48dcd68 Mon Sep 17 00:00:00 2001 From: vladmandic Date: Wed, 28 Jan 2026 19:28:49 +0100 Subject: [PATCH] fix metadata save and temp file handler Signed-off-by: vladmandic --- CHANGELOG.md | 2 + modules/generation_parameters_copypaste.py | 3 +- modules/gr_tempdir.py | 3 ++ modules/images_namegen.py | 46 ++++++++++++++-------- modules/paths.py | 5 +++ modules/ui_common.py | 28 +++++++------ wiki | 2 +- 7 files changed, 58 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64a123934..0a1cb9c60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ existing data files are auto-migrated on startup - further work on type consistency and type checking, thanks @awsr - log captured exceptions + - improve temp folder handling and cleanup - add ui placeholders for future agent-scheduler work, thanks @ryanmeador - implement abort system on repeated errors, thanks @awsr currently used by lora and textual-inversion loaders @@ -46,6 +47,7 @@ - ui css fixes for modernui - support lora inside prompt selector - framepack video save + - metadata save for manual saves ## Update for 2026-01-22 diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index 4a53c74e9..63ffc545e 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -78,7 +78,8 @@ def image_from_url_text(filedata): filedata = filedata[len("data:image/jxl;base64,"):] filebytes = base64.decodebytes(filedata.encode('utf-8')) image = Image.open(io.BytesIO(filebytes)) - images.read_info_from_image(image) + image.load() + # images.read_info_from_image(image) return image diff --git a/modules/gr_tempdir.py b/modules/gr_tempdir.py index eacf782f5..110396414 100644 --- a/modules/gr_tempdir.py +++ b/modules/gr_tempdir.py @@ -104,6 +104,9 @@ def on_tmpdir_changed(): def cleanup_tmpdr(): temp_dir = shared.opts.temp_dir if temp_dir == "" or not os.path.isdir(temp_dir): + temp_dir = os.path.join(paths.temp_dir, "gradio") + shared.log.debug(f'Temp folder: path="{temp_dir}"') + if not os.path.isdir(temp_dir): return for root, _dirs, files in os.walk(temp_dir, topdown=False): for name in files: diff --git a/modules/images_namegen.py b/modules/images_namegen.py index efc490d62..bbbe5026b 100644 --- a/modules/images_namegen.py +++ b/modules/images_namegen.py @@ -10,7 +10,8 @@ from pathlib import Path from modules import shared, errors -debug = errors.log.trace if os.environ.get('SD_NAMEGEN_DEBUG', None) is not None else lambda *args, **kwargs: None +debug= os.environ.get('SD_NAMEGEN_DEBUG', None) is not None +debug_log = errors.log.trace if debug else lambda *args, **kwargs: None re_nonletters = re.compile(r'[\s' + string.punctuation + ']+') re_pattern = re.compile(r"(.*?)(?:\[([^\[\]]+)\]|$)") re_pattern_arg = re.compile(r"(.*)<([^>]*)>$") @@ -66,9 +67,9 @@ class FilenameGenerator: def __init__(self, p, seed, prompt, image=None, grid=False, width=None, height=None): if p is None: - debug('Filename generator init skip') + debug_log('Filename generator init skip') else: - debug(f'Filename generator init: seed={seed} prompt="{prompt}"') + debug_log(f'Filename generator init: seed={seed} prompt="{prompt}"') self.p = p if seed is not None and int(seed) > 0: self.seed = seed @@ -163,7 +164,7 @@ class FilenameGenerator: 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}') + debug_log(f'Prompt sanitize: input="{prompt}" output="{sanitized}"') return sanitized def sanitize(self, filename): @@ -200,7 +201,7 @@ class FilenameGenerator: while len(os.path.abspath(fn)) > max_length: fn = fn[:-1] fn += ext - debug(f'Filename sanitize: input="{filename}" parts={parts} output="{fn}" ext={ext} max={max_length} len={len(fn)}') + debug_log(f'Filename sanitize: input="{filename}" parts={parts} output="{fn}" ext={ext} max={max_length} len={len(fn)}') return fn def safe_int(self, s): @@ -234,25 +235,38 @@ class FilenameGenerator: def apply(self, x): res = '' + if debug: + for k in self.replacements.keys(): + try: + fn = self.replacements.get(k, None) + debug_log(f'Namegen: key={k} value={fn(self)}') + except Exception as e: + shared.log.error(f'Namegen: key={k} {e}') + errors.display(e, 'namegen') for m in re_pattern.finditer(x): text, pattern = m.groups() - if pattern is None: - res += text - continue - pattern_args = [] - while True: - m = re_pattern_arg.match(pattern) - if m is None: - break - pattern, arg = m.groups() - pattern_args.insert(0, arg) + debug_log(f'Filename apply: text="{text}" pattern="{pattern}"') if isinstance(pattern, list): pattern = ' '.join(pattern) + if pattern is None or not isinstance(pattern, str) or pattern.strip() == '': + debug_log(f'Filename skip: pattern="{pattern}"') + res += text + continue + + _pattern = pattern + pattern_args = [] + while True: + m = re_pattern_arg.match(_pattern) + if m is None: + break + _pattern, arg = m.groups() + pattern_args.insert(0, arg) + fun = self.replacements.get(pattern.lower(), None) if fun is not None: try: - debug(f'Filename apply: pattern={pattern.lower()} args={pattern_args}') replacement = fun(self, *pattern_args) + debug_log(f'Filename apply: pattern="{pattern}" args={pattern_args} replacement="{replacement}"') except Exception as e: replacement = None errors.display(e, 'namegen') diff --git a/modules/paths.py b/modules/paths.py index e3141814d..bb36cde5d 100644 --- a/modules/paths.py +++ b/modules/paths.py @@ -4,6 +4,7 @@ import sys import json import shlex import argparse +import tempfile from installer import log @@ -18,12 +19,16 @@ cli = parser.parse_known_args(argv)[0] parser.add_argument("--config", type=str, default=os.environ.get("SD_CONFIG", os.path.join(cli.data_dir, 'config.json')), help="Use specific server configuration file, default: %(default)s") # twice because we want data_dir cli = parser.parse_known_args(argv)[0] config_path = cli.config if os.path.isabs(cli.config) else os.path.join(cli.data_dir, cli.config) + try: with open(config_path, 'r', encoding='utf8') as f: config = json.load(f) except Exception: config = {} +temp_dir = config.get('temp_dir', '') +if len(temp_dir) == 0: + temp_dir = tempfile.gettempdir() reference_path = os.path.join('models', 'Reference') modules_path = os.path.dirname(os.path.realpath(__file__)) script_path = os.path.dirname(modules_path) diff --git a/modules/ui_common.py b/modules/ui_common.py index 3b43ea566..5a6299afe 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -5,8 +5,7 @@ import shutil import platform import subprocess import gradio as gr -from modules import call_queue, shared, errors, ui_sections, ui_symbols, ui_components, generation_parameters_copypaste, images, scripts_manager, script_callbacks, infotext, processing -from modules.paths import resolve_output_path +from modules import paths, call_queue, shared, errors, ui_sections, ui_symbols, ui_components, generation_parameters_copypaste, images, scripts_manager, script_callbacks, infotext, processing folder_symbol = ui_symbols.folder @@ -106,7 +105,7 @@ def delete_files(js_data, files, all_files, index): def save_files(js_data, files, html_info, index): - os.makedirs(resolve_output_path(shared.opts.outdir_samples, shared.opts.outdir_save), exist_ok=True) + os.makedirs(paths.resolve_output_path(shared.opts.outdir_samples, shared.opts.outdir_save), exist_ok=True) class PObject: # pylint: disable=too-few-public-methods def __init__(self, d=None): @@ -116,6 +115,7 @@ def save_files(js_data, files, html_info, index): self.prompt = getattr(self, 'prompt', None) or getattr(self, 'Prompt', None) or '' self.negative_prompt = getattr(self, 'negative_prompt', None) or getattr(self, 'Negative_prompt', None) or '' self.sampler = getattr(self, 'sampler', None) or getattr(self, 'Sampler', None) or '' + self.sampler_name = self.sampler self.seed = getattr(self, 'seed', None) or getattr(self, 'Seed', None) or 0 self.steps = getattr(self, 'steps', None) or getattr(self, 'Steps', None) or 0 self.width = getattr(self, 'width', None) or getattr(self, 'Width', None) or getattr(self, 'Size-1', None) or 0 @@ -128,13 +128,16 @@ def save_files(js_data, files, html_info, index): self.styles = getattr(self, 'styles', None) or getattr(self, 'Styles', None) or [] self.styles = [s.strip() for s in self.styles.split(',')] if isinstance(self.styles, str) else self.styles - self.outpath_grids = resolve_output_path(shared.opts.outdir_grids, shared.opts.outdir_txt2img_grids) + self.outpath_grids = paths.resolve_output_path(shared.opts.outdir_grids, shared.opts.outdir_txt2img_grids) self.infotexts = getattr(self, 'infotexts', [html_info]) self.infotext = self.infotexts[0] if len(self.infotexts) > 0 else html_info self.all_negative_prompt = getattr(self, 'all_negative_prompts', [self.negative_prompt]) self.all_prompts = getattr(self, 'all_prompts', [self.prompt]) self.all_seeds = getattr(self, 'all_seeds', [self.seed]) self.all_subseeds = getattr(self, 'all_subseeds', [self.subseed]) + + self.n_iter = 1 + self.batch_size = 1 try: data = json.loads(js_data) except Exception: @@ -159,17 +162,17 @@ def save_files(js_data, files, html_info, index): p.all_prompts.append(p.prompt) while len(p.infotexts) <= i: p.infotexts.append(p.infotext) - if 'name' in filedata and ('tmp' not in filedata['name']) and os.path.isfile(filedata['name']): + if 'name' in filedata and (paths.temp_dir not in filedata['name']) and os.path.isfile(filedata['name']): fullfn = filedata['name'] fullfns.append(fullfn) - destination = resolve_output_path(shared.opts.outdir_samples, shared.opts.outdir_save) + destination = paths.resolve_output_path(shared.opts.outdir_samples, shared.opts.outdir_save) namegen = images.FilenameGenerator(p, seed=p.all_seeds[i], prompt=p.all_prompts[i], image=None) # pylint: disable=no-member dirname = namegen.apply(shared.opts.directories_filename_pattern or "[prompt_words]").lstrip(' ').rstrip('\\ /') destination = os.path.join(destination, dirname) destination = namegen.sanitize(destination) os.makedirs(destination, exist_ok = True) tgt_filename = os.path.join(destination, os.path.basename(fullfn)) - relfn = os.path.relpath(tgt_filename, resolve_output_path(shared.opts.outdir_samples, shared.opts.outdir_save)) + relfn = os.path.relpath(tgt_filename, paths.resolve_output_path(shared.opts.outdir_samples, shared.opts.outdir_save)) filenames.append(relfn) if not os.path.exists(tgt_filename): try: @@ -195,21 +198,20 @@ def save_files(js_data, files, html_info, index): if len(info) == 0: info = None if (js_data is None or len(js_data) == 0) and image is not None and image.info is not None: - info = image.info.pop('parameters', None) or image.info.pop('UserComment', None) - geninfo, _ = images.read_info_from_image(image) - items = infotext.parse(geninfo) + info, _items = images.read_info_from_image(image) + items = infotext.parse(info) p = PObject(items) try: seed = p.all_seeds[i] if i < len(p.all_seeds) else p.seed prompt = p.all_prompts[i] if i < len(p.all_prompts) else p.prompt - fullfn, txt_fullfn, _exif = images.save_image(image, resolve_output_path(shared.opts.outdir_samples, shared.opts.outdir_save), "", seed=seed, prompt=prompt, info=info, extension=shared.opts.samples_format, grid=is_grid, p=p) + fullfn, txt_fullfn, _exif = images.save_image(image, paths.resolve_output_path(shared.opts.outdir_samples, shared.opts.outdir_save), "", seed=seed, prompt=prompt, info=info, extension=shared.opts.samples_format, grid=is_grid, p=p) except Exception as e: fullfn, txt_fullfn = None, None shared.log.error(f'Save: image={image} i={i} seeds={p.all_seeds} prompts={p.all_prompts}') errors.display(e, 'save') if fullfn is None: continue - filename = os.path.relpath(fullfn, resolve_output_path(shared.opts.outdir_samples, shared.opts.outdir_save)) + filename = os.path.relpath(fullfn, paths.resolve_output_path(shared.opts.outdir_samples, shared.opts.outdir_save)) filenames.append(filename) fullfns.append(fullfn) if txt_fullfn: @@ -217,7 +219,7 @@ def save_files(js_data, files, html_info, index): # fullfns.append(txt_fullfn) script_callbacks.image_save_btn_callback(filename) if shared.opts.samples_save_zip and len(fullfns) > 1: - zip_filepath = os.path.join(resolve_output_path(shared.opts.outdir_samples, shared.opts.outdir_save), "images.zip") + zip_filepath = os.path.join(paths.resolve_output_path(shared.opts.outdir_samples, shared.opts.outdir_save), "images.zip") from zipfile import ZipFile with ZipFile(zip_filepath, "w") as zip_file: for i in range(len(fullfns)): diff --git a/wiki b/wiki index 440b88383..a678731c5 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 440b883838f269712aaae46b0debe9545856b378 +Subproject commit a678731c5da5d77f7c21edf7f1d62d8307d0d7fe