diff --git a/3rd-PARTY-LICENSES b/3rd-PARTY-LICENSES index 05ba4541..93f533ea 100644 --- a/3rd-PARTY-LICENSES +++ b/3rd-PARTY-LICENSES @@ -1120,3 +1120,29 @@ ExifReader is licensed under the Mozilla Public License: This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. +Masonry +======= +https://masonry.desandro.com/ + +Masonry is licensed under the MIT license: + + The MIT License (MIT) + + Copyright © 2023 David DeSandro + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the “Software”), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is furnished + to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 281b4da4..ec46dea7 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,9 @@ Does not require technical knowledge, does not require pre-installed software. 1 Click the download button for your operating system:

- - - + + +

**Hardware requirements:** diff --git a/ui/easydiffusion/bucket_manager.py b/ui/easydiffusion/bucket_manager.py index c4f4c7e1..c06974bd 100644 --- a/ui/easydiffusion/bucket_manager.py +++ b/ui/easydiffusion/bucket_manager.py @@ -1,12 +1,14 @@ from typing import List from fastapi import Depends, FastAPI, HTTPException, Response, File +from fastapi.responses import FileResponse from sqlalchemy.orm import Session from easydiffusion.easydb import crud, models, schemas from easydiffusion.easydb.database import SessionLocal, engine from requests.compat import urlparse +from os.path import abspath import base64, json @@ -92,7 +94,28 @@ def init(): result.data = base64.encodestring(result.data) return result + @server_api.get("/image/{image_path:path}") + def get_image(image_path: str, db: Session = Depends(get_db)): + from easydiffusion.easydb.mappings import GalleryImage + image_path = str(abspath(image_path)) + try: + image = db.query(GalleryImage).filter(GalleryImage.path == image_path).first() + return FileResponse(image.path) + except Exception as e: + raise HTTPException(status_code=404, detail="Image not found") + + @server_api.get("/all_images") + def get_all_images(prompt: str = "", model: str = "", page: int = 0, images_per_page: int = 50, workspace : str = "default", db: Session = Depends(get_db)): + from easydiffusion.easydb.mappings import GalleryImage + images = db.query(GalleryImage).filter(GalleryImage.workspace == workspace).order_by(GalleryImage.time_created.desc()) + if prompt != "": + images = images.filter(GalleryImage.prompt.like("%"+prompt+"%")) + if model != "": + images = images.filter(GalleryImage.use_stable_diffusion_model.like("%"+model+"%")) + images = images.offset(page*images_per_page).limit(images_per_page) + return images.all() + def get_filename_from_url(url): path = urlparse(url).path name = path[path.rfind('/')+1:] diff --git a/ui/easydiffusion/easydb/mappings.py b/ui/easydiffusion/easydb/mappings.py new file mode 100644 index 00000000..9a01f0c9 --- /dev/null +++ b/ui/easydiffusion/easydb/mappings.py @@ -0,0 +1,82 @@ +from sqlalchemy import Column, Integer, String, Float, Boolean, DateTime +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.sql import func + +Base = declarative_base() + +class GalleryImage(Base): + __tablename__ = 'images' + + path = Column(String, primary_key=True) + seed = Column(Integer) + use_stable_diffusion_model = Column(String) + clip_skip = Column(Boolean) + use_vae_model = Column(String) + sampler_name = Column(String) + width = Column(Integer) + height = Column(Integer) + num_inference_steps = Column(Integer) + guidance_scale = Column(Float) + lora = Column(String) + use_hypernetwork_model = Column(String) + tiling = Column(String) + use_face_correction = Column(String) + use_upscale = Column(String) + prompt = Column(String) + negative_prompt = Column(String) + workspace = Column(String, server_default="default") + time_created = Column(DateTime(timezone=True), server_default=func.now()) + nsfw = Column(Boolean, server_default=None) + + def __repr__(self): + return "" % ( + self.path, self.seed, self.use_stable_diffusion_model, self.clip_skip, self.use_vae_model, self.sampler_name, self.width, self.height, self.num_inference_steps, self.guidance_scale, self.lora, self.use_hypernetwork_model, self.tiling, self.use_face_correction, self.use_upscale, self.prompt, self.negative_prompt) + + def htmlForm(self) -> str: + return "

Path: " + str(self.path) + "

" + \ + "

Seed: " + str(self.seed) + "

" + \ + "

Stable Diffusion Model: " + str(self.use_stable_diffusion_model) + "

" + \ + "

Prompt: " + str(self.prompt) + "

" + \ + "

Negative Prompt: " + str(self.negative_prompt) + "

" + \ + "

Clip Skip: " + str(self.clip_skip) + "

" + \ + "

VAE Model: " + str(self.use_vae_model) + "

" + \ + "

Sampler: " + str(self.sampler_name) + "

" + \ + "

Size: " + str(self.height) + "x" + str(self.width) + "

" + \ + "

Inference Steps: " + str(self.num_inference_steps) + "

" + \ + "

Guidance Scale: " + str(self.guidance_scale) + "

" + \ + "

LoRA: " + str(self.lora) + "

" + \ + "

Hypernetwork: " + str(self.use_hypernetwork_model) + "

" + \ + "

Tiling: " + str(self.tiling) + "

" + \ + "

Face Correction: " + str(self.use_face_correction) + "

" + \ + "

Upscale: " + str(self.use_upscale) + "

" + \ + "

Time Created: " + str(self.time_created) + "

" + \ + "

