├── .gitignore
├── preview_1.png
├── preview_2.png
├── preview_3.png
├── scripts
├── api_package.py
├── setting.py
├── api.py
├── fastload.py
└── fastload_view.py
├── localizations
└── zh_CN.json
├── README_zh_CN.md
├── README.md
├── .github
└── ISSUE_TEMPLATE
│ └── bug_report.yml
├── javascript
└── fastload_view.js
└── LICENCE
/.gitignore:
--------------------------------------------------------------------------------
1 | /__pycache__/
--------------------------------------------------------------------------------
/preview_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pk5ls20/sd-webui-controlnet-fastload/HEAD/preview_1.png
--------------------------------------------------------------------------------
/preview_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pk5ls20/sd-webui-controlnet-fastload/HEAD/preview_2.png
--------------------------------------------------------------------------------
/preview_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pk5ls20/sd-webui-controlnet-fastload/HEAD/preview_3.png
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
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 | 
23 | ### 预览嵌入信息
24 | 
25 | ### Controlnet Fastload筛选Tab
26 | 
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 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/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 |
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 | 
23 | ### View embedded information
24 | 
25 | ### Controlnet Fastload Filter tab
26 | 
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 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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);
--------------------------------------------------------------------------------
/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.
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------