diff --git a/CHANGES.md b/CHANGES.md
index 201a0249..bde70f1e 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -21,16 +21,19 @@
Our focus continues to remain on an easy installation experience, and an easy user-interface. While still remaining pretty powerful, in terms of features and speed.
### Detailed changelog
-* 2.5.30 - 1 Apr 2023 - Slider to control the strength of the LoRA model.
+* 2.5.31 - 6 Apr 2023 - Allow seeds upto `4,294,967,295`. Thanks @ogmaresca.
+* 2.5.31 - 6 Apr 2023 - Buttons to show the previous/next image in the image popup. Thanks @ogmaresca.
+* 2.5.30 - 5 Apr 2023 - Fix a bug where the JPEG image quality wasn't being respected when embedding the metadata into it. Thanks @JeLuf.
+* 2.5.30 - 1 Apr 2023 - (beta-only) Slider to control the strength of the LoRA model.
* 2.5.30 - 28 Mar 2023 - Refactor task entry config to use a generating method. Added ability for plugins to easily add to this. Removed confusing sentence from `contributing.md`
* 2.5.30 - 28 Mar 2023 - Allow the user to undo the deletion of tasks or images, instead of showing a pop-up each time. The new `Undo` button will be present at the top of the UI. Thanks @JeLuf.
* 2.5.30 - 28 Mar 2023 - Support saving lossless WEBP images. Thanks @ogmaresca.
* 2.5.30 - 28 Mar 2023 - Lots of bug fixes for the UI (Read LoRA flag in metadata files, new prompt weight format with scrollwheel, fix overflow with lots of tabs, clear button in image editor, shorter filenames in download). Thanks @patriceac, @JeLuf and @ogmaresca.
-* 2.5.29 - 27 Mar 2023 - Fix a bug where some non-square images would fail while inpainting with a `The size of tensor a must match size of tensor b` error.
-* 2.5.29 - 27 Mar 2023 - Fix the `incorrect number of channels` error, when given a PNG image with an alpha channel in `Test Diffusers`.
-* 2.5.29 - 27 Mar 2023 - Fix broken inpainting in `Test Diffusers` (beta).
-* 2.5.28 - 24 Mar 2023 - Support for weighted prompts and long prompt lengths (not limited to 77 tokens). This change requires enabling the `Test Diffusers` setting in beta (in the Settings tab), and restarting the program.
-* 2.5.27 - 21 Mar 2023 - LoRA support, accessible by enabling the `Test Diffusers` setting (in the Settings tab in the UI). This change switches the internal engine to diffusers (if the `Test Diffusers` setting is enabled). If the `Test Diffusers` flag is disabled, it'll have no impact for the user.
+* 2.5.29 - 27 Mar 2023 - (beta-only) Fix a bug where some non-square images would fail while inpainting with a `The size of tensor a must match size of tensor b` error.
+* 2.5.29 - 27 Mar 2023 - (beta-only) Fix the `incorrect number of channels` error, when given a PNG image with an alpha channel in `Test Diffusers`.
+* 2.5.29 - 27 Mar 2023 - (beta-only) Fix broken inpainting in `Test Diffusers`.
+* 2.5.28 - 24 Mar 2023 - (beta-only) Support for weighted prompts and long prompt lengths (not limited to 77 tokens). This change requires enabling the `Test Diffusers` setting in beta (in the Settings tab), and restarting the program.
+* 2.5.27 - 21 Mar 2023 - (beta-only) LoRA support, accessible by enabling the `Test Diffusers` setting (in the Settings tab in the UI). This change switches the internal engine to diffusers (if the `Test Diffusers` setting is enabled). If the `Test Diffusers` flag is disabled, it'll have no impact for the user.
* 2.5.26 - 15 Mar 2023 - Allow styling the buttons displayed on an image. Update the API to allow multiple buttons and text labels in a single row. Thanks @ogmaresca.
* 2.5.26 - 15 Mar 2023 - View images in full-screen, by either clicking on the image, or clicking the "Full screen" icon next to the Seed number on the image. Thanks @ogmaresca for the internal API.
* 2.5.25 - 14 Mar 2023 - Button to download all the images, and all the metadata as a zip file. This is available at the top of the UI, as well as on each image. Thanks @JeLuf.
diff --git a/scripts/functions.sh b/scripts/functions.sh
index 5b1be7f4..495e9950 100644
--- a/scripts/functions.sh
+++ b/scripts/functions.sh
@@ -31,7 +31,7 @@ EOF
filesize() {
case "$(uname -s)" in
Linux*) stat -c "%s" $1;;
- Darwin*) stat -f "%z" $1;;
+ Darwin*) /usr/bin/stat -f "%z" $1;;
*) echo "Unknown OS: $OS_NAME! This script runs only on Linux or Mac" && exit
esac
}
diff --git a/scripts/on_sd_start.bat b/scripts/on_sd_start.bat
index d04eb9bb..c9b3235f 100644
--- a/scripts/on_sd_start.bat
+++ b/scripts/on_sd_start.bat
@@ -95,7 +95,7 @@ if "%ERRORLEVEL%" EQU "0" (
set PYTHONNOUSERSITE=1
set PYTHONPATH=%INSTALL_ENV_DIR%\lib\site-packages
- call python -m pip install --upgrade sdkit==1.0.62 -q || (
+ call python -m pip install --upgrade sdkit==1.0.64 -q || (
echo "Error updating sdkit"
)
)
@@ -106,7 +106,7 @@ if "%ERRORLEVEL%" EQU "0" (
set PYTHONNOUSERSITE=1
set PYTHONPATH=%INSTALL_ENV_DIR%\lib\site-packages
- call python -m pip install sdkit==1.0.62 || (
+ call python -m pip install sdkit==1.0.64 || (
echo "Error installing sdkit. Sorry about that, please try to:" & echo " 1. Run this installer again." & echo " 2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/cmdr2/stable-diffusion-ui/wiki/Troubleshooting" & echo " 3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB" & echo " 4. If that doesn't solve the problem, please file an issue at https://github.com/cmdr2/stable-diffusion-ui/issues" & echo "Thanks!"
pause
exit /b
diff --git a/scripts/on_sd_start.sh b/scripts/on_sd_start.sh
index 80a61b8e..78bcf0ee 100755
--- a/scripts/on_sd_start.sh
+++ b/scripts/on_sd_start.sh
@@ -103,7 +103,7 @@ if python ../scripts/check_modules.py sdkit sdkit.models ldm transformers numpy
export PYTHONNOUSERSITE=1
export PYTHONPATH="$INSTALL_ENV_DIR/lib/python3.8/site-packages"
- python -m pip install --upgrade sdkit==1.0.62 -q
+ python -m pip install --upgrade sdkit==1.0.64 -q
fi
else
echo "Installing sdkit: https://pypi.org/project/sdkit/"
@@ -111,7 +111,7 @@ else
export PYTHONNOUSERSITE=1
export PYTHONPATH="$INSTALL_ENV_DIR/lib/python3.8/site-packages"
- if python -m pip install sdkit==1.0.62 ; then
+ if python -m pip install sdkit==1.0.64 ; then
echo "Installed."
else
fail "sdkit install failed"
diff --git a/ui/easydiffusion/server.py b/ui/easydiffusion/server.py
index e27f9c5b..92453917 100644
--- a/ui/easydiffusion/server.py
+++ b/ui/easydiffusion/server.py
@@ -10,7 +10,7 @@ from typing import List, Union
from fastapi import FastAPI, HTTPException
from fastapi.staticfiles import StaticFiles
from starlette.responses import FileResponse, JSONResponse, StreamingResponse
-from pydantic import BaseModel
+from pydantic import BaseModel, Extra
from easydiffusion import app, model_manager, task_manager
from easydiffusion.types import TaskData, GenerateImageRequest, MergeRequest
@@ -44,7 +44,7 @@ class NoCacheStaticFiles(StaticFiles):
return super().is_not_modified(response_headers, request_headers)
-class SetAppConfigRequest(BaseModel):
+class SetAppConfigRequest(BaseModel, extra=Extra.allow):
update_branch: str = None
render_devices: Union[List[str], List[int], str, int] = None
model_vae: str = None
@@ -136,6 +136,10 @@ def set_app_config_internal(req: SetAppConfigRequest):
config["test_diffusers"] = req.test_diffusers
+ for property, property_value in req.dict().items():
+ if property_value is not None and property not in req.__fields__:
+ config[property] = property_value
+
try:
app.setConfig(config)
diff --git a/ui/easydiffusion/utils/save_utils.py b/ui/easydiffusion/utils/save_utils.py
index 950f04b0..71524cbd 100644
--- a/ui/easydiffusion/utils/save_utils.py
+++ b/ui/easydiffusion/utils/save_utils.py
@@ -1,13 +1,18 @@
import os
import time
+import base64
import re
+from easydiffusion import app
from easydiffusion.types import TaskData, GenerateImageRequest
+from functools import reduce
+from datetime import datetime
from sdkit.utils import save_images, save_dicts
from numpy import base_repr
filename_regex = re.compile("[^a-zA-Z0-9._-]")
+img_number_regex = re.compile("([0-9]{5,})")
# keep in sync with `ui/media/js/dnd.js`
TASK_TEXT_MAPPING = {
@@ -28,15 +33,89 @@ TASK_TEXT_MAPPING = {
"use_hypernetwork_model": "Hypernetwork model",
"hypernetwork_strength": "Hypernetwork Strength",
"use_lora_model": "LoRA model",
- # "lora_alpha": "LoRA Strength",
+ "lora_alpha": "LoRA Strength",
}
+time_placeholders = {
+ "$yyyy": "%Y",
+ "$MM": "%m",
+ "$dd": "%d",
+ "$HH": "%H",
+ "$mm": "%M",
+ "$ss": "%S",
+}
+
+other_placeholders = {
+ "$id": lambda req, task_data: filename_regex.sub("_", task_data.session_id),
+ "$p": lambda req, task_data: filename_regex.sub("_", req.prompt)[:50],
+ "$s": lambda req, task_data: str(req.seed),
+}
+
+class ImageNumber:
+ _factory = None
+ _evaluated = False
+
+ def __init__(self, factory):
+ self._factory = factory
+ self._evaluated = None
+ def __call__(self) -> int:
+ if self._evaluated is None:
+ self._evaluated = self._factory()
+ return self._evaluated
+
+def format_placeholders(format: str, req: GenerateImageRequest, task_data: TaskData, now = None):
+ if now is None:
+ now = time.time()
+
+ for placeholder, time_format in time_placeholders.items():
+ if placeholder in format:
+ format = format.replace(placeholder, datetime.fromtimestamp(now).strftime(time_format))
+ for placeholder, replace_func in other_placeholders.items():
+ if placeholder in format:
+ format = format.replace(placeholder, replace_func(req, task_data))
+
+ return format
+
+def format_folder_name(format: str, req: GenerateImageRequest, task_data: TaskData):
+ format = format_placeholders(format, req, task_data)
+ return filename_regex.sub("_", format)
+
+def format_file_name(
+ format: str,
+ req: GenerateImageRequest,
+ task_data: TaskData,
+ now: float,
+ batch_file_number: int,
+ folder_img_number: ImageNumber,
+):
+ format = format_placeholders(format, req, task_data, now)
+
+ if "$n" in format:
+ format = format.replace("$n", f"{folder_img_number():05}")
+
+ if "$tsb64" in format:
+ img_id = base_repr(int(now * 10000), 36)[-7:] + base_repr(int(batch_file_number), 36) # Base 36 conversion, 0-9, A-Z
+ format = format.replace("$tsb64", img_id)
+
+ if "$ts" in format:
+ format = format.replace("$ts", str(int(now * 1000) + batch_file_number))
+
+ return filename_regex.sub("_", format)
def save_images_to_disk(images: list, filtered_images: list, req: GenerateImageRequest, task_data: TaskData):
now = time.time()
- save_dir_path = os.path.join(task_data.save_to_disk_path, filename_regex.sub("_", task_data.session_id))
+ app_config = app.getConfig()
+ folder_format = app_config.get("folder_format", "$id")
+ save_dir_path = os.path.join(task_data.save_to_disk_path, format_folder_name(folder_format, req, task_data))
metadata_entries = get_metadata_entries_for_request(req, task_data)
- make_filename = make_filename_callback(req, now=now)
+ file_number = calculate_img_number(save_dir_path, task_data)
+ make_filename = make_filename_callback(
+ app_config.get("filename_format", "$p_$tsb64"),
+ req,
+ task_data,
+ file_number,
+ now=now,
+ )
if task_data.show_only_filtered_image or filtered_images is images:
save_images(
@@ -58,7 +137,7 @@ def save_images_to_disk(images: list, filtered_images: list, req: GenerateImageR
file_format=task_data.output_format,
)
else:
- make_filter_filename = make_filename_callback(req, now=now, suffix="filtered")
+ make_filter_filename = make_filename_callback(req, task_data, file_number, now=now, suffix="filtered")
save_images(
images,
@@ -105,9 +184,6 @@ def get_metadata_entries_for_request(req: GenerateImageRequest, task_data: TaskD
if task_data.use_lora_model is None:
if "lora_alpha" in metadata:
del metadata["lora_alpha"]
-
- from easydiffusion import app
-
app_config = app.getConfig()
if not app_config.get("test_diffusers", False) and "use_lora_model" in metadata:
del metadata["use_lora_model"]
@@ -133,16 +209,66 @@ def get_printable_request(req: GenerateImageRequest):
return metadata
-def make_filename_callback(req: GenerateImageRequest, suffix=None, now=None):
+def make_filename_callback(
+ filename_format: str,
+ req: GenerateImageRequest,
+ task_data: TaskData,
+ folder_img_number: int,
+ suffix=None,
+ now=None,
+):
if now is None:
now = time.time()
def make_filename(i):
- img_id = base_repr(int(now * 10000), 36)[-7:] + base_repr(int(i),36) # Base 36 conversion, 0-9, A-Z
-
- prompt_flattened = filename_regex.sub("_", req.prompt)[:50]
- name = f"{prompt_flattened}_{img_id}"
+ name = format_file_name(filename_format, req, task_data, now, i, folder_img_number)
name = name if suffix is None else f"{name}_{suffix}"
+
return name
return make_filename
+
+def _calculate_img_number(save_dir_path: str, task_data: TaskData):
+ def get_highest_img_number(accumulator: int, file: os.DirEntry) -> int:
+ if not file.is_file:
+ return accumulator
+
+ if len(list(filter(lambda e: file.name.endswith(e), app.IMAGE_EXTENSIONS))) == 0:
+ return accumulator
+
+ get_highest_img_number.number_of_images = get_highest_img_number.number_of_images + 1
+
+ number_match = img_number_regex.match(file.name)
+ if not number_match:
+ return accumulator
+
+ file_number = number_match.group().lstrip('0')
+
+ # Handle 00000
+ return int(file_number) if file_number else 0
+
+ get_highest_img_number.number_of_images = 0
+
+ highest_file_number = -1
+
+ if os.path.isdir(save_dir_path):
+ existing_files = list(os.scandir(save_dir_path))
+ highest_file_number = reduce(get_highest_img_number, existing_files, -1)
+
+ calculated_img_number = max(highest_file_number, get_highest_img_number.number_of_images - 1)
+
+ if task_data.session_id in _calculate_img_number.session_img_numbers:
+ calculated_img_number = max(
+ _calculate_img_number.session_img_numbers[task_data.session_id],
+ calculated_img_number,
+ )
+
+ calculated_img_number = calculated_img_number + 1
+
+ _calculate_img_number.session_img_numbers[task_data.session_id] = calculated_img_number
+ return calculated_img_number
+
+_calculate_img_number.session_img_numbers = {}
+
+def calculate_img_number(save_dir_path: str, task_data: TaskData):
+ return ImageNumber(lambda: _calculate_img_number(save_dir_path, task_data))
diff --git a/ui/index.html b/ui/index.html
index fed2b3b9..3a248ea7 100644
--- a/ui/index.html
+++ b/ui/index.html
@@ -30,7 +30,7 @@
Easy Diffusion
- v2.5.30
+ v2.5.31
@@ -221,7 +221,7 @@
-
+
@@ -293,6 +293,12 @@
+
+ Type a prompt and press the "Make Image" button.
You can set an "Initial Image" if you want to guide the AI.
+ You can also add modifiers like "Realistic", "Pencil Sketch", "ArtStation" etc by browsing through the "Image Modifiers" section
+ and selecting the desired modifiers.
+ Click "Image Settings" for additional settings like seed, image size, number of images to generate etc.
Enjoy! :)
+
@@ -326,12 +332,6 @@
-
- Type a prompt and press the "Make Image" button.
You can set an "Initial Image" if you want to guide the AI.
- You can also add modifiers like "Realistic", "Pencil Sketch", "ArtStation" etc by browsing through the "Image Modifiers" section
- and selecting the desired modifiers.
- Click "Image Settings" for additional settings like seed, image size, number of images to generate etc.