diff --git a/3rd-PARTY-LICENSES b/3rd-PARTY-LICENSES index bd29393a..05ba4541 100644 --- a/3rd-PARTY-LICENSES +++ b/3rd-PARTY-LICENSES @@ -712,3 +712,411 @@ FileSaver.js is licensed under the MIT license: SOFTWARE. [1]: http://eligrey.com + +croppr.js +========= +https://github.com/jamesssooi/Croppr.js + +croppr.js is licensed under the MIT license: + + MIT License + + Copyright (c) 2017 James Ooi + + 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. + +ExifReader +========== +https://github.com/mattiasw/ExifReader + +ExifReader is licensed under the Mozilla Public License: + + Mozilla Public License Version 2.0 + ================================== + + 1. Definitions + -------------- + + 1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + + 1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + + 1.3. "Contribution" + means Covered Software of a particular Contributor. + + 1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + + 1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + + 1.6. "Executable Form" + means any form of the work other than Source Code Form. + + 1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + + 1.8. "License" + means this document. + + 1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + + 1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + + 1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + + 1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + + 1.13. "Source Code Form" + means the form of the work preferred for making modifications. + + 1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + + 2. License Grants and Conditions + -------------------------------- + + 2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + (a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + + (b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + + 2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution + become effective for each Contribution on the date the Contributor first + distributes such Contribution. + + 2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under + this License. No additional rights or licenses will be implied from the + distribution or licensing of Covered Software under this License. + Notwithstanding Section 2.1(b) above, no patent license is granted by a + Contributor: + + (a) for any code that a Contributor has removed from Covered Software; + or + + (b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + (c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + + This License does not grant any rights in the trademarks, service marks, + or logos of any Contributor (except as may be necessary to comply with + the notice requirements in Section 3.4). + + 2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this + License (see Section 10.2) or under the terms of a Secondary License (if + permitted under the terms of Section 3.3). + + 2.5. Representation + + Each Contributor represents that the Contributor believes its + Contributions are its original creation(s) or it has sufficient rights + to grant the rights to its Contributions conveyed by this License. + + 2.6. Fair Use + + This License is not intended to limit any rights You have under + applicable copyright doctrines of fair use, fair dealing, or other + equivalents. + + 2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted + in Section 2.1. + + 3. Responsibilities + ------------------- + + 3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under + the terms of this License. You must inform recipients that the Source + Code Form of the Covered Software is governed by the terms of this + License, and how they can obtain a copy of this License. You may not + attempt to alter or restrict the recipients' rights in the Source Code + Form. + + 3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + (a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + + (b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + + 3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for + the Covered Software. If the Larger Work is a combination of Covered + Software with a work governed by one or more Secondary Licenses, and the + Covered Software is not Incompatible With Secondary Licenses, this + License permits You to additionally distribute such Covered Software + under the terms of such Secondary License(s), so that the recipient of + the Larger Work may, at their option, further distribute the Covered + Software under the terms of either this License or such Secondary + License(s). + + 3.4. Notices + + You may not remove or alter the substance of any license notices + (including copyright notices, patent notices, disclaimers of warranty, + or limitations of liability) contained within the Source Code Form of + the Covered Software, except that You may alter any license notices to + the extent required to remedy known factual inaccuracies. + + 3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on + behalf of any Contributor. You must make it absolutely clear that any + such warranty, support, indemnity, or liability obligation is offered by + You alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + + 4. Inability to Comply Due to Statute or Regulation + --------------------------------------------------- + + If it is impossible for You to comply with any of the terms of this + License with respect to some or all of the Covered Software due to + statute, judicial order, or regulation then You must: (a) comply with + the terms of this License to the maximum extent possible; and (b) + describe the limitations and the code they affect. Such description must + be placed in a text file included with all distributions of the Covered + Software under this License. Except to the extent prohibited by statute + or regulation, such description must be sufficiently detailed for a + recipient of ordinary skill to be able to understand it. + + 5. Termination + -------------- + + 5.1. The rights granted under this License will terminate automatically + if You fail to comply with any of its terms. However, if You become + compliant, then the rights granted under this License from a particular + Contributor are reinstated (a) provisionally, unless and until such + Contributor explicitly and finally terminates Your grants, and (b) on an + ongoing basis, if such Contributor fails to notify You of the + non-compliance by some reasonable means prior to 60 days after You have + come back into compliance. Moreover, Your grants from a particular + Contributor are reinstated on an ongoing basis if such Contributor + notifies You of the non-compliance by some reasonable means, this is the + first time You have received notice of non-compliance with this License + from such Contributor, and You become compliant prior to 30 days after + Your receipt of the notice. + + 5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, + counter-claims, and cross-claims) alleging that a Contributor Version + directly or indirectly infringes any patent, then the rights granted to + You by any and all Contributors for the Covered Software under Section + 2.1 of this License shall terminate. + + 5.3. In the event of termination under Sections 5.1 or 5.2 above, all + end user license agreements (excluding distributors and resellers) which + have been validly granted by You or Your distributors under this License + prior to termination shall survive termination. + + ************************************************************************ + * * + * 6. Disclaimer of Warranty * + * ------------------------- * + * * + * Covered Software is provided under this License on an "as is" * + * basis, without warranty of any kind, either expressed, implied, or * + * statutory, including, without limitation, warranties that the * + * Covered Software is free of defects, merchantable, fit for a * + * particular purpose or non-infringing. The entire risk as to the * + * quality and performance of the Covered Software is with You. * + * Should any Covered Software prove defective in any respect, You * + * (not any Contributor) assume the cost of any necessary servicing, * + * repair, or correction. This disclaimer of warranty constitutes an * + * essential part of this License. No use of any Covered Software is * + * authorized under this License except under this disclaimer. * + * * + ************************************************************************ + + ************************************************************************ + * * + * 7. Limitation of Liability * + * -------------------------- * + * * + * Under no circumstances and under no legal theory, whether tort * + * (including negligence), contract, or otherwise, shall any * + * Contributor, or anyone who distributes Covered Software as * + * permitted above, be liable to You for any direct, indirect, * + * special, incidental, or consequential damages of any character * + * including, without limitation, damages for lost profits, loss of * + * goodwill, work stoppage, computer failure or malfunction, or any * + * and all other commercial damages or losses, even if such party * + * shall have been informed of the possibility of such damages. This * + * limitation of liability shall not apply to liability for death or * + * personal injury resulting from such party's negligence to the * + * extent applicable law prohibits such limitation. Some * + * jurisdictions do not allow the exclusion or limitation of * + * incidental or consequential damages, so this exclusion and * + * limitation may not apply to You. * + * * + ************************************************************************ + + 8. Litigation + ------------- + + Any litigation relating to this License may be brought only in the + courts of a jurisdiction where the defendant maintains its principal + place of business and such litigation shall be governed by laws of that + jurisdiction, without reference to its conflict-of-law provisions. + Nothing in this Section shall prevent a party's ability to bring + cross-claims or counter-claims. + + 9. Miscellaneous + ---------------- + + This License represents the complete agreement concerning the subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. Any law or regulation which provides + that the language of a contract shall be construed against the drafter + shall not be used to construe this License against a Contributor. + + 10. Versions of the License + --------------------------- + + 10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + + 10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version + of the License under which You originally received the Covered Software, + or under the terms of any subsequent version published by the license + steward. + + 10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a + modified version of this License if you rename the license and remove + any references to the name of the license steward (except to note that + such modified license differs from this License). + + 10.4. Distributing Source Code Form that is Incompatible With Secondary + Licenses + + If You choose to distribute Source Code Form that is Incompatible With + Secondary Licenses under the terms of this version of the License, the + notice described in Exhibit B of this License must be attached. + + Exhibit A - Source Code Form License Notice + ------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at https://mozilla.org/MPL/2.0/. + + If it is not possible or desirable to put the notice in a particular + file, then You may include the notice in a location (such as a LICENSE + file in a relevant directory) where a recipient would be likely to look + for such a notice. + + You may add additional accurate notices of copyright ownership. + + Exhibit B - "Incompatible With Secondary Licenses" Notice + --------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/CHANGES.md b/CHANGES.md index 141c0773..1209960c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,44 @@ # What's new? +## v3.0 +### Major Changes +- **ControlNet** - Full support for ControlNet, with native integration of the common ControlNet models. Just select a control image, then choose the ControlNet filter/model and run. No additional configuration or download necessary. Supports custom ControlNets as well. +- **SDXL** - Full support for SDXL. No configuration necessary, just put the SDXL model in the `models/stable-diffusion` folder. +- **Multiple LoRAs** - Use multiple LoRAs, including SDXL and SD2-compatible LoRAs. Put them in the `models/lora` folder. +- **Embeddings** - Use textual inversion embeddings easily, by putting them in the `models/embeddings` folder and using their names in the prompt (or by clicking the `+ Embeddings` button to select embeddings visually). Thanks @JeLuf. +- **Seamless Tiling** - Generate repeating textures that can be useful for games and other art projects. Works best in 512x512 resolution. Thanks @JeLuf. +- **Inpainting Models** - Full support for inpainting models, including custom inpainting models. No configuration (or yaml files) necessary. +- **Faster than v2.5** - Nearly 40% faster than Easy Diffusion v2.5, and can be even faster if you enable xFormers. +- **Even less VRAM usage** - Less than 2 GB for 512x512 images on 'low' VRAM usage setting (SD 1.5). Can generate large images with SDXL. +- **WebP images** - Supports saving images in the lossless webp format. +- **Undo/Redo in the UI** - Remove tasks or images from the queue easily, and undo the action if you removed anything accidentally. Thanks @JeLuf. +- **Three new samplers, and latent upscaler** - Added `DEIS`, `DDPM` and `DPM++ 2m SDE` as additional samplers. Thanks @ogmaresca and @rbertus2000. +- **Significantly faster 'Upscale' and 'Fix Faces' buttons on the images** +- **Major rewrite of the code** - We've switched to using diffusers under-the-hood, which allows us to release new features faster, and focus on making the UI and installer even easier to use. + +### Detailed changelog +* 3.0.2 - 29 Aug 2023 - Fixed incorrect matching of embeddings from prompts. +* 3.0.2 - 24 Aug 2023 - Fix broken seamless tiling. +* 3.0.2 - 23 Aug 2023 - Fix styling on mobile devices. +* 3.0.2 - 22 Aug 2023 - Full support for inpainting models, including custom models. Support SD 1.x and SD 2.x inpainting models. Does not require you to specify a yaml config file. +* 3.0.2 - 22 Aug 2023 - Reduce VRAM consumption of controlnet in 'low' VRAM mode, and allow accelerating controlnets using xformers. +* 3.0.2 - 22 Aug 2023 - Improve auto-detection of SD 2.0 and 2.1 models, removing the need for custom yaml files for SD 2.x models. Improve the model load time by speeding-up the black image test. +* 3.0.1 - 18 Aug 2023 - Rotate an image if EXIF rotation is present. For e.g. this is common in images taken with a smartphone. +* 3.0.1 - 18 Aug 2023 - Resize control images to the task dimensions, to avoid memory errors with high-res control images. +* 3.0.1 - 18 Aug 2023 - Show controlnet filter preview in the task entry. +* 3.0.1 - 18 Aug 2023 - Fix drag-and-drop and 'Use these Settings' for LoRA and ControlNet. +* 3.0.1 - 18 Aug 2023 - Auto-save LoRA models and strengths. +* 3.0.1 - 17 Aug 2023 - Automatically use the correct yaml config file for custom SDXL models, even if a yaml file isn't present in the folder. +* 3.0.1 - 17 Aug 2023 - Fix broken embeddings with SDXL. +* 3.0.1 - 16 Aug 2023 - Fix broken LoRA with SDXL. +* 3.0.1 - 15 Aug 2023 - Fix broken seamless tiling. +* 3.0.1 - 15 Aug 2023 - Fix textual inversion embeddings not working in `low` VRAM usage mode. +* 3.0.1 - 15 Aug 2023 - Fix for custom VAEs not working in `low` VRAM usage mode. +* 3.0.1 - 14 Aug 2023 - Slider to change the image dimensions proportionally (in Image Settings). Thanks @JeLuf. +* 3.0.1 - 14 Aug 2023 - Show an error to the user if an embedding isn't compatible with the model, instead of failing silently without informing the user. Thanks @JeLuf. +* 3.0.1 - 14 Aug 2023 - Disable watermarking for SDXL img2img. Thanks @AvidGameFan. +* 3.0.0 - 3 Aug 2023 - Enabled diffusers for everyone by default. The old v2 engine can be used by disabling the "Use v3 engine" option in the Settings tab. + ## v2.5 ### Major Changes - **Nearly twice as fast** - significantly faster speed of image generation. Code contributions are welcome to make our project even faster: https://github.com/easydiffusion/sdkit/#is-it-fast diff --git a/README.md b/README.md index 3349c9d5..2efebf1d 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@ -# Easy Diffusion 2.5 +# Easy Diffusion 3.0 ### The easiest way to install and use [Stable Diffusion](https://github.com/CompVis/stable-diffusion) on your computer. Does not require technical knowledge, does not require pre-installed software. 1-click install, powerful features, friendly community. [Installation guide](#installation) | [Troubleshooting guide](https://github.com/easydiffusion/easydiffusion/wiki/Troubleshooting) | [![Discord Server](https://img.shields.io/discord/1014774730907209781?label=Discord)](https://discord.com/invite/u9yhsFmEkB) (for support queries, and development discussions) -![t2i](https://raw.githubusercontent.com/Stability-AI/stablediffusion/main/assets/stable-samples/txt2img/768/merged-0006.png) +--- +![262597678-11089485-2514-4a11-88fb-c3acc81fc9ec](https://github.com/easydiffusion/easydiffusion/assets/844287/050b5e15-e909-45bf-8162-a38234830e38) + # Installation Click the download button for your operating system: @@ -62,17 +64,19 @@ Just delete the `EasyDiffusion` folder to uninstall all the downloaded packages. - **UI Themes**: Customize the program to your liking. - **Searchable models dropdown**: organize your models into sub-folders, and search through them in the UI. -### Image generation -- **Supports**: "*Text to Image*" and "*Image to Image*". -- **21 Samplers**: `ddim`, `plms`, `heun`, `euler`, `euler_a`, `dpm2`, `dpm2_a`, `lms`, `dpm_solver_stability`, `dpmpp_2s_a`, `dpmpp_2m`, `dpmpp_sde`, `dpm_fast`, `dpm_adaptive`, `ddpm`, `deis`, `unipc_snr`, `unipc_tu`, `unipc_tq`, `unipc_snr_2`, `unipc_tu_2`. -- **In-Painting**: Specify areas of your image to paint into. +### Powerful image generation +- **Supports**: "*Text to Image*", "*Image to Image*" and "*InPainting*" +- **ControlNet**: For advanced control over the image, e.g. by setting the pose or drawing the outline for the AI to fill in. +- **16 Samplers**: `PLMS`, `DDIM`, `DEIS`, `Heun`, `Euler`, `Euler Ancestral`, `DPM2`, `DPM2 Ancestral`, `LMS`, `DPM Solver`, `DPM++ 2s Ancestral`, `DPM++ 2m`, `DPM++ 2m SDE`, `DPM++ SDE`, `DDPM`, `UniPC`. +- **Stable Diffusion XL and 2.1**: Generate higher-quality images using the latest Stable Diffusion XL models. +- **Textual Inversion Embeddings**: For guiding the AI strongly towards a particular concept. - **Simple Drawing Tool**: Draw basic images to guide the AI, without needing an external drawing program. - **Face Correction (GFPGAN)** - **Upscaling (RealESRGAN)** -- **Loopback**: Use the output image as the input image for the next img2img task. +- **Loopback**: Use the output image as the input image for the next image task. - **Negative Prompt**: Specify aspects of the image to *remove*. -- **Attention/Emphasis**: () in the prompt increases the model's attention to enclosed words, and [] decreases it. -- **Weighted Prompts**: Use weights for specific words in your prompt to change their importance, e.g. `red:2.4 dragon:1.2`. +- **Attention/Emphasis**: `+` in the prompt increases the model's attention to enclosed words, and `-` decreases it. E.g. `apple++ falling from a tree`. +- **Weighted Prompts**: Use weights for specific words in your prompt to change their importance, e.g. `(red)2.4 (dragon)1.2`. - **Prompt Matrix**: Quickly create multiple variations of your prompt, e.g. `a photograph of an astronaut riding a horse | illustration | cinematic lighting`. - **Prompt Set**: Quickly create multiple variations of your prompt, e.g. `a photograph of an astronaut on the {moon,earth}` - **1-click Upscale/Face Correction**: Upscale or correct an image after it has been generated. @@ -82,10 +86,11 @@ Just delete the `EasyDiffusion` folder to uninstall all the downloaded packages. ### Advanced features - **Custom Models**: Use your own `.ckpt` or `.safetensors` file, by placing it inside the `models/stable-diffusion` folder! -- **Stable Diffusion 2.1 support** +- **Stable Diffusion XL and 2.1 support** - **Merge Models** - **Use custom VAE models** -- **Use pre-trained Hypernetworks** +- **Textual Inversion Embeddings** +- **ControlNet** - **Use custom GFPGAN models** - **UI Plugins**: Choose from a growing list of [community-generated UI plugins](https://github.com/easydiffusion/easydiffusion/wiki/UI-Plugins), or write your own plugin to add features to the project! @@ -103,18 +108,8 @@ Just delete the `EasyDiffusion` folder to uninstall all the downloaded packages. ---- -## Easy for new users: -![Screenshot of the initial UI](https://user-images.githubusercontent.com/844287/217043152-29454d15-0387-4228-b70d-9a4b84aeb8ba.png) - - -## Powerful features for advanced users: -![Screenshot of advanced settings](https://user-images.githubusercontent.com/844287/217042588-fc53c975-bacd-4a9c-af88-37408734ade3.png) - - -## Live Preview -Useful for judging (and stopping) an image quickly, without waiting for it to finish rendering. - -![live-512](https://user-images.githubusercontent.com/844287/192097249-729a0a1e-a677-485e-9ccc-16a9e848fabe.gif) +## Easy for new users, powerful features for advanced users: +![image](https://github.com/easydiffusion/easydiffusion/assets/844287/efbbac9f-42ce-4aef-8625-fd23c74a8241) ## Task Queue ![Screenshot of task queue](https://user-images.githubusercontent.com/844287/217043984-0b35f73b-1318-47cb-9eed-a2a91b430490.png) @@ -128,12 +123,6 @@ Please refer to our [guide](https://github.com/easydiffusion/easydiffusion/wiki/ # Bugs reports and code contributions welcome If there are any problems or suggestions, please feel free to ask on the [discord server](https://discord.com/invite/u9yhsFmEkB) or [file an issue](https://github.com/easydiffusion/easydiffusion/issues). -We could really use help on these aspects (click to view tasks that need your help): -* [User Interface](https://github.com/users/cmdr2/projects/1/views/1) -* [Engine](https://github.com/users/cmdr2/projects/3/views/1) -* [Installer](https://github.com/users/cmdr2/projects/4/views/1) -* [Documentation](https://github.com/users/cmdr2/projects/5/views/1) - If you have any code contributions in mind, please feel free to say Hi to us on the [discord server](https://discord.com/invite/u9yhsFmEkB). We use the Discord server for development-related discussions, and for helping users. # Credits diff --git a/patch.patch b/patch.patch new file mode 100644 index 00000000..a452181b Binary files /dev/null and b/patch.patch differ diff --git a/scripts/check_modules.py b/scripts/check_modules.py index 640566a5..29d43761 100644 --- a/scripts/check_modules.py +++ b/scripts/check_modules.py @@ -18,13 +18,15 @@ os_name = platform.system() modules_to_check = { "torch": ("1.11.0", "1.13.1", "2.0.0"), "torchvision": ("0.12.0", "0.14.1", "0.15.1"), - "sdkit": "1.0.165", + "sdkit": "2.0.3", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", "fastapi": "0.85.1", "pycloudflared": "0.2.0", "ruamel.yaml": "0.17.21", + "sqlalchemy": "2.0.19", + "python-multipart": "0.0.6", # "xformers": "0.0.16", } modules_to_log = ["torch", "torchvision", "sdkit", "stable-diffusion-sdkit"] diff --git a/scripts/on_env_start.bat b/scripts/on_env_start.bat index 43f7e2b7..d0e915e2 100644 --- a/scripts/on_env_start.bat +++ b/scripts/on_env_start.bat @@ -46,6 +46,8 @@ if "%update_branch%"=="" ( @cd sd-ui-files + @call git add -A . + @call git stash @call git reset --hard @call git -c advice.detachedHead=false checkout "%update_branch%" @call git pull diff --git a/scripts/on_env_start.sh b/scripts/on_env_start.sh index 02428ce5..6fd61c26 100755 --- a/scripts/on_env_start.sh +++ b/scripts/on_env_start.sh @@ -29,6 +29,8 @@ if [ -f "scripts/install_status.txt" ] && [ `grep -c sd_ui_git_cloned scripts/in cd sd-ui-files + git add -A . + git stash git reset --hard git -c advice.detachedHead=false checkout "$update_branch" git pull diff --git a/ui/easydiffusion/app.py b/ui/easydiffusion/app.py index e181f9b8..e2c190f8 100644 --- a/ui/easydiffusion/app.py +++ b/ui/easydiffusion/app.py @@ -38,6 +38,7 @@ SD_UI_DIR = os.getenv("SD_UI_PATH", None) CONFIG_DIR = os.path.abspath(os.path.join(SD_UI_DIR, "..", "scripts")) MODELS_DIR = os.path.abspath(os.path.join(SD_DIR, "..", "models")) +BUCKET_DIR = os.path.abspath(os.path.join(SD_DIR, "..", "bucket")) USER_PLUGINS_DIR = os.path.abspath(os.path.join(SD_DIR, "..", "plugins")) CORE_PLUGINS_DIR = os.path.abspath(os.path.join(SD_UI_DIR, "plugins")) @@ -60,6 +61,7 @@ APP_CONFIG_DEFAULTS = { "ui": { "open_browser_on_start": True, }, + "test_diffusers": True, } IMAGE_EXTENSIONS = [ @@ -115,7 +117,7 @@ def getConfig(default_val=APP_CONFIG_DEFAULTS): def set_config_on_startup(config: dict): if getConfig.__test_diffusers_on_startup is None: - getConfig.__test_diffusers_on_startup = config.get("test_diffusers", False) + getConfig.__test_diffusers_on_startup = config.get("test_diffusers", True) config["config_on_startup"] = {"test_diffusers": getConfig.__test_diffusers_on_startup} if os.path.isfile(config_yaml_path): diff --git a/ui/easydiffusion/bucket_manager.py b/ui/easydiffusion/bucket_manager.py new file mode 100644 index 00000000..60a4ed6c --- /dev/null +++ b/ui/easydiffusion/bucket_manager.py @@ -0,0 +1,102 @@ +from typing import List + +from fastapi import Depends, FastAPI, HTTPException, Response, File +from sqlalchemy.orm import Session + +from easydiffusion.easydb import crud, models, schemas +from easydiffusion.easydb.database import SessionLocal, engine + +from requests.compat import urlparse + +import base64, json + +MIME_TYPES = { + "jpg": "image/jpeg", + "jpeg": "image/jpeg", + "gif": "image/gif", + "png": "image/png", + "webp": "image/webp", + "js": "text/javascript", + "htm": "text/html", + "html": "text/html", + "css": "text/css", + "json": "application/json", + "mjs": "application/json", + "yaml": "application/yaml", + "svg": "image/svg+xml", + "txt": "text/plain", +} + +def init(): + from easydiffusion.server import server_api + + models.BucketBase.metadata.create_all(bind=engine) + + + # Dependency + def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() + + @server_api.get("/bucket/{obj_path:path}") + def bucket_get_object(obj_path: str, db: Session = Depends(get_db)): + filename = get_filename_from_url(obj_path) + path = get_path_from_url(obj_path) + + if filename==None: + bucket = crud.get_bucket_by_path(db, path=path) + if bucket == None: + raise HTTPException(status_code=404, detail="Bucket not found") + bucketfiles = db.query(models.BucketFile).with_entities(models.BucketFile.filename).filter(models.BucketFile.bucket_id == bucket.id).all() + bucketfiles = [ x.filename for x in bucketfiles ] + return bucketfiles + + else: + bucket_id = crud.get_bucket_by_path(db, path).id + bucketfile = db.query(models.BucketFile).filter(models.BucketFile.bucket_id == bucket_id, models.BucketFile.filename == filename).first() + + suffix = get_suffix_from_filename(filename) + + return Response(content=bucketfile.data, media_type=MIME_TYPES.get(suffix, "application/octet-stream")) + + @server_api.post("/bucket/{obj_path:path}") + def bucket_post_object(obj_path: str, file: bytes = File(), db: Session = Depends(get_db)): + filename = get_filename_from_url(obj_path) + path = get_path_from_url(obj_path) + bucket = crud.get_bucket_by_path(db, path) + + if bucket == None: + bucket = crud.create_bucket(db=db, bucket=schemas.BucketCreate(path=path)) + bucket_id = bucket.id + + bucketfile = schemas.BucketFileCreate(filename=filename, data=file) + result = crud.create_bucketfile(db=db, bucketfile=bucketfile, bucket_id=bucket_id) + result.data = base64.encodestring(result.data) + return result + + + @server_api.post("/buckets/{bucket_id}/items/", response_model=schemas.BucketFile) + def create_bucketfile_in_bucket( + bucket_id: int, bucketfile: schemas.BucketFileCreate, db: Session = Depends(get_db) + ): + bucketfile.data = base64.decodestring(bucketfile.data) + result = crud.create_bucketfile(db=db, bucketfile=bucketfile, bucket_id=bucket_id) + result.data = base64.encodestring(result.data) + return result + + +def get_filename_from_url(url): + path = urlparse(url).path + name = path[path.rfind('/')+1:] + return name or None + +def get_path_from_url(url): + path = urlparse(url).path + path = path[0:path.rfind('/')] + return path or None + +def get_suffix_from_filename(filename): + return filename[filename.rfind('.')+1:] diff --git a/ui/easydiffusion/easydb/crud.py b/ui/easydiffusion/easydb/crud.py new file mode 100644 index 00000000..65bea255 --- /dev/null +++ b/ui/easydiffusion/easydb/crud.py @@ -0,0 +1,24 @@ +from sqlalchemy.orm import Session + +from easydiffusion.easydb import models, schemas + + +def get_bucket_by_path(db: Session, path: str): + return db.query(models.Bucket).filter(models.Bucket.path == path).first() + + +def create_bucket(db: Session, bucket: schemas.BucketCreate): + db_bucket = models.Bucket(path=bucket.path) + db.add(db_bucket) + db.commit() + db.refresh(db_bucket) + return db_bucket + + +def create_bucketfile(db: Session, bucketfile: schemas.BucketFileCreate, bucket_id: int): + db_bucketfile = models.BucketFile(**bucketfile.dict(), bucket_id=bucket_id) + db.merge(db_bucketfile) + db.commit() + db_bucketfile = db.query(models.BucketFile).filter(models.BucketFile.bucket_id==bucket_id, models.BucketFile.filename==bucketfile.filename).first() + return db_bucketfile + diff --git a/ui/easydiffusion/easydb/database.py b/ui/easydiffusion/easydb/database.py new file mode 100644 index 00000000..6cb43ecb --- /dev/null +++ b/ui/easydiffusion/easydb/database.py @@ -0,0 +1,14 @@ +import os +from easydiffusion import app + +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker + +os.makedirs(app.BUCKET_DIR, exist_ok=True) +SQLALCHEMY_DATABASE_URL = "sqlite:///"+os.path.join(app.BUCKET_DIR, "bucket.db") + +engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +BucketBase = declarative_base() diff --git a/ui/easydiffusion/easydb/models.py b/ui/easydiffusion/easydb/models.py new file mode 100644 index 00000000..04834951 --- /dev/null +++ b/ui/easydiffusion/easydb/models.py @@ -0,0 +1,25 @@ +from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, BLOB +from sqlalchemy.orm import relationship + +from easydiffusion.easydb.database import BucketBase + + +class Bucket(BucketBase): + __tablename__ = "bucket" + + id = Column(Integer, primary_key=True, index=True) + path = Column(String, unique=True, index=True) + + bucketfiles = relationship("BucketFile", back_populates="bucket") + + +class BucketFile(BucketBase): + __tablename__ = "bucketfile" + + filename = Column(String, index=True, primary_key=True) + bucket_id = Column(Integer, ForeignKey("bucket.id"), primary_key=True) + + data = Column(BLOB, index=False) + + bucket = relationship("Bucket", back_populates="bucketfiles") + diff --git a/ui/easydiffusion/easydb/schemas.py b/ui/easydiffusion/easydb/schemas.py new file mode 100644 index 00000000..68bc04e2 --- /dev/null +++ b/ui/easydiffusion/easydb/schemas.py @@ -0,0 +1,36 @@ +from typing import List, Union + +from pydantic import BaseModel + + +class BucketFileBase(BaseModel): + filename: str + data: bytes + + +class BucketFileCreate(BucketFileBase): + pass + + +class BucketFile(BucketFileBase): + bucket_id: int + + class Config: + orm_mode = True + + +class BucketBase(BaseModel): + path: str + + +class BucketCreate(BucketBase): + pass + + +class Bucket(BucketBase): + id: int + bucketfiles: List[BucketFile] = [] + + class Config: + orm_mode = True + diff --git a/ui/easydiffusion/model_manager.py b/ui/easydiffusion/model_manager.py index 845e9126..e57584ca 100644 --- a/ui/easydiffusion/model_manager.py +++ b/ui/easydiffusion/model_manager.py @@ -11,6 +11,7 @@ from sdkit import Context from sdkit.models import load_model, scan_model, unload_model, download_model, get_model_info_from_db from sdkit.models.model_loader.controlnet_filters import filters as cn_filters from sdkit.utils import hash_file_quick +from sdkit.models.model_loader.embeddings import get_embedding_token KNOWN_MODEL_TYPES = [ "stable-diffusion", @@ -29,7 +30,7 @@ MODEL_EXTENSIONS = { "hypernetwork": [".pt", ".safetensors"], "gfpgan": [".pth"], "realesrgan": [".pth"], - "lora": [".ckpt", ".safetensors"], + "lora": [".ckpt", ".safetensors", ".pt"], "codeformer": [".pth"], "embeddings": [".pt", ".bin", ".safetensors"], "controlnet": [".pth", ".safetensors"], @@ -65,9 +66,6 @@ def load_default_models(context: Context): runtime.set_vram_optimizations(context) - config = app.getConfig() - context.embeddings_path = os.path.join(app.MODELS_DIR, "embeddings") - # init default model paths for model_type in MODELS_TO_LOAD_ON_START: context.model_paths[model_type] = resolve_model_to_use(model_type=model_type, fail_if_not_found=False) @@ -102,7 +100,16 @@ def unload_all(context: Context): def resolve_model_to_use(model_name: Union[str, list] = None, model_type: str = None, fail_if_not_found: bool = True): model_names = model_name if isinstance(model_name, list) else [model_name] - model_paths = [resolve_model_to_use_single(m, model_type, fail_if_not_found) for m in model_names] + model_paths = [] + for m in model_names: + if model_type == "embeddings": + try: + resolve_model_to_use_single(m, model_type) + except FileNotFoundError: # try with spaces + m = m.replace("_", " ") + + path = resolve_model_to_use_single(m, model_type, fail_if_not_found) + model_paths.append(path) return model_paths[0] if len(model_paths) == 1 else model_paths @@ -141,7 +148,9 @@ def resolve_model_to_use_single(model_name: str = None, model_type: str = None, return default_model_path if model_name and fail_if_not_found: - raise Exception(f"Could not find the desired model {model_name}! Is it present in the {model_dir} folder?") + raise FileNotFoundError( + f"Could not find the desired model {model_name}! Is it present in the {model_dir} folder?" + ) def reload_models_if_necessary(context: Context, models_data: ModelsData, models_to_force_reload: list = []): @@ -315,6 +324,7 @@ def getModels(scan_for_malicious: bool = True): {"control_v11p_sd15_mlsd": "Straight Lines"}, {"control_v11p_sd15_seg": "Segment"}, {"control_v11e_sd15_shuffle": "Shuffle"}, + {"control_v11f1e_sd15_tile": "Tile"}, ], }, } @@ -324,9 +334,11 @@ def getModels(scan_for_malicious: bool = True): class MaliciousModelException(Exception): "Raised when picklescan reports a problem with a model" - def scan_directory(directory, suffixes, directoriesFirst: bool = True, default_entries=[]): - tree = list(default_entries) + def scan_directory(directory, suffixes, directoriesFirst: bool = True, default_entries=[], nameFilter=None): nonlocal models_scanned + + tree = list(default_entries) + for entry in sorted( os.scandir(directory), key=lambda entry: (entry.is_file() == directoriesFirst, entry.name.lower()), @@ -345,7 +357,11 @@ def getModels(scan_for_malicious: bool = True): raise MaliciousModelException(entry.path) if scan_for_malicious: known_models[entry.path] = mtime + model_id = entry.name[: -len(matching_suffix)] + if callable(nameFilter): + model_id = nameFilter(model_id) + model_exists = False for m in tree: # allows default "named" models, like CodeFormer and known ControlNet models if (isinstance(m, str) and model_id == m) or (isinstance(m, dict) and model_id in m): @@ -353,14 +369,15 @@ def getModels(scan_for_malicious: bool = True): break if not model_exists: tree.append(model_id) + elif entry.is_dir(): - scan = scan_directory(entry.path, suffixes, directoriesFirst=False) + scan = scan_directory(entry.path, suffixes, directoriesFirst=False, nameFilter=nameFilter) if len(scan) != 0: tree.append((entry.name, scan)) return tree - def listModels(model_type): + def listModels(model_type, nameFilter=None): nonlocal models_scanned model_extensions = MODEL_EXTENSIONS.get(model_type, []) @@ -370,7 +387,9 @@ def getModels(scan_for_malicious: bool = True): try: default_tree = models["options"].get(model_type, []) - models["options"][model_type] = scan_directory(models_dir, model_extensions, default_entries=default_tree) + models["options"][model_type] = scan_directory( + models_dir, model_extensions, default_entries=default_tree, nameFilter=nameFilter + ) except MaliciousModelException as e: models["scan-error"] = str(e) @@ -382,7 +401,7 @@ def getModels(scan_for_malicious: bool = True): listModels(model_type="hypernetwork") listModels(model_type="gfpgan") listModels(model_type="lora") - listModels(model_type="embeddings") + listModels(model_type="embeddings", nameFilter=get_embedding_token) listModels(model_type="controlnet") if scan_for_malicious and models_scanned > 0: diff --git a/ui/easydiffusion/package_manager.py b/ui/easydiffusion/package_manager.py index c246c54d..72479379 100644 --- a/ui/easydiffusion/package_manager.py +++ b/ui/easydiffusion/package_manager.py @@ -12,9 +12,9 @@ from easydiffusion import app manifest = { "tensorrt": { "install": [ - "nvidia-cudnn --extra-index-url=https://pypi.ngc.nvidia.com --trusted-host pypi.ngc.nvidia.com", - "tensorrt-libs --extra-index-url=https://pypi.ngc.nvidia.com --trusted-host pypi.ngc.nvidia.com", - "tensorrt --extra-index-url=https://pypi.ngc.nvidia.com --trusted-host pypi.ngc.nvidia.com", + "nvidia-cudnn --pre --extra-index-url=https://pypi.nvidia.com --trusted-host pypi.nvidia.com", + "tensorrt-libs --pre --extra-index-url=https://pypi.nvidia.com --trusted-host pypi.nvidia.com", + "tensorrt --pre --extra-index-url=https://pypi.nvidia.com --trusted-host pypi.nvidia.com", ], "uninstall": ["tensorrt"], # TODO also uninstall tensorrt-libs and nvidia-cudnn, but do it upon restarting (avoid 'file in use' error) @@ -25,7 +25,7 @@ installing = [] # remove this once TRT releases on pypi if platform.system() == "Windows": trt_dir = os.path.join(app.ROOT_DIR, "tensorrt") - if os.path.exists(trt_dir): + if os.path.exists(trt_dir) and os.path.isdir(trt_dir) and len(os.listdir(trt_dir)) > 0: files = os.listdir(trt_dir) packages = manifest["tensorrt"]["install"] @@ -61,6 +61,10 @@ def install(module_name): raise RuntimeError(f"Can't install unknown package: {module_name}!") commands = manifest[module_name]["install"] + if module_name == "tensorrt": + commands += [ + "protobuf==3.20.3 polygraphy==0.47.1 onnx==1.14.0 --extra-index-url=https://pypi.ngc.nvidia.com --trusted-host pypi.ngc.nvidia.com" + ] commands = [f"python -m pip install --upgrade {cmd}" for cmd in commands] installing.append(module_name) diff --git a/ui/easydiffusion/runtime.py b/ui/easydiffusion/runtime.py index 4098ee8e..38c56e40 100644 --- a/ui/easydiffusion/runtime.py +++ b/ui/easydiffusion/runtime.py @@ -30,9 +30,7 @@ def init(device): from easydiffusion import app app_config = app.getConfig() - context.test_diffusers = ( - app_config.get("test_diffusers", False) and app_config.get("update_branch", "main") != "main" - ) + context.test_diffusers = app_config.get("test_diffusers", True) log.info("Device usage during initialization:") get_device_usage(device, log_info=True, process_usage_only=False) diff --git a/ui/easydiffusion/server.py b/ui/easydiffusion/server.py index a8f848fd..e3f80f42 100644 --- a/ui/easydiffusion/server.py +++ b/ui/easydiffusion/server.py @@ -63,7 +63,7 @@ class SetAppConfigRequest(BaseModel, extra=Extra.allow): ui_open_browser_on_start: bool = None listen_to_network: bool = None listen_port: int = None - test_diffusers: bool = False + test_diffusers: bool = True def init(): @@ -139,6 +139,10 @@ def init(): def modify_package(package_name: str, req: dict): return modify_package_internal(package_name, req) + @server_api.get("/sha256/{obj_path:path}") + def get_sha256(obj_path: str): + return get_sha256_internal(obj_path) + @server_api.get("/") def read_root(): return FileResponse(os.path.join(app.SD_UI_DIR, "index.html"), headers=NOCACHE_HEADERS) @@ -451,3 +455,26 @@ def modify_package_internal(package_name: str, req: dict): log.error(str(e)) log.error(traceback.format_exc()) return HTTPException(status_code=500, detail=str(e)) + +def get_sha256_internal(obj_path): + import hashlib + from easydiffusion.utils import sha256sum + + path = obj_path.split("/") + type = path.pop(0) + + try: + model_path = model_manager.resolve_model_to_use("/".join(path), type) + except Exception as e: + log.error(str(e)) + log.error(traceback.format_exc()) + + return HTTPException(status_code=404) + try: + digest = sha256sum(model_path) + return {"digest": digest} + except Exception as e: + log.error(str(e)) + log.error(traceback.format_exc()) + return HTTPException(status_code=500, detail=str(e)) + diff --git a/ui/easydiffusion/tasks/filter_images.py b/ui/easydiffusion/tasks/filter_images.py index c4e674d7..1e653e3e 100644 --- a/ui/easydiffusion/tasks/filter_images.py +++ b/ui/easydiffusion/tasks/filter_images.py @@ -3,7 +3,7 @@ import pprint from sdkit.filter import apply_filters from sdkit.models import load_model -from sdkit.utils import img_to_base64_str, log +from sdkit.utils import img_to_base64_str, get_image, log from easydiffusion import model_manager, runtime from easydiffusion.types import FilterImageRequest, FilterImageResponse, ModelsData, OutputFormatData @@ -42,7 +42,12 @@ class FilterTask(Task): print_task_info(self.request, self.models_data, self.output_format) - images = filter_images(context, self.request.image, self.request.filter, self.request.filter_params) + if isinstance(self.request.image, list): + images = [get_image(img) for img in self.request.image] + else: + images = get_image(self.request.image) + + images = filter_images(context, images, self.request.filter, self.request.filter_params) output_format = self.output_format images = [ diff --git a/ui/easydiffusion/tasks/render_images.py b/ui/easydiffusion/tasks/render_images.py index bdf6e3ac..f0d25d37 100644 --- a/ui/easydiffusion/tasks/render_images.py +++ b/ui/easydiffusion/tasks/render_images.py @@ -15,6 +15,8 @@ from sdkit.utils import ( img_to_base64_str, img_to_buffer, latent_samples_to_images, + resize_img, + get_image, log, ) @@ -226,8 +228,13 @@ def generate_images_internal( req.width, req.height = map(lambda x: x - x % 8, (req.width, req.height)) # clamp to 8 if req.control_image and task_data.control_filter_to_apply: + req.control_image = get_image(req.control_image) + req.control_image = resize_img(req.control_image.convert("RGB"), req.width, req.height, clamp_to_8=True) req.control_image = filter_images(context, req.control_image, task_data.control_filter_to_apply)[0] + if req.init_image is not None and int(req.num_inference_steps * req.prompt_strength) == 0: + req.prompt_strength = 1 / req.num_inference_steps if req.num_inference_steps > 0 else 1 + if context.test_diffusers: pipe = context.models["stable-diffusion"]["default"] if hasattr(pipe.unet, "_allocate_trt_buffers_backup"): diff --git a/ui/easydiffusion/types.py b/ui/easydiffusion/types.py index fe936ca2..eeffcb72 100644 --- a/ui/easydiffusion/types.py +++ b/ui/easydiffusion/types.py @@ -73,6 +73,7 @@ class TaskData(BaseModel): use_hypernetwork_model: Union[str, List[str]] = None use_lora_model: Union[str, List[str]] = None use_controlnet_model: Union[str, List[str]] = None + use_embeddings_model: Union[str, List[str]] = None filters: List[str] = [] filter_params: Dict[str, Dict[str, Any]] = {} control_filter_to_apply: Union[str, List[str]] = None @@ -200,6 +201,7 @@ def convert_legacy_render_req_to_new(old_req: dict): model_paths["hypernetwork"] = old_req.get("use_hypernetwork_model") model_paths["lora"] = old_req.get("use_lora_model") model_paths["controlnet"] = old_req.get("use_controlnet_model") + model_paths["embeddings"] = old_req.get("use_embeddings_model") model_paths["gfpgan"] = old_req.get("use_face_correction", "") model_paths["gfpgan"] = model_paths["gfpgan"] if "gfpgan" in model_paths["gfpgan"].lower() else None diff --git a/ui/easydiffusion/utils/__init__.py b/ui/easydiffusion/utils/__init__.py index b9c5e21a..f6758809 100644 --- a/ui/easydiffusion/utils/__init__.py +++ b/ui/easydiffusion/utils/__init__.py @@ -6,3 +6,15 @@ from .save_utils import ( save_images_to_disk, get_printable_request, ) + +def sha256sum(filename): + sha256 = hashlib.sha256() + with open(filename, "rb") as f: + while True: + data = f.read(8192) # Read in chunks of 8192 bytes + if not data: + break + sha256.update(data) + + return sha256.hexdigest() + diff --git a/ui/easydiffusion/utils/save_utils.py b/ui/easydiffusion/utils/save_utils.py index 89dae991..3bd5efba 100644 --- a/ui/easydiffusion/utils/save_utils.py +++ b/ui/easydiffusion/utils/save_utils.py @@ -10,6 +10,7 @@ from easydiffusion import app from easydiffusion.types import GenerateImageRequest, TaskData, OutputFormatData from numpy import base_repr from sdkit.utils import save_dicts, save_images +from sdkit.models.model_loader.embeddings import get_embedding_token filename_regex = re.compile("[^a-zA-Z0-9._-]") img_number_regex = re.compile("([0-9]{5,})") @@ -34,7 +35,7 @@ TASK_TEXT_MAPPING = { "lora_alpha": "LoRA Strength", "use_hypernetwork_model": "Hypernetwork model", "hypernetwork_strength": "Hypernetwork Strength", - "use_embedding_models": "Embedding models", + "use_embeddings_model": "Embedding models", "tiling": "Seamless Tiling", "use_face_correction": "Use Face Correction", "use_upscale": "Use Upscaling", @@ -219,7 +220,7 @@ def get_printable_request(req: GenerateImageRequest, task_data: TaskData, output task_data_metadata.update(output_format.dict()) app_config = app.getConfig() - using_diffusers = app_config.get("test_diffusers", False) + using_diffusers = app_config.get("test_diffusers", True) # Save the metadata in the order defined in TASK_TEXT_MAPPING metadata = {} @@ -228,20 +229,18 @@ def get_printable_request(req: GenerateImageRequest, task_data: TaskData, output metadata[key] = req_metadata[key] elif key in task_data_metadata: metadata[key] = task_data_metadata[key] - elif key == "use_embedding_models" and using_diffusers: + + if key == "use_embeddings_model" and using_diffusers: embeddings_extensions = {".pt", ".bin", ".safetensors"} def scan_directory(directory_path: str): used_embeddings = [] for entry in os.scandir(directory_path): if entry.is_file(): - entry_extension = os.path.splitext(entry.name)[1] - if entry_extension not in embeddings_extensions: + # Check if the filename has the right extension + if not any(map(lambda ext: entry.name.endswith(ext), embeddings_extensions)): continue - - embedding_name_regex = regex.compile( - r"(^|[\s,])" + regex.escape(os.path.splitext(entry.name)[0]) + r"([+-]*$|[\s,]|[+-]+[\s,])" - ) + embedding_name_regex = regex.compile(r"(^|[\s,])" + regex.escape(get_embedding_token(entry.name)) + r"([+-]*$|[\s,]|[+-]+[\s,])") if embedding_name_regex.search(req.prompt) or embedding_name_regex.search(req.negative_prompt): used_embeddings.append(entry.path) elif entry.is_dir(): @@ -249,7 +248,7 @@ def get_printable_request(req: GenerateImageRequest, task_data: TaskData, output return used_embeddings used_embeddings = scan_directory(os.path.join(app.MODELS_DIR, "embeddings")) - metadata["use_embedding_models"] = used_embeddings if len(used_embeddings) > 0 else None + metadata["use_embeddings_model"] = used_embeddings if len(used_embeddings) > 0 else None # Clean up the metadata if req.init_image is None and "prompt_strength" in metadata: @@ -265,7 +264,10 @@ def get_printable_request(req: GenerateImageRequest, task_data: TaskData, output if task_data.use_controlnet_model is None and "control_filter_to_apply" in metadata: del metadata["control_filter_to_apply"] - if not using_diffusers: + if using_diffusers: + for key in (x for x in ["use_hypernetwork_model", "hypernetwork_strength"] if x in metadata): + del metadata[key] + else: for key in ( x for x in ["use_lora_model", "lora_alpha", "clip_skip", "tiling", "latent_upscaler_steps", "use_controlnet_model", "control_filter_to_apply"] if x in metadata ): diff --git a/ui/index.html b/ui/index.html index a95fa04f..8ce5f820 100644 --- a/ui/index.html +++ b/ui/index.html @@ -18,12 +18,15 @@ + + +