NSFW: " + str(self.nsfw) + "

" + + def settingsJSON(self) -> str: + # some are still missing: prompt strength, lora + json = { + "numOutputsTotal": 1, + "seed": self.seed, + "reqBody": { + "prompt": self.prompt, + "negative_prompt": self.negative_prompt, + "width": self.width, + "height": self.height, + "seed": self.seed, + "num_inference_steps": self.num_inference_steps, + "guidance_scale": self.guidance_scale, + "use_face_correction": self.use_face_correction, + "use_upscale": self.use_upscale, + "sampler_name": self.sampler_name, + "use_stable_diffusion_model": self.use_stable_diffusion_model, + "clip_skip": self.clip_skip, + "tiling": self.tiling, + "use_vae_model": self.use_vae_model, + "use_hypernetwork_model": self.use_hypernetwork_model + }} + from json import dumps + return dumps(json) + + +from easydiffusion.easydb.database import engine +GalleryImage.metadata.create_all(engine) diff --git a/ui/easydiffusion/server.py b/ui/easydiffusion/server.py index a251ede6..1c3d3169 100644 --- a/ui/easydiffusion/server.py +++ b/ui/easydiffusion/server.py @@ -151,6 +151,10 @@ def init(): def read_root(): return FileResponse(os.path.join(app.SD_UI_DIR, "index.html"), headers=NOCACHE_HEADERS) + @server_api.get("/gallery-image.html") + def read_gallery_image(): + return FileResponse(os.path.join(app.SD_UI_DIR, "gallery-image.html"), headers=NOCACHE_HEADERS) + @server_api.on_event("shutdown") def shutdown_event(): # Signal render thread to close on shutdown task_manager.current_state_error = SystemExit("Application shutting down.") diff --git a/ui/easydiffusion/types.py b/ui/easydiffusion/types.py index ec331c29..422b7205 100644 --- a/ui/easydiffusion/types.py +++ b/ui/easydiffusion/types.py @@ -92,6 +92,7 @@ class RenderTaskData(TaskData): clip_skip: bool = False codeformer_upscale_faces: bool = False codeformer_fidelity: float = 0.5 + use_gallery: str = None class MergeRequest(BaseModel): diff --git a/ui/easydiffusion/utils/save_utils.py b/ui/easydiffusion/utils/save_utils.py index 457af921..4a5ac733 100644 --- a/ui/easydiffusion/utils/save_utils.py +++ b/ui/easydiffusion/utils/save_utils.py @@ -156,8 +156,51 @@ def save_images_to_disk( output_quality=output_format.output_quality, output_lossless=output_format.output_lossless, ) - if save_data.metadata_output_format: - for metadata_output_format in save_data.metadata_output_format.split(","): + + for i in range(len(filtered_images)): + path_i = f"{os.path.join(save_dir_path, make_filename(i))}.{output_format.output_format.lower()}" + + def createLoraString(metadata_entries, i): + if metadata_entries[i]["use_lora_model"] is None: + return "None" + elif isinstance(metadata_entries[i]["use_lora_model"], list): + loraString = "" + for j in range(len(metadata_entries[i]["use_lora_model"])): + loraString += metadata_entries[i]["use_lora_model"][j] + ":" + str(metadata_entries[i]["lora_alpha"][j]) + " " + return loraString.trim() + else: + return metadata_entries[i]["use_lora_model"] + ":" + str(metadata_entries[i]["lora_alpha"]) + + from easydiffusion.easydb.mappings import GalleryImage + from easydiffusion.easydb.database import SessionLocal + + if task_data.use_gallery != None: + session = SessionLocal() + session.add(GalleryImage( + path = path_i, + seed = metadata_entries[i]["seed"], + use_stable_diffusion_model = metadata_entries[i]["use_stable_diffusion_model"], + clip_skip = metadata_entries[i]["clip_skip"], + use_vae_model = metadata_entries[i]["use_vae_model"], + sampler_name = metadata_entries[i]["sampler_name"], + width = metadata_entries[i]["width"], + height = metadata_entries[i]["height"], + num_inference_steps = metadata_entries[i]["num_inference_steps"], + guidance_scale = metadata_entries[i]["guidance_scale"], + lora = createLoraString(metadata_entries, i), + use_hypernetwork_model = metadata_entries[i]["use_hypernetwork_model"], + tiling = metadata_entries[i]["tiling"], + use_face_correction = metadata_entries[i]["use_face_correction"], + use_upscale = metadata_entries[i]["use_upscale"], + prompt = metadata_entries[i]["prompt"], + negative_prompt = metadata_entries[i]["negative_prompt"], + workspace = task_data.use_gallery + )) + session.commit() + session.close() + + if task_data.metadata_output_format: + for metadata_output_format in task_data.metadata_output_format.split(","): if metadata_output_format.lower() in ["json", "txt", "embed"]: save_dicts( metadata_entries, diff --git a/ui/gallery-image.html b/ui/gallery-image.html new file mode 100644 index 00000000..ad6258e1 --- /dev/null +++ b/ui/gallery-image.html @@ -0,0 +1,14 @@ + + + + + +
+ + +
+ + +
+
+ diff --git a/ui/index.html b/ui/index.html index fcbcfa73..5d010f7b 100644 --- a/ui/index.html +++ b/ui/index.html @@ -27,6 +27,7 @@ +
@@ -52,6 +53,9 @@ Help & Community + + Gallery +
@@ -561,6 +565,34 @@ +