diff --git a/javascript/gallery.js b/javascript/gallery.js index bc84acaa9..acea7127b 100644 --- a/javascript/gallery.js +++ b/javascript/gallery.js @@ -239,9 +239,16 @@ class SimpleFunctionQueue { // HTML Elements class GalleryFolder extends HTMLElement { - constructor(name) { + constructor(folder) { super(); - this.name = decodeURI(name); + // Support both old format (string) and new format (object with path and label) + if (typeof folder === 'object' && folder !== null) { + this.name = decodeURI(folder.path || ''); + this.label = decodeURI(folder.label || folder.path || ''); + } else { + this.name = decodeURI(folder); + this.label = this.name; + } this.style.overflowX = 'hidden'; this.shadow = this.attachShadow({ mode: 'open' }); this.shadow.adoptedStyleSheets = [folderStylesheet]; @@ -250,8 +257,8 @@ class GalleryFolder extends HTMLElement { connectedCallback() { const div = document.createElement('div'); div.className = 'gallery-folder'; - div.innerHTML = `\uf03e ${this.name}`; - div.title = this.name; + div.innerHTML = `\uf03e ${this.label}`; + div.title = this.name; // Show full path on hover div.addEventListener('click', () => { for (const folder of el.folders.children) { if (folder.name === this.name) folder.shadow.firstElementChild.classList.add('gallery-folder-selected'); diff --git a/modules/api/gallery.py b/modules/api/gallery.py index d3fdf85ef..e4dc8ba0b 100644 --- a/modules/api/gallery.py +++ b/modules/api/gallery.py @@ -10,6 +10,7 @@ from starlette.websockets import WebSocket, WebSocketState from pydantic import BaseModel, Field # pylint: disable=no-name-in-module from PIL import Image from modules import shared, images, files_cache, modelstats +from modules.paths import resolve_output_path debug = shared.log.debug if os.environ.get('SD_BROWSER_DEBUG', None) is not None else lambda *args, **kwargs: None @@ -128,20 +129,51 @@ def register_api(app: FastAPI): # register api # @app.get('/sdapi/v1/browser/folders', response_model=List[str]) def get_folders(): + def make_folder(path, label=None): + """Create folder entry with path and display label.""" + if label is None: + label = os.path.basename(path) or path + return {"path": path, "label": label} + reference_dir = os.path.join('models', 'Reference') - folders = [shared.opts.data.get(f, '') for f in OPTS_FOLDERS] - folders += list(shared.opts.browser_folders.split(',')) - folders += [reference_dir] - folders = [f.strip() for f in folders if f != ''] - folders = list(dict.fromkeys(folders)) # filter duplicates - folders = [f for f in folders if os.path.isdir(f)] - if shared.demo is not None: - for f in folders: - if f not in shared.demo.allowed_paths: - debug(f'Browser folders allow: {f}') - shared.demo.allowed_paths.append(quote(f)) - debug(f'Browser folders: {folders}') - return JSONResponse(content=folders) + base_samples = shared.opts.outdir_samples + base_grids = shared.opts.outdir_grids + # Build list of resolved output paths with labels + folders = [] + if base_samples: + folders.append(make_folder(base_samples, os.path.basename(base_samples.rstrip('/\\')))) + if base_grids and base_grids != base_samples: + folders.append(make_folder(base_grids, os.path.basename(base_grids.rstrip('/\\')))) + # Use the specific folder setting values as labels (e.g., "outputs/text" -> "outputs/text") + folders.append(make_folder(resolve_output_path(base_samples, shared.opts.outdir_txt2img_samples), shared.opts.outdir_txt2img_samples)) + folders.append(make_folder(resolve_output_path(base_samples, shared.opts.outdir_img2img_samples), shared.opts.outdir_img2img_samples)) + folders.append(make_folder(resolve_output_path(base_samples, shared.opts.outdir_control_samples), shared.opts.outdir_control_samples)) + folders.append(make_folder(resolve_output_path(base_samples, shared.opts.outdir_extras_samples), shared.opts.outdir_extras_samples)) + folders.append(make_folder(resolve_output_path(base_samples, shared.opts.outdir_save), shared.opts.outdir_save)) + folders.append(make_folder(resolve_output_path(base_samples, shared.opts.outdir_video), shared.opts.outdir_video)) + folders.append(make_folder(resolve_output_path(base_samples, shared.opts.outdir_init_images), shared.opts.outdir_init_images)) + folders.append(make_folder(resolve_output_path(base_grids, shared.opts.outdir_txt2img_grids), shared.opts.outdir_txt2img_grids)) + folders.append(make_folder(resolve_output_path(base_grids, shared.opts.outdir_img2img_grids), shared.opts.outdir_img2img_grids)) + folders.append(make_folder(resolve_output_path(base_grids, shared.opts.outdir_control_grids), shared.opts.outdir_control_grids)) + # Custom browser folders and reference dir + for f in shared.opts.browser_folders.split(','): + f = f.strip() + if f: + folders.append(make_folder(f)) + folders.append(make_folder(reference_dir, 'Reference')) + # Filter empty and duplicates (by path) + seen_paths = set() + unique_folders = [] + for f in folders: + path = f["path"].strip() + if path and path not in seen_paths and os.path.isdir(path): + seen_paths.add(path) + unique_folders.append(f) + if shared.demo is not None and path not in shared.demo.allowed_paths: + debug(f'Browser folders allow: {path}') + shared.demo.allowed_paths.append(quote(path)) + debug(f'Browser folders: {unique_folders}') + return JSONResponse(content=unique_folders) # @app.get("/sdapi/v1/browser/thumb", response_model=dict) async def get_thumb(file: str): diff --git a/modules/api/generate.py b/modules/api/generate.py index f03a3a360..102b15f2c 100644 --- a/modules/api/generate.py +++ b/modules/api/generate.py @@ -3,6 +3,7 @@ from fastapi.responses import JSONResponse from modules import errors, shared, scripts_manager, ui from modules.api import models, script, helpers from modules.processing import StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img, process_images +from modules.paths import resolve_output_path errors.install() @@ -105,8 +106,8 @@ class APIGenerate(): p = StableDiffusionProcessingTxt2Img(sd_model=shared.sd_model, **args) self.prepare_ip_adapter(txt2imgreq, p) p.scripts = script_runner - p.outpath_grids = shared.opts.outdir_grids or shared.opts.outdir_txt2img_grids - p.outpath_samples = shared.opts.outdir_samples or shared.opts.outdir_txt2img_samples + p.outpath_grids = resolve_output_path(shared.opts.outdir_grids, shared.opts.outdir_txt2img_grids) + p.outpath_samples = resolve_output_path(shared.opts.outdir_samples, shared.opts.outdir_txt2img_samples) for key, value in getattr(txt2imgreq, "extra", {}).items(): setattr(p, key, value) jobid = shared.state.begin('API-TXT', api=True) @@ -157,8 +158,8 @@ class APIGenerate(): self.prepare_ip_adapter(img2imgreq, p) p.init_images = [helpers.decode_base64_to_image(x) for x in init_images] p.scripts = script_runner - p.outpath_grids = shared.opts.outdir_img2img_grids - p.outpath_samples = shared.opts.outdir_img2img_samples + p.outpath_grids = resolve_output_path(shared.opts.outdir_grids, shared.opts.outdir_img2img_grids) + p.outpath_samples = resolve_output_path(shared.opts.outdir_samples, shared.opts.outdir_img2img_samples) for key, value in getattr(img2imgreq, "extra", {}).items(): setattr(p, key, value) jobid = shared.state.begin('API-IMG', api=True) diff --git a/modules/control/run.py b/modules/control/run.py index 726d3aa4f..243840ff1 100644 --- a/modules/control/run.py +++ b/modules/control/run.py @@ -18,6 +18,7 @@ from modules.processing_class import StableDiffusionProcessingControl from modules.ui_common import infotext_to_html from modules.api import script from modules.generation_parameters_copypaste import create_override_settings_dict +from modules.paths import resolve_output_path debug = os.environ.get('SD_CONTROL_DEBUG', None) is not None @@ -402,8 +403,8 @@ def control_run(state: str = '', # pylint: disable=keyword-arg-before-vararg hdr_mode=hdr_mode, hdr_brightness=hdr_brightness, hdr_color=hdr_color, hdr_sharpen=hdr_sharpen, hdr_clamp=hdr_clamp, hdr_boundary=hdr_boundary, hdr_threshold=hdr_threshold, hdr_maximize=hdr_maximize, hdr_max_center=hdr_max_center, hdr_max_boundary=hdr_max_boundary, hdr_color_picker=hdr_color_picker, hdr_tint_ratio=hdr_tint_ratio, # path - outpath_samples=shared.opts.outdir_samples or shared.opts.outdir_control_samples, - outpath_grids=shared.opts.outdir_grids or shared.opts.outdir_control_grids, + outpath_samples=resolve_output_path(shared.opts.outdir_samples, shared.opts.outdir_control_samples), + outpath_grids=resolve_output_path(shared.opts.outdir_grids, shared.opts.outdir_control_grids), # overrides override_settings=extra ) @@ -572,13 +573,13 @@ def control_run(state: str = '', # pylint: disable=keyword-arg-before-vararg # what are we doing? if 'control' in p.ops: - p.outpath_samples = shared.opts.outdir_samples or shared.opts.outdir_control_samples + p.outpath_samples = resolve_output_path(shared.opts.outdir_samples, shared.opts.outdir_control_samples) elif 'img2img' in p.ops: - p.outpath_samples = shared.opts.outdir_samples or shared.opts.outdir_img2img_samples + p.outpath_samples = resolve_output_path(shared.opts.outdir_samples, shared.opts.outdir_img2img_samples) elif 'txt2img' in p.ops: - p.outpath_samples = shared.opts.outdir_samples or shared.opts.outdir_txt2img_samples + p.outpath_samples = resolve_output_path(shared.opts.outdir_samples, shared.opts.outdir_txt2img_samples) else: # fallback to txt2img - p.outpath_samples = shared.opts.outdir_samples or shared.opts.outdir_txt2img_samples + p.outpath_samples = resolve_output_path(shared.opts.outdir_samples, shared.opts.outdir_txt2img_samples) # pipeline output = None diff --git a/modules/gr_tempdir.py b/modules/gr_tempdir.py index f19fd53a3..eacf782f5 100644 --- a/modules/gr_tempdir.py +++ b/modules/gr_tempdir.py @@ -19,19 +19,32 @@ def check_tmp_file(gradio, filename): ok = False if hasattr(gradio, 'temp_file_sets'): ok = ok or any(filename in fileset for fileset in gradio.temp_file_sets) - if shared.opts.outdir_samples != '': - ok = ok or Path(shared.opts.outdir_samples).resolve() in Path(filename).resolve().parents - else: - ok = ok or Path(shared.opts.outdir_txt2img_samples).resolve() in Path(filename).resolve().parents - ok = ok or Path(shared.opts.outdir_img2img_samples).resolve() in Path(filename).resolve().parents - ok = ok or Path(shared.opts.outdir_extras_samples).resolve() in Path(filename).resolve().parents - if shared.opts.outdir_grids != '': - ok = ok or Path(shared.opts.outdir_grids).resolve() in Path(filename).resolve().parents - else: - ok = ok or Path(shared.opts.outdir_txt2img_grids).resolve() in Path(filename).resolve().parents - ok = ok or Path(shared.opts.outdir_img2img_grids).resolve() in Path(filename).resolve().parents - ok = ok or Path(shared.opts.outdir_save).resolve() in Path(filename).resolve().parents - ok = ok or Path(shared.opts.outdir_init_images).resolve() in Path(filename).resolve().parents + # Check resolved output paths (base + specific) + base_samples = shared.opts.outdir_samples + base_grids = shared.opts.outdir_grids + resolved_paths = [ + paths.resolve_output_path(base_samples, shared.opts.outdir_txt2img_samples), + paths.resolve_output_path(base_samples, shared.opts.outdir_img2img_samples), + paths.resolve_output_path(base_samples, shared.opts.outdir_extras_samples), + paths.resolve_output_path(base_samples, shared.opts.outdir_control_samples), + paths.resolve_output_path(base_samples, shared.opts.outdir_save), + paths.resolve_output_path(base_samples, shared.opts.outdir_video), + paths.resolve_output_path(base_samples, shared.opts.outdir_init_images), + paths.resolve_output_path(base_grids, shared.opts.outdir_txt2img_grids), + paths.resolve_output_path(base_grids, shared.opts.outdir_img2img_grids), + paths.resolve_output_path(base_grids, shared.opts.outdir_control_grids), + ] + # Also check base folders directly if set + if base_samples: + resolved_paths.append(base_samples) + if base_grids: + resolved_paths.append(base_grids) + for path in resolved_paths: + if path: + try: + ok = ok or Path(path).resolve() in Path(filename).resolve().parents + except Exception: + pass return ok diff --git a/modules/images.py b/modules/images.py index 3088305ad..c54f982eb 100644 --- a/modules/images.py +++ b/modules/images.py @@ -164,7 +164,7 @@ def save_image(image, if not check_grid_size([image]): return None, None, None if path is None or path == '': # set default path to avoid errors when functions are triggered manually or via api and param is not set - path = shared.opts.outdir_save + path = paths.resolve_output_path(shared.opts.outdir_samples, shared.opts.outdir_save) namegen = FilenameGenerator(p, seed, prompt, image, grid=grid) suffix = suffix if suffix is not None else '' basename = '' if basename is None else basename diff --git a/modules/img2img.py b/modules/img2img.py index 4809d9fa0..bc080f732 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -7,6 +7,7 @@ from modules import scripts_manager, shared, processing, images, errors from modules.generation_parameters_copypaste import create_override_settings_dict from modules.ui_common import plaintext_to_html from modules.memstats import memory_stats +from modules.paths import resolve_output_path debug = shared.log.trace if os.environ.get('SD_PROCESS_DEBUG', None) is not None else lambda *args, **kwargs: None @@ -239,8 +240,8 @@ def img2img(id_task: str, state: str, mode: int, p = processing.StableDiffusionProcessingImg2Img( sd_model=shared.sd_model, - outpath_samples=shared.opts.outdir_samples or shared.opts.outdir_img2img_samples, - outpath_grids=shared.opts.outdir_grids or shared.opts.outdir_img2img_grids, + outpath_samples=resolve_output_path(shared.opts.outdir_samples, shared.opts.outdir_img2img_samples), + outpath_grids=resolve_output_path(shared.opts.outdir_grids, shared.opts.outdir_img2img_grids), prompt=prompt, negative_prompt=negative_prompt, styles=prompt_styles, diff --git a/modules/paths.py b/modules/paths.py index 503f6eeb0..e3141814d 100644 --- a/modules/paths.py +++ b/modules/paths.py @@ -57,6 +57,23 @@ def create_path(folder): log.error(f'Create failed: folder="{folder}" {e}') +def resolve_output_path(base_path: str, specific_path: str) -> str: + """ + Resolve output path by combining base and specific paths. + + - If specific_path is absolute, return it directly (base is ignored) + - If base_path is set and specific_path is relative, join them + - If base_path is empty/None, return specific_path as-is + """ + if not specific_path: + return base_path or '' + if os.path.isabs(specific_path): + return specific_path + if base_path: + return os.path.normpath(os.path.join(base_path, specific_path)) + return specific_path + + def create_paths(opts): def fix_path(folder): tgt = None @@ -111,6 +128,22 @@ def create_paths(opts): create_path(fix_path('yolo_dir')) create_path(fix_path('wildcards_dir')) + # Create resolved output paths (base + specific) + base_samples = opts.data.get('outdir_samples', '') + base_grids = opts.data.get('outdir_grids', '') + if base_samples: + create_path(resolve_output_path(base_samples, opts.data.get('outdir_txt2img_samples', ''))) + create_path(resolve_output_path(base_samples, opts.data.get('outdir_img2img_samples', ''))) + create_path(resolve_output_path(base_samples, opts.data.get('outdir_control_samples', ''))) + create_path(resolve_output_path(base_samples, opts.data.get('outdir_extras_samples', ''))) + create_path(resolve_output_path(base_samples, opts.data.get('outdir_save', ''))) + create_path(resolve_output_path(base_samples, opts.data.get('outdir_video', ''))) + create_path(resolve_output_path(base_samples, opts.data.get('outdir_init_images', ''))) + if base_grids: + create_path(resolve_output_path(base_grids, opts.data.get('outdir_txt2img_grids', ''))) + create_path(resolve_output_path(base_grids, opts.data.get('outdir_img2img_grids', ''))) + create_path(resolve_output_path(base_grids, opts.data.get('outdir_control_grids', ''))) + class Prioritize: def __init__(self, name): diff --git a/modules/postprocessing.py b/modules/postprocessing.py index 296db6eda..1f04905c5 100644 --- a/modules/postprocessing.py +++ b/modules/postprocessing.py @@ -6,6 +6,7 @@ from PIL import Image from modules import shared, images, devices, scripts_manager, scripts_postprocessing, infotext from modules.shared import opts +from modules.paths import resolve_output_path def run_postprocessing(extras_mode, image, image_folder: List[tempfile.NamedTemporaryFile], input_dir, output_dir, show_extras_results, *args, save_output: bool = True): @@ -58,7 +59,7 @@ def run_postprocessing(extras_mode, image, image_folder: List[tempfile.NamedTemp if extras_mode == 2 and output_dir != '': outpath = output_dir else: - outpath = opts.outdir_samples or opts.outdir_extras_samples + outpath = resolve_output_path(opts.outdir_samples, opts.outdir_extras_samples) processed_images = [] for image, name, ext in zip(image_data, image_names, image_ext): # pylint: disable=redefined-argument-from-local shared.log.debug(f'Process: image={image} {args}') diff --git a/modules/processing_class.py b/modules/processing_class.py index 37ad0b602..a4675074c 100644 --- a/modules/processing_class.py +++ b/modules/processing_class.py @@ -7,6 +7,7 @@ from dataclasses import dataclass, field import numpy as np from PIL import Image, ImageOps from modules import shared, images, scripts_manager, masking, sd_models, sd_vae, processing_helpers +from modules.paths import resolve_output_path debug = shared.log.trace if os.environ.get('SD_PROCESS_DEBUG', None) is not None else lambda *args, **kwargs: None @@ -537,7 +538,7 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): self.init_img_width = getattr(self, 'init_img_width', img.width) # pylint: disable=attribute-defined-outside-init self.init_img_height = getattr(self, 'init_img_height', img.height) # pylint: disable=attribute-defined-outside-init if shared.opts.save_init_img: - images.save_image(img, path=shared.opts.outdir_init_images, basename=None, forced_filename=self.init_img_hash, suffix="-init-image") + images.save_image(img, path=resolve_output_path(shared.opts.outdir_samples, shared.opts.outdir_init_images), basename=None, forced_filename=self.init_img_hash, suffix="-init-image") image = images.flatten(img, shared.opts.img2img_background_color) if crop_region is None and self.resize_mode > 0: image = images.resize_image(self.resize_mode, image, self.width, self.height, upscaler_name=self.resize_name, context=self.resize_context) diff --git a/modules/shared.py b/modules/shared.py index 56d624846..6a3fbf8f0 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -558,7 +558,7 @@ options_templates.update(options_section(('saving-paths', "Image Paths"), { "directories_max_prompt_words": OptionInfo(8, "Max words", gr.Slider, {"minimum": 1, "maximum": 99, "step": 1, **hide_dirs}), "outdir_sep_dirs": OptionInfo("

Folders

", "", gr.HTML), - "outdir_samples": OptionInfo("", "Images folder", component_args=hide_dirs, folder=True), + "outdir_samples": OptionInfo("", "Base images folder", component_args=hide_dirs, folder=True), "outdir_txt2img_samples": OptionInfo("outputs/text", 'Folder for text generate', component_args=hide_dirs, folder=True), "outdir_img2img_samples": OptionInfo("outputs/image", 'Folder for image generate', component_args=hide_dirs, folder=True), "outdir_control_samples": OptionInfo("outputs/control", 'Folder for control generate', component_args=hide_dirs, folder=True), @@ -568,7 +568,7 @@ options_templates.update(options_section(('saving-paths', "Image Paths"), { "outdir_init_images": OptionInfo("outputs/inputs", "Folder for init images", component_args=hide_dirs, folder=True), "outdir_sep_grids": OptionInfo("

Grids

", "", gr.HTML), - "outdir_grids": OptionInfo("", "Grids folder", component_args=hide_dirs, folder=True), + "outdir_grids": OptionInfo("", "Base grids folde", component_args=hide_dirs, folder=True), "outdir_txt2img_grids": OptionInfo("outputs/grids", 'Folder for txt2img grids', component_args=hide_dirs, folder=True), "outdir_img2img_grids": OptionInfo("outputs/grids", 'Folder for img2img grids', component_args=hide_dirs, folder=True), "outdir_control_grids": OptionInfo("outputs/grids", 'Folder for control grids', component_args=hide_dirs, folder=True), diff --git a/modules/txt2img.py b/modules/txt2img.py index 08d4f1bf2..021b586e5 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -2,6 +2,7 @@ import os from modules import shared, processing, scripts_manager from modules.generation_parameters_copypaste import create_override_settings_dict from modules.ui_common import plaintext_to_html +from modules.paths import resolve_output_path debug = shared.log.trace if os.environ.get('SD_PROCESS_DEBUG', None) is not None else lambda *args, **kwargs: None @@ -41,8 +42,8 @@ def txt2img(id_task, state, p = processing.StableDiffusionProcessingTxt2Img( sd_model=shared.sd_model, - outpath_samples=shared.opts.outdir_samples or shared.opts.outdir_txt2img_samples, - outpath_grids=shared.opts.outdir_grids or shared.opts.outdir_txt2img_grids, + outpath_samples=resolve_output_path(shared.opts.outdir_samples, shared.opts.outdir_txt2img_samples), + outpath_grids=resolve_output_path(shared.opts.outdir_grids, shared.opts.outdir_txt2img_grids), prompt=prompt, styles=prompt_styles, negative_prompt=negative_prompt, diff --git a/modules/ui_common.py b/modules/ui_common.py index fcb522701..aa7a1c8bb 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -6,6 +6,7 @@ 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 folder_symbol = ui_symbols.folder @@ -105,7 +106,7 @@ def delete_files(js_data, files, all_files, index): def save_files(js_data, files, html_info, index): - os.makedirs(shared.opts.outdir_save, exist_ok=True) + os.makedirs(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): @@ -127,7 +128,7 @@ 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 = shared.opts.outdir_grids or shared.opts.outdir_txt2img_grids + self.outpath_grids = 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]) @@ -161,14 +162,14 @@ def save_files(js_data, files, html_info, index): if 'name' in filedata and ('tmp' not in filedata['name']) and os.path.isfile(filedata['name']): fullfn = filedata['name'] fullfns.append(fullfn) - destination = shared.opts.outdir_save + destination = 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, shared.opts.outdir_save) + relfn = os.path.relpath(tgt_filename, resolve_output_path(shared.opts.outdir_samples, shared.opts.outdir_save)) filenames.append(relfn) if not os.path.exists(tgt_filename): try: @@ -201,14 +202,14 @@ def save_files(js_data, files, html_info, index): 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, 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, 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, shared.opts.outdir_save) + filename = os.path.relpath(fullfn, resolve_output_path(shared.opts.outdir_samples, shared.opts.outdir_save)) filenames.append(filename) fullfns.append(fullfn) if txt_fullfn: @@ -216,7 +217,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(shared.opts.outdir_save, "images.zip") + zip_filepath = os.path.join(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/modules/video_models/video_run.py b/modules/video_models/video_run.py index 596497b6f..90fafa09a 100644 --- a/modules/video_models/video_run.py +++ b/modules/video_models/video_run.py @@ -3,6 +3,7 @@ import copy import time from modules import shared, errors, sd_models, processing, devices, images, ui_common from modules.video_models import models_def, video_utils, video_load, video_vae, video_overrides, video_save, video_prompt +from modules.paths import resolve_output_path debug = shared.log.trace if os.environ.get('SD_VIDEO_DEBUG', None) is not None else lambda *args, **kwargs: None @@ -56,7 +57,7 @@ def generate(*args, **kwargs): p.state = ui_state p.do_not_save_grid = True p.do_not_save_samples = not mp4_frames - p.outpath_samples = shared.opts.outdir_samples or shared.opts.outdir_video + p.outpath_samples = resolve_output_path(shared.opts.outdir_samples, shared.opts.outdir_video) if 'T2V' in model: if init_image is not None: shared.log.warning('Video: op=T2V init image not supported')