diff --git a/cli/nvidia-smi.py b/cli/nvidia-smi.py
index 37d6390c5..298fc562e 100755
--- a/cli/nvidia-smi.py
+++ b/cli/nvidia-smi.py
@@ -13,9 +13,8 @@ def get_nvidia_smi(output='dict'):
if smi is None:
log.error("nvidia-smi not found")
return None
- result = subprocess.run(f'"{smi}" -q -x', shell=True, check=False, env=os.environ, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- xml = result.stdout.decode(encoding="utf8", errors="ignore")
- d = xmltodict.parse(xml)
+ result = subprocess.run(f'"{smi}" -q -x', shell=True, check=False, env=os.environ, capture_output=True, text=True)
+ d = xmltodict.parse(result.stdout)
if 'nvidia_smi_log' in d:
d = d['nvidia_smi_log']
if 'gpu' in d and 'supported_clocks' in d['gpu']:
diff --git a/installer.py b/installer.py
index 97a64be9b..633724413 100644
--- a/installer.py
+++ b/installer.py
@@ -200,14 +200,32 @@ def uninstall(package, quiet = False):
return res
-def run(cmd: str, arg: str):
- result = subprocess.run(f'"{cmd}" {arg}', shell=True, check=False, env=os.environ, capture_output=True)
- txt = result.stdout.decode(encoding="utf8", errors="ignore")
- if len(result.stderr) > 0:
- txt += ('\n' if len(txt) > 0 else '') + result.stderr.decode(encoding="utf8", errors="ignore")
- txt = txt.strip()
- debug(f'Exec {cmd}: {txt}')
- return txt
+def run(cmd: str, *args: str, **kwargs):
+ """Run command and arguments with `subprocess.run`.
+
+ Default run options are `shell=True, check=False, env=os.environ`.
+
+ Args:
+ cmd (str): Main command to run.
+ *args (str): Additional command arguments.
+ **kwargs: `subprocess.run` option overrides.
+
+ Returns:
+ tuple[CompletedProcess[str], str]: Tuple with the results and the combined `stdout` and `stderr` values.
+ """
+ options = {
+ "check": False,
+ "env": os.environ,
+ }
+ options |= kwargs # Override defaults with passed kwargs
+ result = subprocess.run(f'"{cmd}" {" ".join(args)}', **options, shell=True, capture_output=True, text=True)
+ result.stdout = result.stdout.strip()
+ result.stderr = result.stderr.strip()
+ txt = result.stdout
+ if result.stderr:
+ # Put newline between outputs only if stdout isn't empty
+ txt += "\n" + result.stderr if txt else result.stderr
+ return result, txt
def cleanup_broken_packages():
@@ -246,18 +264,13 @@ def pip(arg: str, ignore: bool = False, quiet: bool = True, uv = True):
all_args = f'{pip_log}{arg} {env_args}'.strip()
if not quiet:
log.debug(f'Running: {pipCmd}="{all_args}"')
- result = subprocess.run(f'"{sys.executable}" -m {pipCmd} {all_args}', shell=True, check=False, env=os.environ, capture_output=True)
- txt = result.stdout.decode(encoding="utf8", errors="ignore")
+ result, txt = run(sys.executable, "-m", pipCmd, all_args)
if len(result.stderr) > 0:
if uv and result.returncode != 0:
- err = result.stderr.decode(encoding="utf8", errors="ignore")
log.warning(f'Install: cmd="{pipCmd}" args="{all_args}" cannot use uv, fallback to pip')
- debug(f'Install: uv pip error: {err}')
+ debug(f'Install: uv pip error: {result.stderr}')
cleanup_broken_packages()
return pip(originalArg, ignore, quiet, uv=False)
- else:
- txt += ('\n' if len(txt) > 0 else '') + result.stderr.decode(encoding="utf8", errors="ignore")
- txt = txt.strip()
debug(f'Install {pipCmd}: {txt}')
if result.returncode != 0 and not ignore:
errors.append(f'pip: {package}')
@@ -294,25 +307,21 @@ def git(arg: str, folder: str = None, ignore: bool = False, optional: bool = Fal
git_cmd = os.environ.get('GIT', "git")
if git_cmd != "git":
git_cmd = os.path.abspath(git_cmd)
- result = subprocess.run(f'"{git_cmd}" {arg}', check=False, shell=True, env=os.environ, capture_output=True, cwd=folder or '.')
- stdout = result.stdout.decode(encoding="utf8", errors="ignore")
- if len(result.stderr) > 0:
- stdout += ('\n' if len(stdout) > 0 else '') + result.stderr.decode(encoding="utf8", errors="ignore")
- stdout = stdout.strip()
+ result, txt = run(git_cmd, arg, cwd=folder or ".")
if result.returncode != 0 and not ignore:
if folder is None:
folder = 'root'
- if "couldn't find remote ref" in stdout: # not a git repo
+ if "couldn't find remote ref" in txt: # not a git repo
log.error(f'Git: folder="{folder}" could not identify repository')
- elif "no submodule mapping found" in stdout:
+ elif "no submodule mapping found" in txt:
log.warning(f'Git: folder="{folder}" submodules changed')
- elif 'or stash them' in stdout:
+ elif 'or stash them' in txt:
log.error(f'Git: folder="{folder}" local changes detected')
else:
- log.error(f'Git: folder="{folder}" arg="{arg}" output={stdout}')
+ log.error(f'Git: folder="{folder}" arg="{arg}" output={txt}')
errors.append(f'git: {folder}')
ts('git', t_start)
- return stdout
+ return txt
# reattach as needed as head can get detached
@@ -471,7 +480,7 @@ def check_diffusers():
t_start = time.time()
if args.skip_all:
return
- target_commit = '4a2833c1c226362677e165a363fecb4da331e122' # diffusers commit hash
+ target_commit = '8ec0a5ccad96957c10388d2d2acc7fdd8e0fab84' # diffusers commit hash
# if args.use_rocm or args.use_zluda or args.use_directml:
# sha = '043ab2520f6a19fce78e6e060a68dbc947edb9f9' # lock diffusers versions for now
pkg = package_spec('diffusers')
@@ -927,13 +936,10 @@ def run_extension_installer(folder):
if os.environ.get('PYTHONPATH', None) is not None:
seperator = ';' if sys.platform == 'win32' else ':'
env['PYTHONPATH'] += seperator + os.environ.get('PYTHONPATH', None)
- result = subprocess.run(f'"{sys.executable}" "{path_installer}"', shell=True, env=env, check=False, capture_output=True, cwd=folder)
- txt = result.stdout.decode(encoding="utf8", errors="ignore")
- debug(f'Extension installer: file="{path_installer}" {txt}')
+ result, txt = run(sys.executable, path_installer, env=env, cwd=folder)
+ debug(f'Extension installer: file="{path_installer}" {result.stdout}')
if result.returncode != 0:
errors.append(f'ext: {os.path.basename(folder)}')
- if len(result.stderr) > 0:
- txt = txt + '\n' + result.stderr.decode(encoding="utf8", errors="ignore")
log.error(f'Extension installer error: {path_installer}')
log.debug(txt)
except Exception as e:
@@ -1264,29 +1270,24 @@ def get_version(force=False):
except Exception:
pass
try:
- res = subprocess.run('git log --pretty=format:"%h %ad" -1 --date=short', capture_output=True, shell=True, check=True)
- ver = res.stdout.decode(encoding = 'utf8', errors='ignore') if len(res.stdout) > 0 else ' '
+ ver = run('git', 'log --pretty=format:"%h %ad" -1 --date=short', check=True)[0].stdout or ' '
commit, updated = ver.split(' ')
version['commit'], version['updated'] = commit, updated
except Exception as e:
log.warning(f'Version: where=commit {e}')
try:
- res = subprocess.run('git remote get-url origin', capture_output=True, shell=True, check=True)
- origin = res.stdout.decode(encoding = 'utf8', errors='ignore') if len(res.stdout) > 0 else ''
- res = subprocess.run('git rev-parse --abbrev-ref HEAD', capture_output=True, shell=True, check=True)
- branch_name = res.stdout.decode(encoding = 'utf8', errors='ignore') if len(res.stdout) > 0 else ''
- version['url'] = origin.replace('\n', '').removesuffix('.git') + '/tree/' + branch_name.replace('\n', '')
- version['branch'] = branch_name.replace('\n', '')
+ origin = run('git', 'remote get-url origin', check=True)[0].stdout
+ branch_name = run('git', 'rev-parse --abbrev-ref HEAD', check=True)[0].stdout
+ version['url'] = origin.removesuffix('.git') + '/tree/' + branch_name
+ version['branch'] = branch_name
if version['branch'] == 'HEAD':
log.warning('Version: detached state detected')
except Exception as e:
log.warning(f'Version: where=branch {e}')
try:
if os.path.exists('extensions-builtin/sdnext-modernui'):
- res = subprocess.run('git rev-parse --abbrev-ref HEAD', capture_output=True, shell=True, check=True, cwd='extensions-builtin/sdnext-modernui')
- branch_ui = res.stdout.decode(encoding = 'utf8', errors='ignore') if len(res.stdout) > 0 else ''
- branch_ui = 'dev' if 'dev' in branch_ui else 'main'
- version['ui'] = branch_ui
+ branch_ui = run('git', 'rev-parse --abbrev-ref HEAD', check=True, cwd='extensions-builtin/sdnext-modernui')[0].stdout
+ version['ui'] = 'dev' if 'dev' in branch_ui else 'main'
else:
version['ui'] = 'unavailable'
except Exception as e:
@@ -1296,10 +1297,8 @@ def get_version(force=False):
if os.environ.get('SD_KANVAS_DISABLE', None) is not None:
version['kanvas'] = 'disabled'
elif os.path.exists('extensions-builtin/sdnext-kanvas'):
- res = subprocess.run('git rev-parse --abbrev-ref HEAD', capture_output=True, shell=True, check=True, cwd='extensions-builtin/sdnext-kanvas')
- branch_kanvas = res.stdout.decode(encoding = 'utf8', errors='ignore') if len(res.stdout) > 0 else ''
- branch_kanvas = 'dev' if 'dev' in branch_kanvas else 'main'
- version['kanvas'] = branch_kanvas
+ branch_kanvas = run('git', 'rev-parse --abbrev-ref HEAD', check=True, cwd='extensions-builtin/sdnext-kanvas')[0].stdout
+ version['kanvas'] = 'dev' if 'dev' in branch_kanvas else 'main'
else:
version['kanvas'] = 'unavailable'
except Exception as e:
@@ -1487,8 +1486,7 @@ def get_state():
def _get_commit(item):
ext, ext_dir = item
try:
- res = subprocess.run('git rev-parse HEAD', capture_output=True, shell=True, check=False, cwd=ext_dir)
- return ext, res.stdout.decode(encoding='utf8', errors='ignore').strip()
+ return ext, run('git', 'rev-parse HEAD', cwd=ext_dir)[0].stdout
except Exception:
return ext, ''
diff --git a/launch.py b/launch.py
index 876d66391..5cb423ea0 100755
--- a/launch.py
+++ b/launch.py
@@ -97,13 +97,13 @@ def run(command, desc=None, errdesc=None, custom_env=None, live=False): # compat
if result.returncode != 0:
raise RuntimeError(f"""{errdesc or 'Error running command'} Command: {command} Error code: {result.returncode}""")
return ''
- result = subprocess.run(command, capture_output=True, check=False, shell=True, env=os.environ if custom_env is None else custom_env)
+ result = subprocess.run(command, capture_output=True, check=False, shell=True, env=os.environ if custom_env is None else custom_env, text=True)
if result.returncode != 0:
raise RuntimeError(f"""{errdesc or 'Error running command'}: {command} code: {result.returncode}
-{result.stdout.decode(encoding="utf8", errors="ignore") if len(result.stdout)>0 else ''}
-{result.stderr.decode(encoding="utf8", errors="ignore") if len(result.stderr)>0 else ''}
+{result.stdout}
+{result.stderr}
""")
- return result.stdout.decode(encoding="utf8", errors="ignore")
+ return result.stdout
def check_run(command): # compatbility function
diff --git a/modules/caption/deepbooru.py b/modules/caption/deepbooru.py
index 19ff58afc..6ccb848b1 100644
--- a/modules/caption/deepbooru.py
+++ b/modules/caption/deepbooru.py
@@ -205,6 +205,7 @@ def batch(
Returns:
Combined tag results
"""
+ import os
import time
from pathlib import Path
import rich.progress as rp
@@ -214,55 +215,15 @@ def batch(
# Collect image files
image_files = []
- image_extensions = {'.jpg', '.jpeg', '.png', '.webp', '.bmp', '.gif'}
-
- # From file picker
- if batch_files:
- for f in batch_files:
- if isinstance(f, dict):
- image_files.append(Path(f['name']))
- elif hasattr(f, 'name'):
- image_files.append(Path(f.name))
- else:
- image_files.append(Path(f))
-
- # From folder picker
- if batch_folder:
- folder_path = None
- if isinstance(batch_folder, list) and len(batch_folder) > 0:
- f = batch_folder[0]
- if isinstance(f, dict):
- folder_path = Path(f['name']).parent
- elif hasattr(f, 'name'):
- folder_path = Path(f.name).parent
- if folder_path and folder_path.is_dir():
- if recursive:
- for ext in image_extensions:
- image_files.extend(folder_path.rglob(f'*{ext}'))
- else:
- for ext in image_extensions:
- image_files.extend(folder_path.glob(f'*{ext}'))
-
- # From string path
- if batch_str and batch_str.strip():
+ if batch_files is not None:
+ image_files += [f.name for f in batch_files]
+ if batch_folder is not None:
+ image_files += [f.name for f in batch_folder]
+ if batch_str is not None and len(batch_str) > 0 and os.path.exists(batch_str) and os.path.isdir(batch_str):
+ image_extensions = {'.jpg', '.jpeg', '.png', '.webp', '.bmp', '.gif'}
folder_path = Path(batch_str.strip())
- if folder_path.is_dir():
- if recursive:
- for ext in image_extensions:
- image_files.extend(folder_path.rglob(f'*{ext}'))
- else:
- for ext in image_extensions:
- image_files.extend(folder_path.glob(f'*{ext}'))
-
- # Remove duplicates while preserving order
- seen = set()
- unique_files = []
- for f in image_files:
- f_resolved = f.resolve()
- if f_resolved not in seen:
- seen.add(f_resolved)
- unique_files.append(f)
- image_files = unique_files
+ for ext in image_extensions:
+ image_files.extend(str(p) for p in (folder_path.rglob(f'*{ext}') if recursive else folder_path.glob(f'*{ext}')))
if not image_files:
log.warning('DeepBooru batch: no images found')
@@ -280,25 +241,26 @@ def batch(
with pbar:
task = pbar.add_task(total=len(image_files), description='starting...')
- for img_path in image_files:
- pbar.update(task, advance=1, description=str(img_path.name))
+ for file in image_files:
+ file_name = os.path.basename(file)
+ pbar.update(task, advance=1, description=file_name)
try:
if shared.state.interrupted:
log.info('DeepBooru batch: interrupted')
break
- image = Image.open(img_path)
+ image = Image.open(file)
tags_str = model.tag_multi(image, **kwargs)
if save_output:
from modules.caption import tagger
- tagger.save_tags_to_file(img_path, tags_str, save_append)
+ tagger.save_tags_to_file(Path(file), tags_str, save_append)
- results.append(f'{img_path.name}: {tags_str[:100]}...' if len(tags_str) > 100 else f'{img_path.name}: {tags_str}')
+ results.append(f'{file_name}: {tags_str[:100]}...' if len(tags_str) > 100 else f'{file_name}: {tags_str}')
except Exception as e:
- log.error(f'DeepBooru batch: file="{img_path}" error={e}')
- results.append(f'{img_path.name}: ERROR - {e}')
+ log.error(f'DeepBooru batch: file="{file}" error={e}')
+ results.append(f'{file_name}: ERROR - {e}')
model.stop()
elapsed = time.time() - t0
diff --git a/modules/caption/waifudiffusion.py b/modules/caption/waifudiffusion.py
index ba7077dac..3e284db75 100644
--- a/modules/caption/waifudiffusion.py
+++ b/modules/caption/waifudiffusion.py
@@ -419,6 +419,7 @@ def batch(
Returns:
Combined tag results
"""
+ import os
from pathlib import Path
# Load model
@@ -429,55 +430,15 @@ def batch(
# Collect image files
image_files = []
- image_extensions = {'.jpg', '.jpeg', '.png', '.webp', '.bmp', '.gif'}
-
- # From file picker
- if batch_files:
- for f in batch_files:
- if isinstance(f, dict):
- image_files.append(Path(f['name']))
- elif hasattr(f, 'name'):
- image_files.append(Path(f.name))
- else:
- image_files.append(Path(f))
-
- # From folder picker
- if batch_folder:
- folder_path = None
- if isinstance(batch_folder, list) and len(batch_folder) > 0:
- f = batch_folder[0]
- if isinstance(f, dict):
- folder_path = Path(f['name']).parent
- elif hasattr(f, 'name'):
- folder_path = Path(f.name).parent
- if folder_path and folder_path.is_dir():
- if recursive:
- for ext in image_extensions:
- image_files.extend(folder_path.rglob(f'*{ext}'))
- else:
- for ext in image_extensions:
- image_files.extend(folder_path.glob(f'*{ext}'))
-
- # From string path
- if batch_str and batch_str.strip():
+ if batch_files is not None:
+ image_files += [f.name for f in batch_files]
+ if batch_folder is not None:
+ image_files += [f.name for f in batch_folder]
+ if batch_str is not None and len(batch_str) > 0 and os.path.exists(batch_str) and os.path.isdir(batch_str):
+ image_extensions = {'.jpg', '.jpeg', '.png', '.webp', '.bmp', '.gif'}
folder_path = Path(batch_str.strip())
- if folder_path.is_dir():
- if recursive:
- for ext in image_extensions:
- image_files.extend(folder_path.rglob(f'*{ext}'))
- else:
- for ext in image_extensions:
- image_files.extend(folder_path.glob(f'*{ext}'))
-
- # Remove duplicates while preserving order
- seen = set()
- unique_files = []
- for f in image_files:
- f_resolved = f.resolve()
- if f_resolved not in seen:
- seen.add(f_resolved)
- unique_files.append(f)
- image_files = unique_files
+ for ext in image_extensions:
+ image_files.extend(str(p) for p in (folder_path.rglob(f'*{ext}') if recursive else folder_path.glob(f'*{ext}')))
if not image_files:
log.warning('WaifuDiffusion batch: no images found')
@@ -496,25 +457,26 @@ def batch(
with pbar:
task = pbar.add_task(total=len(image_files), description='starting...')
- for img_path in image_files:
- pbar.update(task, advance=1, description=str(img_path.name))
+ for file in image_files:
+ file_name = os.path.basename(file)
+ pbar.update(task, advance=1, description=file_name)
try:
if shared.state.interrupted:
log.info('WaifuDiffusion batch: interrupted')
break
- image = Image.open(img_path)
+ image = Image.open(file)
tags_str = tagger.predict(image, **kwargs)
if save_output:
from modules.caption import tagger as tagger_module
- tagger_module.save_tags_to_file(img_path, tags_str, save_append)
+ tagger_module.save_tags_to_file(Path(file), tags_str, save_append)
- results.append(f'{img_path.name}: {tags_str[:100]}...' if len(tags_str) > 100 else f'{img_path.name}: {tags_str}')
+ results.append(f'{file_name}: {tags_str[:100]}...' if len(tags_str) > 100 else f'{file_name}: {tags_str}')
except Exception as e:
- log.error(f'WaifuDiffusion batch: file="{img_path}" error={e}')
- results.append(f'{img_path.name}: ERROR - {e}')
+ log.error(f'WaifuDiffusion batch: file="{file}" error={e}')
+ results.append(f'{file_name}: ERROR - {e}')
elapsed = time.time() - t0
log.info(f'WaifuDiffusion batch: complete images={len(results)} time={elapsed:.1f}s')
diff --git a/modules/devices.py b/modules/devices.py
index 99276e30e..90f86ffcb 100644
--- a/modules/devices.py
+++ b/modules/devices.py
@@ -104,8 +104,8 @@ def get_gpu_info():
elif torch.cuda.is_available() and torch.version.cuda:
try:
import subprocess
- result = subprocess.run('nvidia-smi --query-gpu=driver_version --format=csv,noheader', shell=True, check=False, env=os.environ, capture_output=True)
- version = result.stdout.decode(encoding="utf8", errors="ignore").strip()
+ result = subprocess.run('nvidia-smi --query-gpu=driver_version --format=csv,noheader', shell=True, check=False, env=os.environ, capture_output=True, text=True)
+ version = result.stdout.strip()
return version
except Exception:
return ''
diff --git a/modules/rocm.py b/modules/rocm.py
index 7295b4a52..576686d18 100644
--- a/modules/rocm.py
+++ b/modules/rocm.py
@@ -31,8 +31,8 @@ def dirname(path_: str, r: int = 1) -> str:
def spawn(command: str | list[str], cwd: os.PathLike = '.') -> str:
- process = subprocess.run(command, cwd=cwd, shell=True, check=False, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
- return process.stdout.decode(encoding="utf8", errors="ignore")
+ process = subprocess.run(command, cwd=cwd, shell=True, check=False, capture_output=True, text=True)
+ return process.stdout
def load_library_global(path_: str):
diff --git a/modules/ui_caption.py b/modules/ui_caption.py
index 33ee8132f..ceb2c2d31 100644
--- a/modules/ui_caption.py
+++ b/modules/ui_caption.py
@@ -270,6 +270,7 @@ def create_ui():
with gr.Row():
wd_show_scores = gr.Checkbox(label='Show confidence scores', value=shared.opts.tagger_show_scores, elem_id='wd_show_scores')
gr.HTML('')
+ gr.HTML('')
with gr.Accordion(label='Caption: Batch', open=False, visible=True):
with gr.Row():
wd_batch_files = gr.File(label="Files", show_label=True, file_count='multiple', file_types=['image'], interactive=True, height=100, elem_id='wd_batch_files')
diff --git a/modules/ui_common.py b/modules/ui_common.py
index 48c399a9e..3b7530e08 100644
--- a/modules/ui_common.py
+++ b/modules/ui_common.py
@@ -249,12 +249,14 @@ def open_folder(result_gallery, gallery_index = 0):
path = os.path.normpath(folder)
if platform.system() == "Windows":
os.startfile(path) # pylint: disable=no-member
- elif platform.system() == "Darwin":
- subprocess.Popen(["open", path]) # pylint: disable=consider-using-with
+ return
+ if platform.system() == "Darwin":
+ opener = "open"
elif "microsoft-standard-WSL2" in platform.uname().release:
- subprocess.Popen(["wsl-open", path]) # pylint: disable=consider-using-with
+ opener = "wslview" if shutil.which("wslview") is not None else "wsl-open"
else:
- subprocess.Popen(["xdg-open", path]) # pylint: disable=consider-using-with
+ opener = "xdg-open"
+ subprocess.Popen([opener, path]) # pylint: disable=consider-using-with
def create_output_panel(tabname, preview=True, prompt=None, height=None, transfer=True, scale=1, result_info=None):