├── .github └── ISSUE_TEMPLATE │ └── bug_report.yml ├── .gitignore ├── LICENCE ├── README.md ├── README_zh_CN.md ├── javascript └── fastload_view.js ├── localizations └── zh_CN.json ├── preview_1.png ├── preview_2.png ├── preview_3.png └── scripts ├── api.py ├── api_package.py ├── fastload.py ├── fastload_view.py └── setting.py /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report / Bug报告 2 | description: Create a bug report / 创建一个新Bug报告 3 | title: "[Bug]: " 4 | labels: ["bug-report"] 5 | 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | ## **Please fill this form with as much information as possible** 11 | ## **请尽可能详细的描述你遇到的问题** 12 | 13 | - type: checkboxes 14 | attributes: 15 | label: Is there an existing issue for this? / issue里有这个问题吗? 16 | options: 17 | - label: I have searched the existing issues and checked the recent builds/commits of both this extension and the webui / 我已经确认过这里和webui的issue区中没有和我相同的问题 18 | required: true 19 | 20 | - type: textarea 21 | id: what-did 22 | attributes: 23 | label: What happened and what should have happened? / 发生了什么以及本来应该发生什么? 24 | description: | 25 | Tell us what happened in a very clear and simple way 26 | 请尽可能简单清晰的描述 27 | validations: 28 | required: true 29 | 30 | - type: textarea 31 | id: steps 32 | attributes: 33 | label: Steps to reproduce the problem / 重现问题的步骤 34 | description: | 35 | Please provide us with precise step by step information on how to reproduce the bug 36 | 请提供复现这个问题的步骤 37 | value: | 38 | 1. Go to .... 39 | 2. Press .... 40 | 3. ... 41 | validations: 42 | required: true 43 | 44 | - type: textarea 45 | id: browsers 46 | attributes: 47 | label: What browsers do you use to access the UI ? / 你在使用什么浏览器访问webui? 48 | validations: 49 | required: true 50 | 51 | - type: textarea 52 | id: optional-log 53 | attributes: 54 | label: Are you using a third-party webui integration package that contains logging? / 你是否在使用含有日志记录的第三方webui整合包? 55 | description: | 56 | If so, you can directly upload the log file generated by the third-party integration package here to skip the following steps. 57 | 如果是,你可以直接在这里上传第三方整合包生成的日志文件以略过以下环节 58 | value: | 59 | [Users who deploy webui by themselves should skip this section.] 60 | [自行部署webui的用户请跳过本部分] 61 | 62 | - type: textarea 63 | id: commits 64 | attributes: 65 | label: Commit where the problem happens / 问题出现时你使用的commit 66 | description: | 67 | Which commit of the extension are you running on? 68 | 问题出现时, 你使用的以下webui/拓展的commit是? 69 | value: | 70 | [Users who have already uploaded the complete log file can skip this section] 71 | [已经上传完整日志文件的用户可跳过本部分] 72 | webui: xxxxxxxx 73 | controlnet: xxxxxxxx 74 | controlnet-fastload: xxxxxxxx 75 | 76 | - type: textarea 77 | id: cmdargs 78 | attributes: 79 | label: Command Line Arguments / 命令行参数 80 | description: | 81 | Are you using any launching parameters/command line arguments (modified webui-user .bat/.sh) ? If yes, please write them below. Write "No" otherwise. 82 | 您是否使用任何启动参数/命令行参数(修改后的 webui-user .bat/.sh)? 如果是,请写在下面。否则写“否”。 83 | value: | 84 | [Users who have already uploaded the complete log file can skip this section] 85 | [已经上传完整日志文件的用户可跳过本部分] 86 | render: Shell 87 | 88 | - type: textarea 89 | id: extensions 90 | attributes: 91 | label: List of enabled extensions / 使用的拓展列表 92 | description: | 93 | Please provide a full list of enabled extensions or screenshots of your "Extensions" tab. 94 | 请提供已启用扩展程序的完整列表或“扩展程序”选项卡的屏幕截图。 95 | value: | 96 | [Users who have already uploaded the complete log file can skip this section] 97 | [已经上传完整日志文件的用户可跳过本部分] 98 | 99 | - type: textarea 100 | id: logs 101 | attributes: 102 | label: Console logs / 控制台输出 103 | description: | 104 | Please provide full cmd/terminal logs from the moment you started UI to the end of it, after your bug happened. If it's very long, provide a link to pastebin or similar service. 105 | 在错误发生后,请提供从启动 UI 到结束的完整 cmd/终端日志。 如果很长,请提供 Pastebin 或类似服务(在线剪贴板)的链接。 106 | value: | 107 | [Users who have already uploaded the complete log file can skip this section] 108 | [已经上传完整日志文件的用户可跳过本部分] 109 | render: Shell 110 | 111 | - type: textarea 112 | id: misc 113 | attributes: 114 | label: Additional information / 额外信息 115 | description: | 116 | Please provide us with any relevant additional info or context. 117 | 请向我们提供任何相关的附加信息或背景。 118 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /__pycache__/ -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # sd-webui-controlnet-fastload 4 | An extension for AUTOMATIC1111's [stable-diffusion-webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui). 5 |
6 | readme-zh-cn 7 | wakatime 8 | welcome 9 |
10 |
11 | 12 | ## Features 13 | - Can save the parameters and original diagram of the [Controlnet plugin](https://github.com/Mikubill/sd-webui-controlnet). 14 | - Embed Controlnet parameters directly into the image or save in a separate file for sharing. 15 | - Quickly load parameters from an image or file embedded with Controlnet parameters to txt2img or img2img. 16 | - Use under the UI or call through the API. 17 | - Optional features `isEnabledManualSend` **allow you to complete all preparations under this plugin.** 18 | - `Controlnet Fastload Filter` Tab allow you import pictures according to **ControlNet parameter classification** 19 | 20 | ## Preview 21 | ### Main function 22 | ![preview_1.png](preview_1.png) 23 | ### View embedded information 24 | ![preview_2.png](preview_2.png) 25 | ### Controlnet Fastload Filter tab 26 | ![preview_3.png](preview_3.png) 27 | 28 | https://github.com/pk5ls20/sd-webui-controlnet-fastload/assets/114645197/ff6ce950-b52e-4748-8a27-212cc5a96c3d 29 | 30 | 31 | ## Usage 32 | **For more details, please refer to the [wiki](https://github.com/pk5ls20/sd-webui-controlnet-fastload/wiki) of this project.** 33 | 34 | ## Star History 35 | 36 | 37 | 38 | 39 | 40 | Star History Chart 41 | 42 | 43 | -------------------------------------------------------------------------------- /README_zh_CN.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # sd-webui-controlnet-fastload 4 | 一个用于AUTOMATIC1111's [stable-diffusion-webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui)的插件 5 |
6 | readme 7 | wakatime 8 | welcome 9 |
10 |
11 | 12 | ## 功能 13 | - 可以保存[Controlnet插件](https://github.com/Mikubill/sd-webui-controlnet)的参数及原始图 14 | - 将Controlnet参数及原始图直接嵌入到图像中或保存在单独的文件中,以便分享 15 | - 从嵌入Controlnet参数的图片或文件快速加载参数到txt2img或img2img中 16 | - 可以在UI下使用, 或通过API调用 17 | - 可选功能`isEnabledManualSend`**可以让你在这个插件下完成出图的全部前期准备** 18 | - `Controlnet Fastload Filter` Tab可以**根据ControlNet参数**导入图片 19 | 20 | ## 预览 21 | ### 主功能 22 | ![preview_1.png](preview_1.png) 23 | ### 预览嵌入信息 24 | ![preview_2.png](preview_2.png) 25 | ### Controlnet Fastload筛选Tab 26 | ![preview_3.png](preview_3.png) 27 | 28 | https://github.com/pk5ls20/sd-webui-controlnet-fastload/assets/114645197/054dcbd2-3f04-4d56-9339-e119a2eeed46 29 | 30 | ## 使用方法 31 | **更多细节,请参阅本项目的[wiki](https://github.com/pk5ls20/sd-webui-controlnet-fastload/wiki)** 32 | 33 | ## Star History 34 | 35 | 36 | 37 | 38 | 39 | Star History Chart 40 | 41 | 42 | -------------------------------------------------------------------------------- /javascript/fastload_view.js: -------------------------------------------------------------------------------- 1 | const delay = (timeout = 0) => new Promise((resolve) => setTimeout(resolve, timeout)); 2 | const appDoc = gradioApp(); 3 | 4 | const sendMsg = (isDebug, send_) => { 5 | return isDebug === "True" 6 | ? console.log(`Log ${send_}`) 7 | : alert(`${send_}`); 8 | } 9 | 10 | async function checkAccessToken(accessTokenInput, accessTokenRightSHA512) { 11 | const encoder = new TextEncoder(); 12 | const data = encoder.encode(accessTokenInput); 13 | const hashArrayBuffer = await crypto.subtle.digest('SHA-512', data); 14 | const hash = Array.from(new Uint8Array(hashArrayBuffer)) 15 | .map(b => b.toString(16).padStart(2, '0')) 16 | .join(''); 17 | if (hash === accessTokenRightSHA512) { 18 | sendMsg("False", "You have access permission now!"); 19 | } else { 20 | sendMsg("False", "You have no access permission!"); 21 | } 22 | return [accessTokenInput, accessTokenRightSHA512] 23 | } 24 | 25 | function checkElementExistence(selector) { 26 | return new Promise((resolve, reject) => { 27 | let checkInterval = setInterval(() => { 28 | let element = document.querySelector(selector); 29 | if (element) { 30 | clearInterval(checkInterval); 31 | resolve(); 32 | } 33 | }, 500); 34 | }); 35 | } 36 | 37 | //selectPicAddress, selectPicControlnetAddress, sendControlnetPriority, sendControlnetTxt2img, info 38 | async function sendToAny2img(picAddress, controlnetAddress, priority, way, isDebug) { 39 | way = (way === "Send to Controlnet-txt2img") ? "txt2img" : "img2img"; 40 | let fastloadElemId = (way === "txt2img") ? "script_txt2img_controlnet_fastload_" : "script_img2img_controlnet_fastload_"; 41 | let status; 42 | // priority === auto, 当controlnetAddress不为空发送到fastload, 否则controlnet 43 | if (picAddress === "" && controlnetAddress === "") return sendMsg(isDebug, "Please select a picture first!"); 44 | (way === "txt2img") ? window.switch_to_txt2img() : window.switch_to_img2img(); 45 | await delay(200); 46 | if (priority === "Auto") { 47 | if (controlnetAddress === "") status = await sendToControlnet(way, picAddress); 48 | else status = await sendToControlnetFastload(way, controlnetAddress, fastloadElemId); 49 | sendMsg(isDebug, status[1]); 50 | } else if (priority === "Controlnet First") { 51 | status = await sendToControlnet(way, picAddress); 52 | if (controlnetAddress !== "") sendMsg(isDebug, `Send to Controlnet, But there are fastload data in there...`); 53 | sendMsg(isDebug, status[1]); 54 | } else { 55 | status = await sendToControlnetFastload(way, controlnetAddress, fastloadElemId); 56 | sendMsg(isDebug, status[1]); 57 | } 58 | } 59 | 60 | async function sendToControlnet(way, url_) { 61 | try { 62 | await delay(100); 63 | const cn = appDoc.querySelector(`#${way}_controlnet`); 64 | const wrap = cn.querySelector('.label-wrap'); 65 | if (!wrap.className.includes('open')) { 66 | wrap.click(); 67 | await delay(100); 68 | } 69 | wrap.scrollIntoView(); 70 | if (way === "img2img") { 71 | let checkbox1 = document.querySelector( 72 | "#img2img_controlnet_ControlNet-0_controlnet_same_img2img_checkbox input[type='checkbox']" 73 | ); 74 | let checkbox2 = document.querySelector( 75 | "#img2img_controlnet_ControlNet_controlnet_same_img2img_checkbox input[type='checkbox']" 76 | ); 77 | let availableCheckbox = (checkbox1 === null) ? checkbox2 : checkbox1; 78 | if (!availableCheckbox.checked) availableCheckbox.click(); 79 | let pic = (checkbox1 === null) ? "#img2img_controlnet_ControlNet_input_image" : "#img2img_controlnet_ControlNet-0_input_image"; 80 | await checkElementExistence(pic); 81 | } 82 | const response = await fetch(url_); 83 | const imageBlob = await response.blob(); 84 | const imageFile = new File([imageBlob], 'image.jpg', { 85 | type: imageBlob.type, 86 | lastModified: Date.now() 87 | }); 88 | const dataTransfer = new DataTransfer(); 89 | dataTransfer.items.add(imageFile); 90 | const pasteEvent = new ClipboardEvent('paste', { 91 | clipboardData: dataTransfer, 92 | bubbles: true 93 | }); 94 | wrap.dispatchEvent(pasteEvent); 95 | return [true, "Send to Controlnet successfully"]; 96 | } catch (err) { 97 | return [false, err] 98 | } 99 | } 100 | 101 | async function sendToControlnetFastload(way, url_, fastloadElemId) { 102 | try { 103 | await delay(200); 104 | if (url_ === "") return [false, "The selected picture lacks Fastload data. Operation aborted."]; 105 | const cn = appDoc.querySelector(`#${fastloadElemId}`); 106 | const wrap = cn.querySelector('.label-wrap'); 107 | if (!wrap.className.includes('open')) { 108 | wrap.click(); 109 | await delay(100); 110 | } 111 | wrap.scrollIntoView(); 112 | const baseElement = document.getElementById(`${fastloadElemId}cnfl_uploadImage`); 113 | const clearButton = baseElement.querySelector('button[aria-label="Clear"]'); 114 | if (clearButton) { 115 | clearButton.click(); 116 | } 117 | await delay(100); 118 | const target = baseElement.querySelector('div:nth-child(3)'); 119 | const imageFile = await urlToImageFile(url_); 120 | triggerEvent(target, 'dragenter'); 121 | await delay(50); 122 | triggerEvent(target, 'dragover'); 123 | await delay(50); 124 | triggerEvent(target, 'drop', createDropEvent(imageFile)); 125 | await delay(50); 126 | const fastloadEnable = document.getElementById('script_txt2img_controlnet_fastload_cnfl_enabled'); 127 | const fastloadEnableCheckBox = fastloadEnable.querySelector('input[type="checkbox"]'); 128 | if (!fastloadEnableCheckBox.checked) { 129 | fastloadEnableCheckBox.click(); 130 | } 131 | return [true, "Send to Controlnet Fastload successfully"]; 132 | } catch (err) { 133 | return [false, err] 134 | } 135 | } 136 | 137 | function triggerEvent(target, type, customEvent = null) { 138 | let event = customEvent ? customEvent : new Event(type, {bubbles: true}); 139 | target.dispatchEvent(event); 140 | } 141 | 142 | function createDropEvent(file) { 143 | const dataTransfer = new DataTransfer(); 144 | dataTransfer.items.add(file); 145 | return new DragEvent('drop', { 146 | dataTransfer: dataTransfer, 147 | bubbles: true 148 | }); 149 | } 150 | 151 | async function urlToImageFile(imgUrl) { 152 | const urlObj = new URL(imgUrl); 153 | const path = urlObj.href.split('=')[1]; // 提取文件路径 154 | const filename = path.split('/').pop(); 155 | const response = await fetch(imgUrl); 156 | const imageBlob = await response.blob(); 157 | return new File([imageBlob], filename, { 158 | type: imageBlob.type, 159 | lastModified: Date.now() 160 | }); 161 | } 162 | 163 | function changeStyle() { 164 | const fastloadTabElemId = "controlnet_fastload_tab_"; 165 | const sendTxt2imgButton = document.getElementById(`${fastloadTabElemId}send_txt2img`); 166 | const sendImg2imgButton = document.getElementById(`${fastloadTabElemId}send_img2img`); 167 | const sendTxt2imgFastloadButton = document.getElementById(`${fastloadTabElemId}send_controlnet_txt2img`); 168 | if (sendTxt2imgButton && sendTxt2imgFastloadButton) { 169 | let width = sendTxt2imgFastloadButton.offsetWidth + 'px'; 170 | let height = sendTxt2imgFastloadButton.offsetHeight + 'px'; 171 | sendTxt2imgButton.style.width = width; 172 | sendTxt2imgButton.style.height = height; 173 | sendImg2imgButton.style.width = width; 174 | sendImg2imgButton.style.height = height; 175 | } 176 | } 177 | 178 | onUiTabChange(changeStyle); -------------------------------------------------------------------------------- /localizations/zh_CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "Enable": "启用", 3 | "Mode": "模式", 4 | "Load Only": "仅加载", 5 | "Save Only": "仅保存", 6 | "Load & Save": "加载并保存", 7 | "Save Controlnet Data in ...": "保存Controlnet数据为...", 8 | "Where to save Controlnet data?": "要在哪里保存Controlnet的数据呢?", 9 | "Embed photo": "嵌入至生成图片", 10 | "Extra .cni file": "保存为.cni文件", 11 | "Both": "全部", 12 | "Overwrite priority": "覆盖优先级", 13 | "If the ControlNet Plugin is enabled, which do you use first?": "在启用ControlNet插件情况下,优先使用哪里的数据呢?", 14 | "Plugin first": "插件优先", 15 | "Script first": "脚本优先", 16 | "Upload Image or .cni file": "上传图片或.cni文件" 17 | } -------------------------------------------------------------------------------- /preview_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pk5ls20/sd-webui-controlnet-fastload/bbdb929f4285ea69d5099268496e4322dc7203ab/preview_1.png -------------------------------------------------------------------------------- /preview_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pk5ls20/sd-webui-controlnet-fastload/bbdb929f4285ea69d5099268496e4322dc7203ab/preview_2.png -------------------------------------------------------------------------------- /preview_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pk5ls20/sd-webui-controlnet-fastload/bbdb929f4285ea69d5099268496e4322dc7203ab/preview_3.png -------------------------------------------------------------------------------- /scripts/api.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import io 3 | from PIL import Image 4 | import gradio as gr 5 | import numpy as np 6 | from fastapi import FastAPI, Body 7 | from fastapi.exceptions import HTTPException 8 | from scripts.fastload import viewSaveDataExecute, addToPicture 9 | import scripts.api_package as api_package 10 | import modules.script_callbacks as script_callbacks 11 | 12 | 13 | def controlnet_api(_: gr.Blocks, app: FastAPI): 14 | @app.get("/controlnetFastload/version") 15 | async def version(): 16 | return {"version": 1.1} 17 | 18 | @app.post("/controlnetFastload/fetch") 19 | async def fetch( 20 | returnFileType: str = Body("Extra .cni file", title='returnType'), 21 | extraPicBase64: str = Body("", title='extraPicBase64'), 22 | ControlNetID: int = Body(title='ControlNetID') 23 | ): 24 | try: 25 | result_dict = {} 26 | controlnetList_ = api_package.api_instance.drawId[ControlNetID] 27 | # 先考虑extraPicBase64=="", 此时一定返回.cni 28 | if extraPicBase64 == "": 29 | result_dict['.cni'] = addToPicture("", controlnetList_, "base64") 30 | # 在考虑extraPicBase64!="", 此时要看returnFileType 31 | else: 32 | if returnFileType == "Embed photo": 33 | result_dict['photo'] = addToPicture(extraPicBase64, controlnetList_, "base64") 34 | if returnFileType == "Extra .cni file": 35 | result_dict['.cni'] = addToPicture(extraPicBase64, controlnetList_, "base64") 36 | if returnFileType == "Both": 37 | result_dict['photo'] = addToPicture(extraPicBase64, controlnetList_, "base64") 38 | result_dict['.cni'] = addToPicture(extraPicBase64, controlnetList_, "base64") 39 | except KeyError as e: 40 | raise HTTPException( 41 | status_code=422, detail="Controlnet not found: " + str(e) 42 | ) 43 | except Exception as e: 44 | raise HTTPException( 45 | status_code=422, detail="An error occurred: " + str(e) 46 | ) 47 | return result_dict 48 | 49 | @app.post("/controlnetFastload/view") 50 | async def view( 51 | filepath: str = Body("", title='filepath'), 52 | except_type: str = Body("base64", title='except_type') 53 | ): 54 | base64_pic_list = [] 55 | if filepath == "": 56 | raise HTTPException( 57 | status_code=422, detail="No file uploaded") 58 | try: 59 | pic_list, info_dict = viewSaveDataExecute(filepath) 60 | for pic in pic_list: 61 | if except_type == "base64": 62 | pic_ = Image.fromarray(pic) 63 | io_ = io.BytesIO() 64 | pic_.save(io_, format="PNG") 65 | base64_pic_list.append(base64.b64encode(io_.getvalue())) 66 | elif except_type == "nparray": 67 | base64_pic_list.append(np.array2string(pic)) 68 | else: 69 | raise HTTPException( 70 | status_code=422, detail="except_type should be base64 or nparray") 71 | except Exception as e: 72 | raise HTTPException( 73 | status_code=422, detail="An error occurred: " + str(e) 74 | ) 75 | return { 76 | "pic_list": base64_pic_list, 77 | "info_list": info_dict 78 | } 79 | 80 | 81 | script_callbacks.on_app_started(controlnet_api) 82 | -------------------------------------------------------------------------------- /scripts/api_package.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | 4 | class ControlNetFastloadAPI: 5 | def __init__(self): 6 | self.enabled = False 7 | self.drawId = {} 8 | 9 | def info(self): 10 | print(f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S,%f")[:-3]} - ControlNetFastload - \033[92mINFO\033[0m - ' 11 | f'API is {self.enabled}, have {len(self.drawId)} drawId(s)') 12 | 13 | 14 | api_instance = ControlNetFastloadAPI() 15 | -------------------------------------------------------------------------------- /scripts/fastload.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import gzip 4 | import pickle 5 | import base64 6 | import importlib 7 | import gradio as gr 8 | import numpy as np 9 | from PIL import Image 10 | from typing import Optional, List 11 | from datetime import datetime 12 | from gradio import Checkbox, Dropdown, File, Textbox, Button, Gallery, JSON 13 | import modules.scripts as scripts 14 | from modules import script_callbacks 15 | from modules.script_callbacks import ImageSaveParams 16 | from modules.shared import opts, cmd_opts 17 | from modules.images import read_info_from_image 18 | from modules.processing import process_images, Processed 19 | import modules.generation_parameters_copypaste as parameters_copypaste 20 | 21 | save_flag = False 22 | controlNetList = [] 23 | save_filetype = "" 24 | overwrite_flag = "" 25 | start_marker = b'###START_OF_CONTROLNET_FASTLOAD###' 26 | end_marker = b'###END_OF_CONTROLNET_FASTLOAD###' 27 | current_timestamp = lambda: datetime.now().strftime('%Y-%m-%d %H:%M:%S,%f')[:-3] 28 | print_err = lambda msg: print(f'{current_timestamp()} - ControlNetFastload - \033[91mERROR\033[0m - {msg}') 29 | print_warn = lambda msg: print(f'{current_timestamp()} - ControlNetFastload - \033[93mWARNING\033[0m - {msg}') 30 | print_info = lambda msg: print(f'{current_timestamp()} - ControlNetFastload - \033[92mINFO\033[0m - {msg}') 31 | 32 | 33 | class ControlNetFastLoad(scripts.Script): 34 | """ 35 | 插件的主类, 继承自scripts.Script 36 | 参见https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Developing-extensions 37 | """ 38 | 39 | def __init__(self): 40 | pass 41 | 42 | def title(self) -> str: 43 | return "ControlNet Fastload" 44 | 45 | def show(self, is_img2img: bool) -> bool: 46 | return scripts.AlwaysVisible 47 | 48 | def ui(self, is_img2img: bool) -> list[Checkbox | Dropdown | File | Textbox | Button | Gallery | JSON]: 49 | ui_list = [] 50 | with (gr.Accordion("ControlNet Fastload v1.2.1", open=False, elem_id=self.elem_id(""))): 51 | with gr.Tab("Load data from file"): 52 | with gr.Row(): 53 | enabled = gr.Checkbox(value=False, label="Enable", elem_id=self.elem_id("cnfl_enabled")) 54 | mode = gr.Dropdown(["Load Only", "Save Only", "Load & Save"], label="Mode", 55 | value="Load Only", elem_id=self.elem_id("cnfl_mode")) 56 | ui_list.extend([enabled, mode]) 57 | # 在这里, load代表仅重写controlnet, save代表仅重写生成后的图片 58 | # load&save仅能在controlnet全部未启用情况下使用, 其会加载传入controlnet, 重写controlnet, 重写生成后的图片 59 | with gr.Row(): 60 | # 出于引用关系, gr.Textbox放到这里 61 | png_other_info = gr.Textbox(visible=False, elem_id="pnginfo_generation_info") 62 | uploadFile = gr.File(type="file", label="Upload Image or .cni file", 63 | file_types=["image", ".cni"], elem_id=self.elem_id("cnfl_uploadImage")) 64 | uploadFile.upload( 65 | fn=uploadFileListen, 66 | inputs=[uploadFile, enabled], 67 | outputs=png_other_info 68 | ) 69 | ui_list.extend([uploadFile, png_other_info]) 70 | # 测试填充整个参数 71 | with gr.Row(): 72 | # 保持统一, 参见https://github.com/AUTOMATIC1111/stable-diffusion-webui/issues/11210 73 | visible_ = opts.data.get("isEnabledManualSend") 74 | visible_ = False if visible_ is None else visible_ 75 | send_to_txt2img = gr.Button(label="Send to txt2img", value="Send to txt2img", 76 | elem_id=self.elem_id("send_to_txt2img"), 77 | visible=((not is_img2img) and visible_)) 78 | send_to_img2img = gr.Button(label="Send to img2img", value="Send to img2img", 79 | elem_id=self.elem_id("send_to_img2img"), 80 | visible=(is_img2img and visible_)) 81 | parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding( 82 | paste_button=send_to_txt2img, tabname="txt2img", source_text_component=png_other_info, 83 | source_image_component=None, 84 | )) 85 | parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding( 86 | paste_button=send_to_img2img, tabname="img2img", source_text_component=png_other_info, 87 | source_image_component=None, 88 | )) 89 | ui_list.extend([send_to_txt2img, send_to_img2img]) 90 | with gr.Tab("View saved data"): 91 | with gr.Row(): 92 | execute_view_tab = gr.Button(label="Execute", elem_id=self.elem_id("cnfl_execute_view_tab")) 93 | with gr.Row(): 94 | uploadFile_view_tab = gr.File(type="file", label="Upload Image or .cni file", 95 | file_types=["image", ".cni"], 96 | elem_id=self.elem_id("cnfl_uploadImage_view_tab")) 97 | with gr.Row(): 98 | img_view_tab = gr.Gallery(type="file", label="Image data view", 99 | elem_id=self.elem_id("cnfl_img_view_tab"), rows=2, columns=2, 100 | allow_preview=True, show_download_button=True, object_fit="contain", 101 | show_label=True) 102 | with gr.Row(): 103 | text_view_tab = gr.Json(label="Text data view", 104 | elem_id=self.elem_id("cnfl_text_view_tab")) 105 | ui_list.extend([execute_view_tab, uploadFile_view_tab, img_view_tab, text_view_tab]) 106 | execute_view_tab.click( 107 | fn=viewSaveDataExecute, 108 | inputs=[uploadFile_view_tab], 109 | outputs=[img_view_tab, text_view_tab] 110 | ) 111 | return ui_list 112 | 113 | def before_process(self, p, *args) -> None: 114 | api_module = importlib.import_module('extensions.sd-webui-controlnet-fastload.scripts.api') 115 | api_package = getattr(api_module, "api_package") 116 | if type(args[0]) is not bool: 117 | enabled, mode, uploadFile = True, args[0]['mode'], args[0]['filepath'] 118 | saveControlnet, overwritePriority = "", args[0]['overwritePriority'] 119 | api_package.api_instance.enabled = True 120 | api_package.api_instance.drawId[id(p)] = [] 121 | api_package.api_instance.info() 122 | else: 123 | enabled, mode, uploadFile = args[:3] 124 | saveControlnet, overwritePriority = opts.saveControlnet, opts.overwritePriority 125 | if enabled: 126 | # Load start 127 | try: 128 | global controlNetList 129 | break_load = False 130 | controlNetModule = importlib.import_module('extensions.sd-webui-controlnet.scripts.external_code', 131 | 'external_code') 132 | # 获取最原始的controlnetList 133 | controlNetList = controlNetModule.get_all_units_in_processing(p) 134 | controlNetListOriLen = len(controlNetList) 135 | controlNetListIsEmpty = not (any(itm.enabled for itm in controlNetList)) 136 | # 上传文件是否为空, 若为空则不能加载文件 137 | if uploadFile is None and (mode == "Load Only" or mode == "Load & Save"): 138 | print_warn("Script received no input; the loading process will be skipped.") 139 | break_load = True 140 | except ImportError: 141 | print_warn("ControlNet not found; the script will not work.") 142 | # proc = process_images(p) 143 | # return proc 144 | return 145 | if (mode == "Load Only" or mode == "Load & Save") and not break_load: 146 | load_file_name_ = uploadFile if isinstance(uploadFile, str) else uploadFile.name 147 | # 更新controlnetList 148 | if controlNetListIsEmpty: 149 | controlNetList = loadFromFile(load_file_name_) 150 | else: 151 | if overwritePriority == "ControlNet Plugin First": 152 | print_warn("The plugin is not empty and has priority; the script will not work.") 153 | else: 154 | print_warn("The plugin is not empty, but the script has priority;" 155 | " it will overwrite the existing Controlnet plugin data.") 156 | controlNetList = loadFromFile(load_file_name_) 157 | if len(controlNetList) > controlNetListOriLen: 158 | print_warn("The ControlNet count in the file exceeds the current setting;" 159 | " this might cause an error.") 160 | # 重写controlnet 161 | controlNetModule.update_cn_script_in_processing(p, controlNetList) 162 | if mode == "Save Only" or mode == "Load & Save": 163 | global save_flag, save_filetype 164 | save_flag = True 165 | save_filetype = saveControlnet 166 | if api_package.api_instance.enabled: 167 | api_package.api_instance.drawId[id(p)] = controlNetList 168 | 169 | def postprocess_image(self, p, pp, *args): 170 | if type(args[0]) is not bool and args[0]['mode'] != "Load Only": 171 | p.extra_generation_params['ControlNetID'] = id(p) 172 | 173 | 174 | def uploadFileListen(pic: gr.File, enabled: bool) -> str: 175 | """ 176 | 从上传的图片/文件中提取出PNG_INFO后传回, 参考自from modules.extras import run_pnginfo 177 | :param pic: 上传的图片/文件, 以包装好的gr.File形式传入 178 | :param enabled: (主插件)是否启用, 未启用直接返回空字符串 179 | :return: str: read_info_from_image函数返回值, 返回给一个textbox 180 | """ 181 | if not pic: 182 | return "" 183 | filetype_is_cni = lambda filename: os.path.splitext(pic.name)[1] == '.cni' 184 | if filetype_is_cni(pic.name) or not enabled: 185 | return "" 186 | fileInPil = Image.open(pic.name) 187 | gen_info, items = read_info_from_image(fileInPil) 188 | print(gen_info) 189 | return gen_info 190 | 191 | 192 | def judgeControlnetDataFile(filepath: str, filepathWeb: str) -> str: 193 | """ 194 | 传入图片文件地址,判断controlnet数据存在于图片文件/.同名cni文件 195 | :param filepath: 图片文件地址 196 | :param filepathWeb: 图片文件地址(网页端) 197 | :return filepath(修改后): 含有controlnet数据文件地址 198 | """ 199 | urlStart = re.search(r'^(.*?)/file=', filepathWeb).group(1) 200 | cnList = loadFromFile(filepath, False) 201 | cniFilePath = filepath[:-4] + ".cni" 202 | if len(cnList) > 0: 203 | return filepathWeb 204 | elif len(cnList) == 0 and os.path.exists(cniFilePath): 205 | cnList = loadFromFile(cniFilePath, False) 206 | return f"{urlStart}/file={filepath[:-4]}.cni" if len(cnList) > 0 else "" 207 | else: 208 | return "" 209 | 210 | 211 | def viewSaveDataExecute(file: gr.File or str) -> tuple: 212 | """ 213 | 查看本插件存储在图片/.cni中的数据 214 | :param file: 上传的图片/文件, 以包装好的gr.File/str形式传入 215 | :return: tuple: (list, list) 参见下面和ui渲染部分, 这个tuple喂给两个ui组件 216 | """ 217 | try: 218 | if file is None: 219 | print_warn("You did not upload an image or file.") 220 | return [], {"Error": "You did not upload an image or file."} 221 | file_name_ = file if isinstance(file, str) else file.name 222 | tmpControlNetList = loadFromFile(file_name_) 223 | previewPicture = [] 224 | previewInformation = [] 225 | loop_count = 0 226 | for itm in tmpControlNetList: 227 | tmp = itm if isinstance(itm, dict) else vars(itm) 228 | if "image" in tmp and tmp["image"] is not None: 229 | if isinstance(tmp["image"], np.ndarray): 230 | image_arrays = [(tmp["image"], f"Controlnet - {loop_count}")] 231 | else: 232 | image_arrays = [(img_array, f"Controlnet - {loop_count}") for img_array in tmp["image"].values()] 233 | previewPicture.extend(image_arrays) 234 | tmp.pop("image") 235 | previewInformation.append(tmp) 236 | loop_count += 1 237 | return previewPicture, previewInformation 238 | except Exception as e: 239 | print_err(e) 240 | return [], {"Error": "An unknown error occurred, see the console for details"} 241 | 242 | 243 | def addToPicture(image: str, datalist: list, imageType: str) -> bytes | None: 244 | """ 245 | 将ControlnetList经过gzip压缩后序列化存入图片中 246 | :param image: 和type挂钩 247 | :param datalist: ControlnetList 248 | :param imageType: "filepath" / "base64" 249 | """ 250 | if imageType == "filepath" and (not os.path.exists(image)): 251 | print_err(f"File {image} does not exist.") 252 | return 253 | serialized_data = gzip.compress(pickle.dumps(datalist)) 254 | if imageType == "filepath": 255 | with open(image, 'rb') as img_file: 256 | image_data = img_file.read() 257 | else: 258 | image_data = base64.b64decode(image) 259 | combined_data = image_data + start_marker + serialized_data + end_marker 260 | if imageType == "filepath": 261 | with open(image, 'wb') as img_file: 262 | img_file.write(combined_data) 263 | else: 264 | return base64.b64encode(combined_data) 265 | 266 | 267 | def loadFromFile(filepath: str, enableWarn: Optional[bool] = None) -> list: 268 | """ 269 | 从图片中读取ControlnetList 270 | :param filepath: 图片路径 271 | :param enableWarn 是否报错 272 | """ 273 | if not os.path.exists(filepath): 274 | print_err(f"File {filepath} does not exist.") if enableWarn is None else None 275 | return [{"Error": f"File {filepath} does not exist."}] 276 | with open(filepath, 'rb') as fp: 277 | readyLoadData = fp.read() 278 | start_idx = readyLoadData.find(start_marker) + len(start_marker) 279 | end_idx = readyLoadData.find(end_marker) 280 | try: 281 | embedded_data = gzip.decompress(readyLoadData[start_idx:end_idx]) 282 | # 判定存入的controlnet和现有的controlnet数量差异 283 | readyLoadList = pickle.loads(embedded_data) 284 | return readyLoadList 285 | except gzip.BadGzipFile: 286 | print_err(f"{filepath} does not contain valid Controlnet Fastload data.") if enableWarn is None else None 287 | return [{"Error": f"{filepath} does not contain valid Controlnet Fastload data."}] 288 | except Exception as e: 289 | print_err(f"Error while loading Controlnet Fastload data from the image: {e}") if enableWarn is None else None 290 | return [{"Error": f"Error while loading Controlnet Fastload data from the image: {e}"}] 291 | 292 | 293 | # 保存图片钩子 294 | def afterSavePicture(img_save_param: ImageSaveParams) -> None: 295 | """ 296 | 保存图片后的钩子函数, 用于将ControlnetList写入图片中 297 | 注意这个函数并不能被api调用 298 | :param img_save_param: 参见script_callbacks.py 299 | """ 300 | # 在这里已经知道生成图像所在位置了,直接写入数据 301 | if save_flag: 302 | filepath = os.path.join(os.getcwd(), img_save_param.filename) 303 | filepath_pure, _ = os.path.splitext(filepath) 304 | if save_filetype == "Embed photo" or save_filetype == "Both": 305 | addToPicture(filepath, controlNetList, "filepath") 306 | if save_filetype == "Extra .cni file" or save_filetype == "Both": 307 | with open(filepath_pure + ".cni", 'wb'): 308 | pass 309 | addToPicture(filepath_pure + ".cni", controlNetList, "filepath") 310 | print_info(f"ControlNet data saved to {filepath}") 311 | 312 | 313 | script_callbacks.on_image_saved(afterSavePicture) 314 | -------------------------------------------------------------------------------- /scripts/fastload_view.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import PIL 4 | import hashlib 5 | import gradio as gr 6 | from PIL import Image 7 | from typing import Tuple, List 8 | from modules.shared import opts 9 | import modules.scripts as scripts 10 | from modules import script_callbacks 11 | from scripts.fastload import judgeControlnetDataFile, print_info 12 | import modules.generation_parameters_copypaste as parameters_copypaste 13 | 14 | picSHA256, allViewData = {}, {} 15 | addEmoji = "➕" 16 | flyEmoji = "✈️" 17 | elemIdFlag = "controlnet_fastload_tab_" 18 | accessLevel = -1 19 | 20 | 21 | class viewDataWrap: 22 | def __init__(self, filepathList: list, picDict: dict): 23 | self.filepathList = filepathList 24 | self.picDict = picDict 25 | 26 | 27 | class ToolButton(gr.Button, gr.components.FormComponent): 28 | def __init__(self, **kwargs): 29 | super().__init__(variant="tool", elem_classes=["toolButton"], **kwargs) 30 | 31 | def get_block_name(self): 32 | return "button" 33 | 34 | 35 | def on_ui_tabs() -> list: 36 | global accessLevel 37 | from modules.shared import cmd_opts 38 | isRemote = cmd_opts.share or cmd_opts.ngrok or cmd_opts.listen or cmd_opts.server_name 39 | accessLevel = int(os.getenv("CONTROLNET_FASTLOAD_FILTER_ACCESS_CONTROL", -1)) if isRemote else 2 40 | accessToken = str(os.getenv("CONTROLNET_FASTLOAD_FILTER_ACCESS_TOKEN", "")) if accessLevel <= 1 else "" 41 | tabDebug = True if os.getenv("CONTROLNET_FASTLOAD_DEBUG", "") == "True" else False # Only for self-test 42 | viewPathSelectList = ["txt2img", "img2img", "manually"] if accessLevel > 1 else ["txt2img", "img2img"] 43 | viewPathSelectList = [] if accessLevel == 0 else viewPathSelectList 44 | print_info(f"Load Controlnet Fastload Filter on isRemote={isRemote} and accessLevel={accessLevel}") 45 | print_info(f"You have enabled access token in Controlnet Fastload Filter") if accessToken != "" else None 46 | with gr.Blocks(analytics_enabled=False) as ui_component: 47 | with gr.Column(): 48 | with gr.Row(): 49 | with gr.Column(scale=3): 50 | with gr.Row(): 51 | accessTokenInput = gr.Textbox(label="Access Token", visible=(accessToken != "")) 52 | accessTokenSubmit = ToolButton(value=flyEmoji, visible=(accessToken != "")) 53 | accessTokenRightSHA512 = gr.Textbox(visible=False, interactive=False, 54 | value=hashlib.sha512(accessToken.encode()).hexdigest()) 55 | accessTokenSubmit.click(_js="checkAccessToken", 56 | fn=fnaccessTokenSubmit, 57 | inputs=[accessTokenInput, accessTokenRightSHA512], 58 | outputs=[accessTokenInput, accessTokenSubmit]) 59 | with gr.Row(): 60 | currentDir = os.path.join(scripts.basedir(), opts.data.get("outdir_txt2img_samples")) 61 | if os.path.exists(currentDir) and accessLevel >= 1: 62 | path_, fast_way = currentDir, "txt2img" 63 | elif (not os.path.exists(currentDir)) and accessLevel >= 2: 64 | path_, fast_way = "", "manually" 65 | else: 66 | path_, fast_way = "", "" 67 | with gr.Column(scale=4): 68 | viewPath = gr.Textbox(value=path_, 69 | label="View Path", 70 | elem_id=f'{elemIdFlag}view_path', 71 | interactive=False) 72 | with gr.Column(scale=1, min_width=180): 73 | viewPathSelect = gr.Dropdown(label="View Path Select", 74 | choices=viewPathSelectList, 75 | value=fast_way, 76 | elem_id=f'{elemIdFlag}view_path_select') 77 | with gr.Row(): 78 | with gr.Column(min_width=100): 79 | firstPage = gr.Button("First Page") 80 | with gr.Column(min_width=100): 81 | prevPage = gr.Button("Prev Page") 82 | with gr.Column(min_width=100): 83 | pageIndex = gr.Number(value=1, label="Page Index") 84 | with gr.Column(min_width=100): 85 | nextPage = gr.Button("Next Page") 86 | with gr.Column(min_width=100): 87 | endPage = gr.Button("End Page") 88 | lastViewPath = gr.Textbox(visible=False, interactive=False) 89 | gallery = gr.Gallery(elem_id="_images_history_gallery", columns=6) 90 | with gr.Column(scale=2): 91 | with gr.Row(): 92 | # filterKey和filterValue绑定 93 | filterKey = gr.Dropdown(label="filter item", 94 | choices=["None"], 95 | value="None", 96 | elem_id=f'{elemIdFlag}view_path_select') 97 | filterValueDropDown = gr.Dropdown(label="filter value", choices=[], multiselect=True) 98 | filterValueTextbox = gr.Dropdown(label="filter value", value="", visible=False) 99 | filterAddAll = ToolButton(value=addEmoji, elem_id=f'{elemIdFlag}filter_button') 100 | filterManualSend = ToolButton(value=flyEmoji, elem_id=f'{elemIdFlag}filter_send') 101 | with gr.Row(): 102 | filterAll = gr.Dropdown(label="all filters", choices=[], multiselect=True, interactive=False) 103 | with gr.Row(): 104 | sendControlnetPriority = gr.Dropdown(label="send priority", 105 | choices=["Controlnet First", "Controlnet Fastload First", 106 | "Auto"], 107 | value="Auto") 108 | with gr.Row(): 109 | diff = gr.HighlightedText( 110 | label="ControlNet Info", 111 | combine_adjacent=False, 112 | show_legend=False, 113 | color_map={"include": "green"}) 114 | with gr.Row(): 115 | with (gr.Accordion("Other Info", open=False)): 116 | otherInfo = gr.HTML() 117 | selectPicAddress = gr.Textbox(value="", visible=False, interactive=False) 118 | selectPicControlnetAddress = gr.Textbox(value="", visible=False, interactive=False) 119 | pass 120 | with gr.Row(equal_height=True): 121 | tabDebugBox = gr.Textbox(value="True" if tabDebug else "False", visible=False) 122 | with gr.Column(min_width=80): 123 | sendTxt2img = gr.Button(value="Send to txt2img", elem_id=f'{elemIdFlag}send_txt2img') 124 | with gr.Column(min_width=80): 125 | sendImg2img = gr.Button(value="Send to img2img", elem_id=f'{elemIdFlag}send_img2img') 126 | with gr.Column(min_width=80): 127 | sendControlnetTxt2img = gr.Button(value="Send to Controlnet-txt2img", 128 | elem_id=f'{elemIdFlag}send_controlnet_txt2img') 129 | with gr.Column(min_width=80): 130 | sendControlnetImg2img = gr.Button(value="Send to Controlnet-img2img", 131 | elem_id=f'{elemIdFlag}send_controlnet_img2img') 132 | parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding( 133 | paste_button=sendTxt2img, tabname="txt2img", source_text_component=otherInfo, 134 | source_image_component=None, 135 | )) 136 | parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding( 137 | paste_button=sendImg2img, tabname="img2img", source_text_component=otherInfo, 138 | source_image_component=None, 139 | )) 140 | fnLoadPictureInputListBase = [viewPath, viewPathSelect, lastViewPath, filterAll, filterKey, pageIndex] 141 | fnLoadPictureInputList = lambda obj: fnLoadPictureInputListBase + [obj] 142 | fnLoadPictureOutputList = [lastViewPath, gallery, filterKey, pageIndex, 143 | diff, otherInfo, filterAll, filterValueDropDown] 144 | sendControlnetTxt2img.click(fn=None, _js="sendToAny2img", 145 | inputs=[selectPicAddress, selectPicControlnetAddress, 146 | sendControlnetPriority, sendControlnetTxt2img, tabDebugBox]) 147 | sendControlnetImg2img.click(fn=None, _js="sendToAny2img", 148 | inputs=[selectPicAddress, selectPicControlnetAddress, 149 | sendControlnetPriority, sendControlnetImg2img, tabDebugBox]) 150 | viewPath.change(fn=fnViewPathChange, 151 | inputs=[viewPath, viewPathSelect, lastViewPath], 152 | outputs=fnLoadPictureOutputList) 153 | viewPathSelect.select(fn=fnViewPathSelect, 154 | inputs=viewPathSelect, 155 | outputs=viewPath) 156 | # 绑定左面五个事件 157 | firstPage.click(fn=fnLoadPicture, 158 | inputs=fnLoadPictureInputList(firstPage), 159 | outputs=fnLoadPictureOutputList) 160 | prevPage.click(fn=fnLoadPicture, 161 | inputs=fnLoadPictureInputList(prevPage), 162 | outputs=fnLoadPictureOutputList) 163 | nextPage.click(fn=fnLoadPicture, 164 | inputs=fnLoadPictureInputList(nextPage), 165 | outputs=fnLoadPictureOutputList) 166 | endPage.click(fn=fnLoadPicture, 167 | inputs=fnLoadPictureInputList(endPage), 168 | outputs=fnLoadPictureOutputList) 169 | pageIndex.input(fn=fnLoadPicture, 170 | inputs=fnLoadPictureInputListBase, 171 | outputs=fnLoadPictureOutputList) 172 | filterManualSend.click(fn=fnLoadPicture, 173 | inputs=fnLoadPictureInputListBase, 174 | outputs=fnLoadPictureOutputList) 175 | # 绑定filterKey变换事件, 注意可以返回gr.update 176 | filterKey.input(fn=fnFilterKeyChange, 177 | inputs=[filterKey, filterAll], 178 | outputs=[filterValueDropDown, filterValueTextbox, filterAll]) 179 | # 绑定+按钮事件 180 | filterAddAll.click(fn=fnFilterAddAll, 181 | inputs=[filterKey, filterValueDropDown, filterValueTextbox, filterAll], 182 | outputs=[filterAll]) 183 | gallery.select(fn=fnGallerySelect, 184 | inputs=[gallery, filterAll], 185 | outputs=[diff, otherInfo, selectPicAddress, selectPicControlnetAddress]) 186 | return [(ui_component, "Controlnet Fastload Filter", "controlnet_fastload_filter")] 187 | 188 | 189 | def fnViewPathChange(viewPath: str, viewPathSelect: str, lastViewPath: str) -> list: 190 | if viewPathSelect != "manually": 191 | return fnLoadPicture(viewPath, viewPathSelect, lastViewPath, [], [], 1) 192 | else: 193 | return [lastViewPath, [], gr.update(choices=["None"]), 1, [], "", gr.update(value=[]), gr.update(value=[])] 194 | 195 | 196 | def fnaccessTokenSubmit(accessTokenInput: str, accessTokenRightSHA512: str) -> list: 197 | global accessLevel 198 | if accessTokenInput == str(os.getenv("CONTROLNET_FASTLOAD_FILTER_ACCESS_TOKEN", "")): 199 | accessLevel = 2 200 | return [gr.update(visible=False), gr.update(visible=False)] 201 | else: 202 | return [gr.update(visible=True), gr.update(visible=True)] 203 | 204 | 205 | def fnGallerySelect(selectData: gr.SelectData, gallery: list, filterAll: list) -> list: 206 | selectFile = gallery[selectData.index]['name'] # 它在临时文件夹,需要sha256寻找真相 207 | with open(selectFile, 'rb') as f: 208 | originalFile = picSHA256[hashlib.sha256(f.read()).hexdigest()] 209 | with Image.open(selectFile) as img: 210 | if "parameters" in img.info: 211 | infoList = extractControlNet(selectFile, img.info['parameters'], {}, "diff") 212 | else: 213 | infoList = [] 214 | result = [] 215 | for info in range(len(infoList)): 216 | for item in infoList[info]: 217 | filter_ = f"{item[0]} - {item[1]}" 218 | if filter_ in filterAll: 219 | result.append((f"[ControlNet {info}] {filter_}\n", "include")) 220 | else: 221 | result.append((f"[ControlNet {info}] {filter_}\n", None)) 222 | returnCNFilePath = judgeControlnetDataFile(originalFile, gallery[selectData.index]['data']) 223 | return [result, img.info['parameters'] if "parameters" in img.info else "", 224 | gallery[selectData.index]['data'], returnCNFilePath] 225 | 226 | 227 | def fnViewPathSelect(viewPathSelect: str) -> dict: 228 | if viewPathSelect == "txt2img": 229 | return gr.update(value=os.path.join(scripts.basedir(), opts.data.get("outdir_txt2img_samples")), 230 | interactive=False) 231 | elif viewPathSelect == "img2img": 232 | return gr.update(value=os.path.join(scripts.basedir(), opts.data.get("outdir_img2img_samples")), 233 | interactive=False) 234 | else: 235 | return gr.update(value="", interactive=True) 236 | 237 | 238 | def fnFilterKeyChange(filterKey: str, filterAll: list) -> list: 239 | picDict = {} 240 | tmpList = [] if filterKey == "None" else [f"{filterKey} - {itm}" for itm in picDict[filterKey].keys()] 241 | return [gr.update(visible=True, choices=tmpList, value=[]), gr.update(visible=False), filterAll] 242 | 243 | 244 | def fnFilterAddAll(filterKey: str, filterValueDropDown: list, filterValueTextbox: str, filterAll: list) -> list: 245 | unique_filterAll = set(filterAll) 246 | if len(filterValueDropDown) > 0: 247 | unique_filterAll.update(filterValueDropDown) 248 | return list(unique_filterAll) 249 | 250 | 251 | def fnLoadPicture(*args) -> list: 252 | viewPath, viewPathSelect, lastViewPath, filterAll, filterKey, pageIndex = args[:6] 253 | global allViewData 254 | if accessLevel <= 0: 255 | raise gr.Error("You have no permission to use this function") 256 | if not (os.path.exists(viewPath) and os.path.isdir(viewPath)): 257 | raise gr.Error(f"ViewPath {viewPath} does not exist or not a folder") 258 | if viewPath != lastViewPath: 259 | # 全新加载 260 | filepathList, picDict = loadPicture(viewPath) 261 | allViewData[viewPath] = viewDataWrap(filepathList, picDict) 262 | tmpFilterKey = list(picDict.keys()) 263 | tmpFilterKey.insert(0, "None") 264 | displayPic, pageIndex_ = loadDisplayPic(*args, 265 | filepathList_=filepathList, pageIndex_=pageIndex) 266 | calculateSHA256(displayPic) 267 | # 第一次无法过滤,直接展示全部图片 268 | return [viewPath, gr.update(value=displayPic), gr.update(choices=tmpFilterKey), 269 | gr.update(value=pageIndex_), [], "", gr.update(value=[]), gr.update(value=[])] 270 | else: 271 | # 直接对filepathList进行筛选 272 | filepathList, picDict = (allViewData[viewPath].filepathList, allViewData[viewPath].picDict) 273 | allSet = set(filepathList) 274 | for itm in filterAll: 275 | key, val = itm.split(" - ") 276 | smallSet = picDict[key][val] 277 | allSet = allSet.intersection(smallSet) 278 | displayPic, pageIndex_ = loadDisplayPic(*args, filepathList_=list(allSet), pageIndex_=pageIndex) 279 | calculateSHA256(displayPic) 280 | return [viewPath, gr.update(value=displayPic), filterKey, 281 | gr.update(value=pageIndex_), [], "", gr.update(), gr.update()] 282 | 283 | 284 | def loadDisplayPic(*args, **kwargs) -> Tuple[List[str], int]: 285 | pageEnum = { 286 | "First Page": 0, 287 | "Prev Page": -1, 288 | "Next Page": 1, 289 | "End Page": -1 290 | } 291 | argsLenLimit = 6 292 | perPagePicNum = 36 293 | pageIndex_, filepathList_ = kwargs["pageIndex_"], kwargs["filepathList_"] 294 | displayAllPic = [filepathList_[i:i + perPagePicNum] for i in range(0, len(filepathList_), perPagePicNum)] 295 | # fix pageIndex_ 296 | pageIndex_ = 1 if pageIndex_ is None else pageIndex_ 297 | pageIndex_ = pageIndex_ if 1 <= pageIndex_ <= len(displayAllPic) else 1 298 | # 无翻页操作 299 | if len(args) <= argsLenLimit: 300 | displayPic = filepathList_ 301 | # 有翻页操作, 首尾 302 | elif len(args) > argsLenLimit and (args[argsLenLimit] == "First Page" or args[argsLenLimit] == "End Page"): 303 | pageIndex_ = 1 if args[argsLenLimit] == "First Page" else len(displayAllPic) 304 | displayPic = displayAllPic[pageIndex_ - 1] 305 | # 有翻页操作,前后 306 | else: 307 | pageIndex_ = pageIndex_ + pageEnum[args[argsLenLimit]] 308 | pageIndex_ = pageIndex_ if 1 <= pageIndex_ <= len(displayAllPic) else pageIndex_ - pageEnum[args[argsLenLimit]] 309 | displayPic = displayAllPic[int(pageIndex_) - 1] 310 | return displayPic, pageIndex_ 311 | 312 | 313 | def loadPicture(filepath: str) -> Tuple[List[str], dict]: 314 | filepathList_ = [] 315 | picDict_ = {"preprocessor": {}, "model": {}, "weight": {}, "starting/ending": {}, "resize mode": {}, 316 | "pixel perfect": {}, "control mode": {}, "preprocessor params": {}} 317 | for folderName, subFolders, fileNames in os.walk(filepath): 318 | for fileName in fileNames: 319 | fullname = os.path.join(folderName, fileName) 320 | # 处理png_info 321 | try: 322 | with Image.open(fullname) as img: 323 | if "parameters" in img.info: 324 | extractControlNet(fullname, img.info['parameters'], picDict_, "init") 325 | filepathList_.append(fullname) 326 | except (PIL.UnidentifiedImageError, IOError, OSError, ValueError): 327 | pass 328 | return filepathList_, picDict_ 329 | 330 | 331 | def extractControlNet(fullname: str, pngInfo: str, picDict_: dict, mode: str) -> list: 332 | res = re.findall(r'ControlNet[^"]+"([^"]+)"', pngInfo) 333 | pairList = [] 334 | for itm in res: 335 | pairs = re.findall(r'\s*([^:,]+):\s*(\([^)]+\)|[^,]+)(?:,|$)', itm) 336 | if mode == "init": 337 | for key, value in pairs: 338 | picDict_.setdefault(key, {}).setdefault(value, set()).add(fullname) 339 | elif mode == "diff": 340 | pairList.append(pairs) 341 | else: 342 | pass 343 | return pairList if mode == "diff" else None 344 | 345 | 346 | def calculateSHA256(fileList: list) -> None: 347 | global picSHA256 348 | for file in fileList: 349 | with open(file, 'rb') as f: 350 | picSHA256[hashlib.sha256(f.read()).hexdigest()] = file 351 | 352 | 353 | script_callbacks.on_ui_tabs(on_ui_tabs) 354 | -------------------------------------------------------------------------------- /scripts/setting.py: -------------------------------------------------------------------------------- 1 | import gradio as gr 2 | from modules import shared 3 | from modules import script_callbacks 4 | 5 | 6 | def on_ui_settings(): 7 | section = ('controlnet-fastload', "Controlnet Fastload") 8 | shared.opts.add_option( 9 | "isEnabledManualSend", 10 | shared.OptionInfo( 11 | False, 12 | "Allow manually uploading pnginfo from the uploaded plugin image to txt2img or img2img.", 13 | gr.Checkbox, 14 | section=section).needs_restart() 15 | ) 16 | shared.opts.add_option( 17 | "saveControlnet", 18 | shared.OptionInfo( 19 | "Extra .cni file", 20 | "Where to save Controlnet data?", 21 | gr.Dropdown, 22 | lambda: {"choices": ["Embed photo", "Extra .cni file", "Both"]}, 23 | section=section) 24 | ) 25 | shared.opts.add_option( 26 | "overwritePriority", 27 | shared.OptionInfo( 28 | "ControlNet Plugin First", 29 | "If the ControlNet Plugin is enabled, which do you use first?", 30 | gr.Dropdown, 31 | lambda: {"choices": ["ControlNet Plugin First", "ControlNet Fastload Plugin First"]}, 32 | section=section) 33 | ) 34 | 35 | 36 | script_callbacks.on_ui_settings(on_ui_settings) 37 | --------------------------------------------------------------------------------